Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions permuta/bisc/bisc.py
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,32 @@ def add_length(self, length: int) -> None:
good_count += 1
self._keep(length, bytes(bitmap), good_count)

def __copy__(self) -> "PropertyBitmaps":
"""Return a copy of self whose dictionaries of lengths are its own.

copy.copy calls this. Adding a length to either, or labeling one again,
leaves the other as it was, and the views of the copy, good, bad and
bitmaps, read the copy. The copy is a PropertyBitmaps, whatever the
class of self.

Examples:
>>> import copy
>>> increasing = PropertyBitmaps(Perm.is_increasing)
>>> increasing.add_length(3)
>>> copied = copy.copy(increasing)
>>> copied.add_length(4)
>>> sorted(increasing.good_counts), sorted(copied.good_counts)
([3], [3, 4])
>>> 4 in copied.good, 4 in increasing.good
(True, False)
"""
copied = PropertyBitmaps(self.prop)
# A length's labels are replaced when it is labeled again, never
# changed, so the copy shares them: only the dictionaries are its own
copied._labels = dict(self._labels)
copied.good_counts = dict(self.good_counts)
return copied


class _LengthKeys:
"""The keys of a PropertyBitmaps' Mappings, bitmaps and the views: the
Expand Down
29 changes: 29 additions & 0 deletions tests/bisc/test_property_bitmaps.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import contextlib
import copy
import importlib
import io
import os
Expand Down Expand Up @@ -474,3 +475,31 @@ def recording_mine(goodperms, M, N=None, report=False):
auto_bisc((good, bad))
assert given[0][0] == []
assert given[0][3] == [perm for perm in Perm.of_length(3) if perm.stack_sortable()]


@pytest.mark.parametrize("copy_function", [copy.copy, copy.deepcopy])
def test_a_copy_keeps_its_lengths_apart_from_the_original(copy_function):
# Lengths 0 to 4 are kept as bitmaps, and 5 as the index of its one good
# permutation, as fewer than one in 64 are good from length 5 on
original = property_bitmaps_up_to(Perm.is_increasing, 5)
copied = copy_function(original)
assert copied.good_counts == original.good_counts
for length in range(6):
assert list(copied.good[length]) == list(original.good[length])
assert copied.bitmaps[length] == original.bitmaps[length]
# A length added to one is not added to the other, and the views of each
# read its own lengths
copied.add_length(6)
original.add_length(7)
assert sorted(copied.good_counts) == list(range(7))
assert sorted(original.good_counts) == [0, 1, 2, 3, 4, 5, 7]
assert 6 in copied.good and 6 not in original.good
assert 6 in copied.bad and 6 not in original.bad
assert 7 in original.bad and 7 not in copied.bitmaps
assert 7 not in copied.bad
assert list(copied.good[6]) == [Perm(range(6))]
# A length labeled again, with another property, changes only in the copy
copied.prop = Perm.is_decreasing
copied.add_length(3)
assert list(copied.good[3]) == [Perm((2, 1, 0))]
assert list(original.good[3]) == [Perm((0, 1, 2))]
56 changes: 56 additions & 0 deletions tests/bisc/test_sparse_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
_SPARSE_WHEN_ONE_IN is good. Each way in chooses the form by the number of good
permutations, and every view reads both forms."""

import copy
import importlib
import itertools
import random
Expand Down Expand Up @@ -896,3 +897,58 @@ def test_a_sequence_that_is_not_a_permutation_is_refused(not_a_permutation, rank
bisc_module._from_perms_by_length({length: perms})
with pytest.raises(ValueError, match=message):
bisc(perms, 1, length)


@pytest.mark.parametrize("relabeled", ["copy", "original"])
def test_relabeling_a_length_kept_as_indices_changes_only_one(relabeled):
"""A copy shares each length's labels with the original, which holds while
a length's labels are replaced and never changed: labeling lengths kept as
indices again, in the copy or in the original, leaves the other as it
was."""
original = PropertyBitmaps(Perm.is_increasing)
for length in range(7):
original.add_length(length)
assert is_kept_as_indices(original, 5) and is_kept_as_indices(original, 6)
copied = copy.copy(original)
changed, kept = (copied, original) if relabeled == "copy" else (original, copied)
changed.prop = Perm.is_decreasing
for length in (5, 6):
changed.add_length(length)
for length in (5, 6):
assert list(changed.good[length]) == [Perm(range(length - 1, -1, -1))]
assert list(kept.good[length]) == [Perm(range(length))]
assert kept.good_counts[length] == 1
assert Perm(range(length)) in kept.good[length]
assert Perm(range(length)) in changed.bad[length]


def test_a_copy_of_lengths_kept_as_indices_sizes_no_bitmap(monkeypatch):
"""A copy of lengths kept as indices, in an array and in a list, of a
PropertyBitmaps with no property, sizes no bitmap, and is apart from the
original."""
guard_bitmap_sizes(monkeypatch)
last_of_21 = Perm.unrank(2**63 + 5, 21)
original = bisc_module._from_perms_by_length(
{
3: [Perm((0, 1, 2))],
12: [Perm(range(12))],
14: [Perm(range(14)), Perm(range(13, -1, -1))],
21: [last_of_21],
25: [],
}
)
copied = copy.copy(original)
assert copied.prop is None
assert copied.good_counts == {3: 1, 12: 1, 14: 2, 21: 1, 25: 0}
assert list(copied.good[3]) == [Perm((0, 1, 2))]
assert copied.bitmaps[3] == b"\x01"
assert list(copied.good[12]) == [Perm(range(12))]
assert list(copied.good[14]) == [Perm(range(14)), Perm(range(13, -1, -1))]
assert list(copied.good[21]) == [last_of_21]
assert list(copied.good[25]) == []
assert Perm(range(25)) in copied.bad[25]
# The copy has a property of its own to add a length with
copied.prop = Perm.is_increasing
copied.add_length(4)
assert 4 in copied.good and 4 not in original.good
assert sorted(original.good_counts) == [3, 12, 14, 21, 25]
Loading