From 41c2f0c9d2b72037edec5a03c275967d147c7102 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 27 Mar 2023 21:46:24 +0200 Subject: [PATCH 1/6] feat: `sort_rows` of a `Table` --- src/safeds/data/tabular/containers/_table.py | 53 +++++++++++++------ .../containers/_table/test_sort_rows.py | 25 +++++++++ 2 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 tests/safeds/data/tabular/containers/_table/test_sort_rows.py diff --git a/src/safeds/data/tabular/containers/_table.py b/src/safeds/data/tabular/containers/_table.py index 2ff97c833..3f539862e 100644 --- a/src/safeds/data/tabular/containers/_table.py +++ b/src/safeds/data/tabular/containers/_table.py @@ -272,7 +272,7 @@ def get_column(self, column_name: str) -> Column: if self._schema.has_column(column_name): output_column = Column( self._data.iloc[ - :, [self._schema._get_column_index_by_name(column_name)] + :, [self._schema._get_column_index_by_name(column_name)] ].squeeze(), column_name, self._schema.get_type_of_column(column_name), @@ -853,24 +853,24 @@ def slice( def sort_columns( self, - query: Callable[[Column, Column], int] = lambda col1, col2: ( - col1.name > col2.name - ) - - (col1.name < col2.name), + sorter: Callable[[Column, Column], int] = + lambda col1, col2: (col1.name > col2.name) - (col1.name < col2.name), ) -> Table: """ - Sort a table with the given lambda function. - If no function is given the columns will be sorted alphabetically. - This function uses the default python sort algorithm. - The query returns - 0, if both columns are equal. - < 0, if the first column should be ordered after the second column. - > 0, if the first column should be ordered before the second column. + Sort the columns of a `Table` with the given sorter and return a new `Table`. The original table is not + modified. + + The sorter is a function that takes two columns `col1` and `col2` and returns an integer. If `col1` should be + ordered before `col2`, the function should return a negative number. If `col1` should be ordered after `col2`, + the function should return a positive number. If the original order of `col1` and `col2` should be kept, the + function should return 0. + + If no sorter is given, the columns will be sorted alphabetically by their name. Parameters ---------- - query : a lambda function - The lambda function used to sort the columns. + sorter : Callable[[Column, Column], int] + The function used to sort the columns. Returns ------- @@ -878,9 +878,32 @@ def sort_columns( A new table with sorted columns. """ columns = self.to_columns() - columns.sort(key=functools.cmp_to_key(query)) + columns.sort(key=functools.cmp_to_key(sorter)) return Table.from_columns(columns) + def sort_rows(self, sorter: Callable[[Row, Row], int]) -> Table: + """ + Sort the rows of a `Table` with the given sorter and return a new `Table`. The original table is not modified. + + The sorter is a function that takes two rows `row1` and `row2` and returns an integer. If `row1` should be + ordered before `row2`, the function should return a negative number. If `row1` should be ordered after `row2`, + the function should return a positive number. If the original order of `row1` and `row2` should be kept, the + function should return 0. + + Parameters + ---------- + sorter : Callable[[Row, Row], int] + The function used to sort the rows. + + Returns + ------- + new_table : Table + A new table with sorted rows. + """ + rows = self.to_rows() + rows.sort(key=functools.cmp_to_key(sorter)) + return Table.from_rows(rows) + def split(self, percentage_in_first: float) -> typing.Tuple[Table, Table]: """ Split the table into two new tables. diff --git a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py new file mode 100644 index 000000000..32c2e12f8 --- /dev/null +++ b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py @@ -0,0 +1,25 @@ +from typing import Callable + +import pytest + +from safeds.data.tabular.containers import Table, Row, Column + + +@pytest.mark.parametrize( + ("table", "sorter", "expected"), + [ + # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. + # ( + # Table.from_columns([Column([], "col1")]), + # lambda row1, row2: row1["col1"] - row2["col1"], + # Table.from_columns([Column([], "col1")]) + # ), + ( + Table.from_columns([Column([3, 2, 1], "col1")]), + lambda row1, row2: row1["col1"] - row2["col1"], + Table.from_columns([Column([1, 2, 3], "col1")]) + ), + ] +) +def test_sort_rows(table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: + assert table.sort_rows(sorter) == expected From d1dc62eda2538d277cc09061f79229104d6d4b44 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 27 Mar 2023 21:51:16 +0200 Subject: [PATCH 2/6] docs: improve docstrings --- src/safeds/data/tabular/containers/_table.py | 41 +++++++++++--------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/safeds/data/tabular/containers/_table.py b/src/safeds/data/tabular/containers/_table.py index 3f539862e..e0c90f2ad 100644 --- a/src/safeds/data/tabular/containers/_table.py +++ b/src/safeds/data/tabular/containers/_table.py @@ -853,24 +853,25 @@ def slice( def sort_columns( self, - sorter: Callable[[Column, Column], int] = + comparator: Callable[[Column, Column], int] = lambda col1, col2: (col1.name > col2.name) - (col1.name < col2.name), ) -> Table: """ - Sort the columns of a `Table` with the given sorter and return a new `Table`. The original table is not + Sort the columns of a `Table` with the given comparator and return a new `Table`. The original table is not modified. - The sorter is a function that takes two columns `col1` and `col2` and returns an integer. If `col1` should be - ordered before `col2`, the function should return a negative number. If `col1` should be ordered after `col2`, - the function should return a positive number. If the original order of `col1` and `col2` should be kept, the - function should return 0. + The comparator is a function that takes two columns `col1` and `col2` and returns an integer: - If no sorter is given, the columns will be sorted alphabetically by their name. + * If `col1` should be ordered before `col2`, the function should return a negative number. + * If `col1` should be ordered after `col2`, the function should return a positive number. + * If the original order of `col1` and `col2` should be kept, the function should return 0. + + If no comparator is given, the columns will be sorted alphabetically by their name. Parameters ---------- - sorter : Callable[[Column, Column], int] - The function used to sort the columns. + comparator : Callable[[Column, Column], int] + The function used to compare two columns. Returns ------- @@ -878,22 +879,24 @@ def sort_columns( A new table with sorted columns. """ columns = self.to_columns() - columns.sort(key=functools.cmp_to_key(sorter)) + columns.sort(key=functools.cmp_to_key(comparator)) return Table.from_columns(columns) - def sort_rows(self, sorter: Callable[[Row, Row], int]) -> Table: + def sort_rows(self, comparator: Callable[[Row, Row], int]) -> Table: """ - Sort the rows of a `Table` with the given sorter and return a new `Table`. The original table is not modified. + Sort the rows of a `Table` with the given comparator and return a new `Table`. The original table is not + modified. + + The comparator is a function that takes two rows `row1` and `row2` and returns an integer: - The sorter is a function that takes two rows `row1` and `row2` and returns an integer. If `row1` should be - ordered before `row2`, the function should return a negative number. If `row1` should be ordered after `row2`, - the function should return a positive number. If the original order of `row1` and `row2` should be kept, the - function should return 0. + * If `col1` should be ordered before `col2`, the function should return a negative number. + * If `col1` should be ordered after `col2`, the function should return a positive number. + * If the original order of `col1` and `col2` should be kept, the function should return 0. Parameters ---------- - sorter : Callable[[Row, Row], int] - The function used to sort the rows. + comparator : Callable[[Row, Row], int] + The function used to compare two rows. Returns ------- @@ -901,7 +904,7 @@ def sort_rows(self, sorter: Callable[[Row, Row], int]) -> Table: A new table with sorted rows. """ rows = self.to_rows() - rows.sort(key=functools.cmp_to_key(sorter)) + rows.sort(key=functools.cmp_to_key(comparator)) return Table.from_rows(rows) def split(self, percentage_in_first: float) -> typing.Tuple[Table, Table]: From 3e937de3d51619535f7047264fde8c5381388bd7 Mon Sep 17 00:00:00 2001 From: lars-reimann Date: Mon, 27 Mar 2023 19:53:52 +0000 Subject: [PATCH 3/6] style: apply automated linter fixes --- src/safeds/data/tabular/containers/_table.py | 8 +++++--- .../data/tabular/containers/_table/test_sort_rows.py | 11 ++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/safeds/data/tabular/containers/_table.py b/src/safeds/data/tabular/containers/_table.py index e0c90f2ad..2c20b7ff2 100644 --- a/src/safeds/data/tabular/containers/_table.py +++ b/src/safeds/data/tabular/containers/_table.py @@ -272,7 +272,7 @@ def get_column(self, column_name: str) -> Column: if self._schema.has_column(column_name): output_column = Column( self._data.iloc[ - :, [self._schema._get_column_index_by_name(column_name)] + :, [self._schema._get_column_index_by_name(column_name)] ].squeeze(), column_name, self._schema.get_type_of_column(column_name), @@ -853,8 +853,10 @@ def slice( def sort_columns( self, - comparator: Callable[[Column, Column], int] = - lambda col1, col2: (col1.name > col2.name) - (col1.name < col2.name), + comparator: Callable[[Column, Column], int] = lambda col1, col2: ( + col1.name > col2.name + ) + - (col1.name < col2.name), ) -> Table: """ Sort the columns of a `Table` with the given comparator and return a new `Table`. The original table is not diff --git a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py index 32c2e12f8..080f27007 100644 --- a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py +++ b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py @@ -1,8 +1,7 @@ from typing import Callable import pytest - -from safeds.data.tabular.containers import Table, Row, Column +from safeds.data.tabular.containers import Column, Row, Table @pytest.mark.parametrize( @@ -17,9 +16,11 @@ ( Table.from_columns([Column([3, 2, 1], "col1")]), lambda row1, row2: row1["col1"] - row2["col1"], - Table.from_columns([Column([1, 2, 3], "col1")]) + Table.from_columns([Column([1, 2, 3], "col1")]), ), - ] + ], ) -def test_sort_rows(table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: +def test_sort_rows( + table: Table, sorter: Callable[[Row, Row], int], expected: Table +) -> None: assert table.sort_rows(sorter) == expected From 3410e0e2417c984b22b3925f10bd6f8d2521a9de Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 27 Mar 2023 22:01:14 +0200 Subject: [PATCH 4/6] test: ensure original table is not modified --- .../containers/_table/test_sort_rows.py | 59 ++++++++++++------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py index 080f27007..48d4a35f4 100644 --- a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py +++ b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py @@ -4,23 +4,42 @@ from safeds.data.tabular.containers import Column, Row, Table -@pytest.mark.parametrize( - ("table", "sorter", "expected"), - [ - # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. - # ( - # Table.from_columns([Column([], "col1")]), - # lambda row1, row2: row1["col1"] - row2["col1"], - # Table.from_columns([Column([], "col1")]) - # ), - ( - Table.from_columns([Column([3, 2, 1], "col1")]), - lambda row1, row2: row1["col1"] - row2["col1"], - Table.from_columns([Column([1, 2, 3], "col1")]), - ), - ], -) -def test_sort_rows( - table: Table, sorter: Callable[[Row, Row], int], expected: Table -) -> None: - assert table.sort_rows(sorter) == expected +class TestSortRows: + @pytest.mark.parametrize( + ("table", "sorter", "expected"), + [ + # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. + # ( + # Table.from_columns([Column([], "col1")]), + # lambda row1, row2: row1["col1"] - row2["col1"], + # Table.from_columns([Column([], "col1")]), + # ), + ( + Table.from_columns([Column([3, 2, 1], "col1")]), + lambda row1, row2: row1["col1"] - row2["col1"], + Table.from_columns([Column([1, 2, 3], "col1")]), + ), + ], + ) + def test_should_return_sorted_table(self, table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: + assert table.sort_rows(sorter) == expected + + @pytest.mark.parametrize( + ("table", "sorter", "expected"), + [ + # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. + # ( + # Table.from_columns([Column([], "col1")]), + # lambda row1, row2: row1["col1"] - row2["col1"], + # Table.from_columns([Column([], "col1")]) + # ), + ( + Table.from_columns([Column([3, 2, 1], "col1")]), + lambda row1, row2: row1["col1"] - row2["col1"], + Table.from_columns([Column([3, 2, 1], "col1")]), + ), + ] + ) + def test_should_not_modify_original_table(self, table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: + table.sort_rows(sorter) + assert table == expected From b9dcd6ec545e0f91d5de1d707c41c7757356b051 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Mon, 27 Mar 2023 22:01:36 +0200 Subject: [PATCH 5/6] test: rename parameter --- .../data/tabular/containers/_table/test_sort_rows.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py index 48d4a35f4..889223b3b 100644 --- a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py +++ b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py @@ -6,7 +6,7 @@ class TestSortRows: @pytest.mark.parametrize( - ("table", "sorter", "expected"), + ("table", "comparator", "expected"), [ # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. # ( @@ -21,11 +21,11 @@ class TestSortRows: ), ], ) - def test_should_return_sorted_table(self, table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: - assert table.sort_rows(sorter) == expected + def test_should_return_sorted_table(self, table: Table, comparator: Callable[[Row, Row], int], expected: Table) -> None: + assert table.sort_rows(comparator) == expected @pytest.mark.parametrize( - ("table", "sorter", "expected"), + ("table", "comparator", "expected"), [ # Activate when https://github.com/Safe-DS/Stdlib/issues/75 is fixed. # ( @@ -40,6 +40,6 @@ def test_should_return_sorted_table(self, table: Table, sorter: Callable[[Row, R ), ] ) - def test_should_not_modify_original_table(self, table: Table, sorter: Callable[[Row, Row], int], expected: Table) -> None: - table.sort_rows(sorter) + def test_should_not_modify_original_table(self, table: Table, comparator: Callable[[Row, Row], int], expected: Table) -> None: + table.sort_rows(comparator) assert table == expected From 954e56e5c0814bbefebc168c1ec41f8566ee206b Mon Sep 17 00:00:00 2001 From: lars-reimann Date: Mon, 27 Mar 2023 20:03:41 +0000 Subject: [PATCH 6/6] style: apply automated linter fixes --- .../data/tabular/containers/_table/test_sort_rows.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py index 889223b3b..c2eb10730 100644 --- a/tests/safeds/data/tabular/containers/_table/test_sort_rows.py +++ b/tests/safeds/data/tabular/containers/_table/test_sort_rows.py @@ -21,7 +21,9 @@ class TestSortRows: ), ], ) - def test_should_return_sorted_table(self, table: Table, comparator: Callable[[Row, Row], int], expected: Table) -> None: + def test_should_return_sorted_table( + self, table: Table, comparator: Callable[[Row, Row], int], expected: Table + ) -> None: assert table.sort_rows(comparator) == expected @pytest.mark.parametrize( @@ -38,8 +40,10 @@ def test_should_return_sorted_table(self, table: Table, comparator: Callable[[Ro lambda row1, row2: row1["col1"] - row2["col1"], Table.from_columns([Column([3, 2, 1], "col1")]), ), - ] + ], ) - def test_should_not_modify_original_table(self, table: Table, comparator: Callable[[Row, Row], int], expected: Table) -> None: + def test_should_not_modify_original_table( + self, table: Table, comparator: Callable[[Row, Row], int], expected: Table + ) -> None: table.sort_rows(comparator) assert table == expected