Skip to content

WiP: setitem with slices for matrix, generation by rows #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 5, 2022
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Plans what I want to do:
- [Learn how to make a package](https://packaging.python.org/en/latest/tutorials/packaging-projects/)
- Add Fraction class and use it in Chance
- Divide documentation to several MD files in docs/
95 changes: 81 additions & 14 deletions src/matrix/matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __setitem__(
key: _MKT,
value: _MVT,
) -> None:
# pylint: disable=R0912
# Type Check
if not isinstance(key, tuple):
raise IndexError("Index must be tuple")
Expand All @@ -60,10 +61,42 @@ def __setitem__(
if isinstance(key[1], int) and (key[1] < 0 or key[1] >= self._height):
raise IndexError("Matrix doesn't support negative or overflow indexes")

# Check and recreate slices
if isinstance(key[0], slice):
if key[0].step is not None:
raise NotImplementedError
key = slice(key[0].start or 0, key[0].stop or self._width, None), key[1]
if isinstance(key[1], slice):
if key[1].step is not None:
raise NotImplementedError
key = key[0], slice(key[1].start or 0, key[1].stop or self._height, None)

if isinstance(key[0], int) and isinstance(key[1], int):
self._values[key[1]][key[0]] = value
else:
raise NotImplementedError()
elif isinstance(key[0], slice) and isinstance(key[1], int):
if not isinstance(value, list):
raise ValueError
if key[0].stop - key[0].start != len(value):
raise ValueError
self._values[key[1]][key[0]] = value
elif isinstance(key[0], int) and isinstance(key[1], slice):
if not isinstance(value, list):
raise ValueError
if key[1].stop - key[1].start != len(value):
raise ValueError
for j, val in zip(range(key[1].start, key[1].stop), value):
self._values[j][key[0]] = val
elif isinstance(key[0], slice) and isinstance(key[1], slice):
if not isinstance(value, list):
raise ValueError
if not all(isinstance(val, list) for val in value):
raise ValueError
if key[1].stop - key[1].start != len(value):
raise ValueError
if not all(len(val) == key[0].stop - key[0].start for val in value):
raise ValueError
for j, val in zip(range(key[1].start, key[1].stop), value):
self._values[j][key[0]] = val

def __contains__(self, item: Union[Matrix[_MVT], List[List[_MVT]]]) -> bool:
other = Matrix.from_nested_list(item) if isinstance(item, List) else item
Expand Down Expand Up @@ -105,10 +138,19 @@ def generate(
cls,
width,
height,
value: Union[Callable[[int, int], _MVT], Callable[[], _MVT], _MVT, Iterator],
value: Union[
Callable[[int, int], _MVT],
Callable[[int], List[_MVT]],
Callable[[], _MVT],
Callable[[], List[_MVT]],
_MVT,
Iterator,
],
*,
by_rows: bool = False,
walkthrow: Walkthrow = Walkthrow.DEFAULT, # type: ignore # pylint: disable=W0613 # TODO
):
# pylint: disable=R0912
"""Generates matrix from size and generator, for example (2, 2, lambda x,y: x+y"""
if not isinstance(width, int) or not isinstance(height, int):
raise TypeError
Expand All @@ -117,21 +159,36 @@ def generate(

values = []
for j in range(height):
row = []
for i in range(width):
if not by_rows:
row = []
for i in range(width):
if callable(value):
if value.__code__.co_argcount == 2: # noqa
row.append(value(i, j)) # type: ignore
elif value.__code__.co_argcount == 0: # noqa
row.append(value()) # type: ignore
else:
raise ValueError("Incorrect number of arguments for generator")
elif isinstance(value, Iterator):
row.append(next(value))
else:
row.append(value)
else:
if callable(value):
if value.__code__.co_argcount == 2: # noqa
row.append(value(i, j)) # type: ignore
if value.__code__.co_argcount == 1: # noqa
row = list(value(j)) # type: ignore
elif value.__code__.co_argcount == 0: # noqa
row.append(value()) # type: ignore
row = list(value()) # type: ignore
else:
raise ValueError("Incorrect number of arguments for generator")
elif isinstance(value, Iterator):
row.append(next(value))
row = list(next(value))
elif isinstance(value, list):
row = list(value)
else:
row.append(value)
raise ValueError
values.append(row)
return cls(width=width, height=height, values=values)
return cls(width=width, height=height, values=values) # type: ignore

@classmethod
def from_nested_list(cls, values: List[List[_MVT]]) -> Matrix[_MVT]:
Expand Down Expand Up @@ -162,17 +219,27 @@ def from_joined_lists(cls, width: int, height: int = None, *, values: Union[List
def from_lists(cls, *lists: List[_MVT]) -> Matrix:
return cls.from_nested_list(values=list(lists))

@staticmethod
def __postprocess_input_matrix_by_rows(input_str: str) -> List[_MVT]:
return list(map(int.__call__, input_str.split()))

@classmethod
def input_matrix(
cls,
height: Optional[int] = None,
width: Optional[int] = None,
postprocess: Callable[[str], _MVT] = int.__call__,
postprocess: Union[Callable[[str], _MVT], Callable[[str], List[_MVT]]] = None, # noqa
*,
width_first: bool = False,
# by_rows: bool = False, # TODO
by_rows: bool = False,
walkthrow: Walkthrow = Walkthrow.DEFAULT,
) -> Matrix: # pragma: no cover
if postprocess is None:
if by_rows:
postprocess = cls.__postprocess_input_matrix_by_rows
else:
postprocess = int.__call__
assert postprocess is not None
if width_first:
height = height or int(input())
width = width or int(input())
Expand All @@ -181,7 +248,7 @@ def input_matrix(
height = height or int(input())
assert isinstance(width, int)
assert isinstance(height, int)
return cls.generate(width, height, lambda: postprocess(input()), walkthrow=walkthrow)
return cls.generate(width, height, lambda: postprocess(input()), by_rows=by_rows, walkthrow=walkthrow) # type: ignore

def transpose(self) -> None:
self._values = [[self._values[j][i] for j in range(len(self._values))] for i in range(len(self._values[0]))]
Expand Down
61 changes: 55 additions & 6 deletions tests/test_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,21 @@ def test_generation_lambda(self):
m = NumericMatrix.from_joined_lists(3, values=range(9))
assert m == [[0, 1, 2], [3, 4, 5], [6, 7, 8]]

m = NumericMatrix.generate(3, 3, [1, 2, 3], by_rows=True)
assert m == [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
m = NumericMatrix.generate(3, 4, lambda row_id: [row_id, row_id * 2, row_id**2], by_rows=True)
assert m == [[0, 0, 0], [1, 2, 1], [2, 4, 4], [3, 6, 9]]
m = NumericMatrix.generate(3, 2, lambda: list((1, 2, 3)), by_rows=True)
assert m == [[1, 2, 3], [1, 2, 3]]

def range_lists_iterator(count: int, list_len: int):
rng = range(count)
for element in rng:
yield [element] * list_len

m = NumericMatrix.generate(3, 4, range_lists_iterator(4, 3), by_rows=True)
assert m == [[0, 0, 0], [1, 1, 1], [2, 2, 2], [3, 3, 3]]

def test_errors(self):
self.assertRaises(ValueError, lambda: NumericMatrix.from_lists())
self.assertRaises(ValueError, lambda: NumericMatrix.from_lists([]))
Expand All @@ -137,6 +152,7 @@ def test_errors(self):
self.assertRaises(ValueError, lambda: NumericMatrix.from_nested_list([[], [], []]))
self.assertRaises(ValueError, lambda: NumericMatrix.from_nested_list([[1, 2], [3, 4], [5, 6, 7], [8, 9]]))
self.assertRaises(ValueError, lambda: NumericMatrix.generate(3, 3, lambda x, y, z: x + y + z))
self.assertRaises(ValueError, lambda: NumericMatrix.generate(3, 3, lambda x, y: x + y, by_rows=True))
self.assertRaises(TypeError, lambda: NumericMatrix.zero_matrix("test")) # noqa
self.assertRaises(TypeError, lambda: NumericMatrix.zero_matrix((1, 2, 3))) # noqa
self.assertRaises(TypeError, lambda: BitMatrix.zero_matrix("test")) # noqa
Expand Down Expand Up @@ -192,8 +208,6 @@ def set_val(index, value):


class Slicing(unittest.TestCase):
# TODO: Add test setitem

def setUp(self) -> None:
self.m = NumericMatrix.from_joined_lists(4, values=range(20))
# 0 1 2 3
Expand All @@ -210,19 +224,52 @@ def test_vertical_slice(self):
assert self.m[0, 1:3] == [4, 8], self.m[0, 1:3]
assert self.m[2, 2:] == [10, 14, 18]

def test_set_vertical_slice(self):
self.m[1, :] = [1, 2, 3, 4, 5]
assert self.m[1, :] == [1, 2, 3, 4, 5]
assert self.m[0:3, :] == [[0, 1, 2], [4, 2, 6], [8, 3, 10], [12, 4, 14], [16, 5, 18]]
self.m[2, 1:3] = [0, 0]
assert self.m[2, 1:3] == [0, 0]
assert self.m[0:3, :] == [[0, 1, 2], [4, 2, 0], [8, 3, 0], [12, 4, 14], [16, 5, 18]]

def test_horizontal_slice(self):
assert self.m[:, 1] == [4, 5, 6, 7], self.m[:, 1]
assert self.m[1:3, 0] == [1, 2], self.m[1:3, 0]
assert self.m[:, 1] == [4, 5, 6, 7]
assert self.m[1:3, 0] == [1, 2]
assert self.m[2:, 2] == [10, 11]

def test_set_horizontal_slice(self):
self.m[:, 1] = [-1] * 4
assert self.m[:, 1] == [-1] * 4
assert self.m[0:3, :] == [[0, 1, 2], [-1, -1, -1], [8, 9, 10], [12, 13, 14], [16, 17, 18]]
self.m[2:, 2] = [-5, 5]
assert self.m[0:, :] == [[0, 1, 2, 3], [-1, -1, -1, -1], [8, 9, -5, 5], [12, 13, 14, 15], [16, 17, 18, 19]]

def test_both_slice(self):
assert self.m[:, :] == self.m._values
assert self.m[0:3, 0:3] == [[0, 1, 2], [4, 5, 6], [8, 9, 10]]
assert self.m[1:3, 1:4] == [[5, 6], [9, 10], [13, 14]]
assert self.m[2:, 2:] == [[10, 11], [14, 15], [18, 19]]

def test_set_both(self):
self.m[1:3, 1:4] = [[-1, -2], [-3, -4], [-5, -6]]
assert self.m[1:3, 1:4] == [[-1, -2], [-3, -4], [-5, -6]], self.m[1:3, 1:4]
assert self.m == [[0, 1, 2, 3], [4, -1, -2, 7], [8, -3, -4, 11], [12, -5, -6, 15], [16, 17, 18, 19]]

def test_index_error(self):
pass
def set_val(index, value):
self.m[index] = value

self.assertRaises(NotImplementedError, lambda: set_val((slice(None, None, -1), 1), [1, 2, 3]))
self.assertRaises(NotImplementedError, lambda: set_val((slice(1, 2), slice(None, None, 2)), [1, 2, 3]))
self.assertRaises(ValueError, lambda: set_val((1, slice(1, 2)), 123))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), 2), 123))
self.assertRaises(ValueError, lambda: set_val((1, slice(1, 3)), [1, 2, 3]))
self.assertRaises(ValueError, lambda: set_val((slice(1, 3), 2), [1, 2, 3]))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), slice(1, 2)), 123))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), slice(1, 2)), [1, 2]))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), slice(1, 2)), [1]))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), slice(1, 2)), [[1, 2]]))
self.assertRaises(ValueError, lambda: set_val((slice(1, 2), slice(1, 2)), [[1, 2], [1, 2]]))

def test_minor(self):
assert self.m3.get_minor(0, 0) == Matrix(2, 2, [[4, 5], [7, 8]])
Expand All @@ -244,6 +291,7 @@ def setUp(self) -> None:
self.F = NumericMatrix.from_lists([1, 1], [2, 2])
self.G = NumericMatrix.from_lists([-1, -2], [1, 2])
self.H = NumericMatrix.from_lists([0, -1], [3, 4])
self.I = NumericMatrix.from_lists([1, 3, 5], [2, 4, 6], [0, -1, -2])

def test_mul_to_number(self):
assert self.E * 3 == [[3, 0, 0], [0, 3, 0], [0, 0, 3]]
Expand Down Expand Up @@ -297,7 +345,8 @@ def test_mul_matrix(self):
assert NumericMatrix.from_lists([2, 0], [1, 9]) * NumericMatrix.from_lists(
[3, 9], [4, 7]
) == NumericMatrix.from_lists([6, 18], [39, 72])
# FIXME: assert self.C * self.E == self.C
assert self.I * self.E == self.I
assert self.E * self.I == self.I
self.assertRaises(AttributeError, lambda: self.E * self.F)

def test_inverse_matrix(self):
Expand Down