Skip to content

Commit 5c62abf

Browse files
Refactor API
1 parent bc20ec9 commit 5c62abf

File tree

7 files changed

+92
-115
lines changed

7 files changed

+92
-115
lines changed

README.md

Lines changed: 25 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -56,45 +56,35 @@ Sort rows_dict by _grade_, descending, then _attend_, ascending and put None fir
5656
```
5757
from multisort import multisort
5858
rows_sorted = multisort(rows_dict, [
59-
('grade', {'reverse': False})
60-
,'attend'
59+
('grade', reverse=False),
60+
('attend')
6161
])
6262
6363
```
6464
Sort rows_dict by _grade_, descending, then _attend_ and call upper() for _grade_:
6565
```
6666
from multisort import multisort
6767
rows_sorted = multisort(rows_dict, [
68-
('grade', {'reverse': False, 'clean': lambda s: None if s is None else s.upper()})
69-
,'attend'
68+
mscol('grade', 'reverse'=False, clean=lambda s: None if s is None else s.upper()),
69+
('attend')
7070
])
7171
7272
```
7373
`multisort` parameters:
7474
option|dtype|description
7575
---|---|---
76-
`key`|int or str|Key to access data. int for tuple or list
77-
`spec`|str, int, list|Sort specification. Can be as simple as a column key / index
76+
`rows`|int or str|Key to access data. int for tuple or list
77+
`spec`|str, int, list|Sort specification. Can be as simple as a column key / index or `mscol`
7878
`reverse`|bool|Reverse order of final sort (defalt = False)
7979

80-
`multisort` `spec` options:
80+
`multisort` spec options:
8181
option|dtype|description
8282
---|---|---
83-
reverse|bool|Reverse sort of column
84-
clean|func|Function / lambda to clean the value. These calls can cause a significant slowdown.
85-
required|bool|Default True. If false, will substitute None or default if key not found (not applicable for list or tuple rows)
86-
default|any|Value to substitute if required==False and key does not exist or None is found. Can be used to achive similar functionality to pandas `na_position`
87-
88-
89-
90-
### `sorted` with `reversor`
91-
Sort rows_dict by _grade_, descending, then _attend_ and call upper() for _grade_:
92-
```
93-
rows_sorted = sorted(rows_dict, key=lambda o: (
94-
reversor(None if o['grade'] is None else o['grade'].upper())
95-
,o['attend'])
96-
))
97-
```
83+
`key`|int or str|Key to access data. int for tuple or list
84+
`reverse`|bool|Reverse sort of column
85+
`clean`|func|Function / lambda to clean the value. These calls can cause a significant slowdown.
86+
`required`|bool|Default True. If false, will substitute None or default if key not found (not applicable for list or tuple rows)
87+
`default`|any|Value to substitute if required==False and key does not exist or None is found. Can be used to achive similar functionality to pandas `na_position`
9888

9989

10090
### `sorted` with `cmp_func`
@@ -112,7 +102,17 @@ def cmp_student(a,b):
112102
rows_sorted = sorted(rows_dict, key=cmp_func(cmp_student), reverse=True)
113103
```
114104

115-
105+
### For reference: `superfast` methodology with list of dicts:
106+
```
107+
def key_grade(student):
108+
grade = student['grade']
109+
return grade is None, grade
110+
def key_attend(student):
111+
attend = student['attend']
112+
return attend is None, attend
113+
students_sorted = sorted(students, key=key_attend)
114+
students_sorted.sort(key=key_grade, reverse=True)
115+
```
116116

117117
### Object Examples
118118
For data:
@@ -140,16 +140,6 @@ rows_obj = [
140140
(Same syntax as with 'dict' example)
141141

142142

143-
### `sorted` with `reversor`
144-
Sort rows_obj by _grade_, descending, then _attend_ and call upper() for _grade_:
145-
```
146-
rows_sorted = sorted(rows_obj, key=lambda o: (
147-
reversor(None if o.grade is None else o.grade.upper())
148-
,o.attend)
149-
))
150-
```
151-
152-
153143
### `sorted` with `cmp_func`
154144
Sort rows_obj by _grade_, descending, then _attend_ and call upper() for _grade_:
155145
```
@@ -184,23 +174,12 @@ Sort rows_tuple by _grade_, descending, then _attend_, ascending and put None fi
184174
```
185175
from multisort import multisort
186176
rows_sorted = multisort(rows_tuple, [
187-
(COL_GRADE, {'reverse': False, 'none_first': True})
188-
,COL_ATTEND
177+
mscol(COL_GRADE, reverse=False, default='0'),
178+
(COL_ATTEND)
189179
])
190180
191181
```
192182

193-
194-
### `sorted` with `reversor`
195-
Sort rows_tuple by _grade_, descending, then _attend_ and call upper() for _grade_:
196-
```
197-
rows_sorted = sorted(rows_tuple, key=lambda o: (
198-
reversor(None if o[COL_GRADE] is None else o[COL_GRADE].upper())
199-
,o[COL_ATTEND])
200-
))
201-
```
202-
203-
204183
### `sorted` with `cmp_func`
205184
Sort rows_tuple by _grade_, descending, then _attend_ and call upper() for _grade_:
206185
```

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "multisort"
3-
version = "0.1.2"
3+
version = "0.1.3"
44
description = "NoneType Safe Multi Column Sorting For Python"
55
license = "MIT"
66
authors = ["Timothy C. Quinn"]

src/multisort/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .multisort import multisort, cmp_func, reversor
1+
from .multisort import multisort, mscol, cmp_func, reversor

src/multisort/multisort.py

Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,32 @@
1414
# .: multisort :.
1515
# spec is a list one of the following
1616
# <key>
17-
# (<key>,)
18-
# (<key>, <opts>)
19-
# where:
20-
# <key> Property, Key or Index for 'column' in row
21-
# <opts> dict. Options:
22-
# reverse: opt - reversed sort (defaults to False)
23-
# clean: opt - callback to clean / alter data in 'field'
17+
# spec
18+
# spec options:
19+
# key Property, Key or Index for 'column' in row
20+
# reverse: opt - reversed sort (defaults to False)
21+
# clean: opt - callback to clean / alter data in 'field'
22+
# default: Value to default if None is found or required = False
23+
# required: Will not fail if key not found
24+
# Use mscol helper to ease passing of variables or just pass lists of args:
25+
# spec=mscol('colname1', reverse=True), mscol('colname2', reverse=True)]
26+
# -or-
27+
# spec=[('colname1', True),('colname2',True)]
28+
29+
def mscol(key, reverse=False, clean=None, default=None, required=True):
30+
return (key, reverse, clean, default, required)
31+
2432
def multisort(rows, spec, reverse:bool=False):
25-
key=clean=rows_sorted=default=None
26-
col_reverse=False
27-
required=True
28-
for s_c in reversed([spec] if isinstance(spec, (int, str)) else spec):
29-
if isinstance(s_c, (int, str)):
30-
key = s_c
33+
rows_sorted=None
34+
if isinstance(spec, (int, str)): spec = [mscol(spec)]
35+
for spec_c in reversed(spec):
36+
spec_c_t = type(spec_c)
37+
if spec_c_t in(int, str):
38+
(key, col_reverse, clean, default, required) = (spec_c, False, None, None, True)
3139
else:
32-
if len(s_c) == 1:
33-
key = s_c[0]
34-
elif len(s_c) == 2:
35-
key = s_c[0]
36-
s_opts = s_c[1]
37-
assert not s_opts is None and isinstance(s_opts, dict), f"Invalid Spec. Second value must be a dict. Got {getClassName(s_opts)}"
38-
col_reverse = s_opts.get('reverse', False)
39-
clean = s_opts.get('clean', None)
40-
default = s_opts.get('default', None)
41-
required = s_opts.get('required', True)
42-
40+
assert spec_c_t in (list, tuple), f"Invalid spec. Got: {spec_c_t.__name__}. See docs"
41+
if len(spec_c) < 5: spec_c = mscol(*spec_c)
42+
(key, col_reverse, clean, default, required) = spec_c
4343
def _sort_column(row): # Throws MSIndexError, MSKeyError
4444
ex1=None
4545
try:
@@ -94,7 +94,6 @@ def _sort_column(row): # Throws MSIndexError, MSKeyError
9494

9595

9696
return reversed(rows_sorted) if reverse else rows_sorted
97-
9897

9998
class MultiSortBaseExc(Exception):
10099
def __init__(self, msg, row, cause):

tests/hand_test.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import sys
2-
from multisort import multisort, cmp_func, reversor
2+
from multisort import multisort, mscol, cmp_func, reversor
33
import test_util as util
44
pc = util.pc
55

66
def main():
7-
# test_multisort_dict_single()
8-
# test_multisort_obj_single()
9-
# test_multisort_tuple_single()
7+
8+
test_multisort_dict_single()
9+
test_multisort_obj_single()
10+
test_multisort_tuple_single()
1011

1112
test_multisort_dict_multi()
12-
# test_multisort_obj_multi()
13-
# test_multisort_tuple_multi()
13+
test_multisort_obj_multi()
14+
test_multisort_tuple_multi()
1415

1516

1617
students_dict = [
@@ -69,19 +70,25 @@ def test_multisort_tuple_single():
6970

7071
def test_multisort_dict_multi():
7172
_sorted = multisort(students_dict, [
72-
('grade', {'reverse': True, 'clean': lambda s: None if s is None else s.upper(), 'default': '0', 'required': True}),
73+
mscol('grade', reverse=True, clean=lambda s: None if s is None else s.upper(), default='0', required=True),
7374
# ('attend', {'reverse': False}),
7475
], reverse=False)
7576
_print_stud(_sorted)
7677

7778

7879
def test_multisort_obj_multi():
79-
_sorted = multisort(students_obj, [('grade', {'reverse': True}), 'attend'], reverse=False)
80+
_sorted = multisort(students_obj, [
81+
mscol('grade', reverse=True),
82+
mscol('attend')
83+
], reverse=False)
8084
_print_stud(_sorted)
8185

8286

8387
def test_multisort_tuple_multi():
84-
_sorted = multisort(student_tuple, [(COL_GRADE, {'reverse': True}), COL_ATTEND], reverse=False)
88+
_sorted = multisort(student_tuple, [
89+
mscol(COL_GRADE, reverse=True),
90+
mscol(COL_ATTEND)
91+
], reverse=False)
8592
_print_stud(_sorted)
8693

8794

tests/performance_tests.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pandas
33
from random import randint
44
from operator import itemgetter
5-
from multisort import multisort, cmp_func, reversor
5+
from multisort import multisort, mscol, cmp_func, reversor
66
import test_util as util
77
pc = util.pc
88

@@ -14,7 +14,7 @@
1414
,{'idx': 4, 'name': 'jim' , 'grade': 'F', 'attend': 55}
1515
,{'idx': 5, 'name': 'joe' , 'grade': None, 'attend': 55}
1616
]
17-
ITERATIONS = 5
17+
ITERATIONS = 10
1818
EXTRA_ROW = 1000
1919

2020
def main():
@@ -70,28 +70,25 @@ def cmp_student(a,b):
7070

7171
return ('cmp_func', sw.elapsed(prec=7))
7272

73-
74-
7573
async def run_multisort(rows):
7674
sw = util.StopWatch()
7775
for i in range(0,ITERATIONS):
78-
rows_sorted = multisort(rows, spec=(
79-
('grade', {'reverse': True, 'clean': lambda v: None if v is None else v.lower()})
80-
,('attend', {'reverse': True})
81-
), reverse=True)
76+
def clean_grade(v): return v.lower()
77+
rows_sorted = multisort(rows, [
78+
mscol('grade', reverse=True, clean=clean_grade),
79+
mscol('attend', reverse=True),
80+
], reverse=True)
8281
return ('multisort w/ clean', sw.elapsed(prec=7))
8382

84-
8583
async def run_multisort_noclean(rows):
8684
sw = util.StopWatch()
8785
for i in range(0,ITERATIONS):
88-
rows_sorted = multisort(rows, spec=(
89-
('grade', {'reverse': True})
90-
,('attend', {'reverse': True})
91-
), reverse=True)
86+
rows_sorted = multisort(rows, [
87+
mscol('grade', reverse=True),
88+
mscol('attend', reverse=True),
89+
], reverse=True)
9290
return ('multisort noclean', sw.elapsed(prec=7))
9391

94-
9592
async def run_reversor(rows):
9693
sw = util.StopWatch()
9794
for i in range(0,ITERATIONS):
@@ -119,7 +116,8 @@ def key_grade(student):
119116
grade = student['grade']
120117
return grade is None, grade
121118
def key_attend(student):
122-
return student['attend']
119+
attend = student['attend']
120+
return attend is None, attend
123121
students_sorted = sorted(students, key=key_attend)
124122
students_sorted.sort(key=key_grade, reverse=True)
125123

tests/test_multisort.py

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
import unittest
3-
from multisort import multisort
3+
from multisort import multisort, mscol
44
import test_util as util
55
pc = util.pc
66

@@ -23,14 +23,14 @@ def clean_grade(v):
2323

2424

2525
MSORTED_TESTS=[
26-
( (2,0,5,1,3,4), [(COL_GRADE, {'reverse': False, 'clean': clean_grade}) , (COL_ATTEND, {'reverse': False})]),
27-
( (0,2,1,5,3,4), [(COL_GRADE, {'reverse': False, 'clean': clean_grade}) , (COL_ATTEND, {'reverse': True})]),
28-
( (0,2,1,5,3,4), [(COL_GRADE, {'reverse': False, 'clean': clean_grade}) , (COL_ATTEND, {'reverse': True})]),
29-
( (4,3,1,5,0,2), [(COL_GRADE, {'reverse': True , 'clean': clean_grade}) , (COL_ATTEND, {'reverse': True})]),
30-
( (4,3,5,1,2,0), [(COL_GRADE, {'reverse': True , 'clean': clean_grade}) , (COL_ATTEND, {'reverse': False})]),
26+
( (2,0,5,1,3,4), [mscol(COL_GRADE, reverse=False, clean=clean_grade) , mscol(COL_ATTEND, reverse=False)]),
27+
( (0,2,1,5,3,4), [mscol(COL_GRADE, reverse=False, clean=clean_grade) , mscol(COL_ATTEND, reverse=True)]),
28+
( (0,2,1,5,3,4), [mscol(COL_GRADE, reverse=False, clean=clean_grade) , mscol(COL_ATTEND, reverse=True)]),
29+
( (4,3,1,5,0,2), [mscol(COL_GRADE, reverse=True , clean=clean_grade) , mscol(COL_ATTEND, reverse=True)]),
30+
( (4,3,5,1,2,0), [mscol(COL_GRADE, reverse=True , clean=clean_grade) , mscol(COL_ATTEND, reverse=False)]),
3131
( (2,1,5,3,0,4), COL_GRADE),
3232
( (2,1,5,3,0,4), [COL_GRADE]),
33-
( (2,5,1,3,0,4), [COL_GRADE, COL_NAME]),
33+
( (4,0,3,5,1,2), [[COL_GRADE, True], COL_NAME]),
3434
]
3535

3636

@@ -158,16 +158,10 @@ def test_tuple_of_objects(self):
158158

159159
def norm_spec_item(spec_c):
160160
if isinstance(spec_c, (int, str)):
161-
return (spec_c, None, None)
161+
return (spec_c, False, None, None, True)
162162
else:
163-
assert isinstance(spec_c, tuple) and len(spec_c) in (1,2),\
164-
f"Invalid spec. Must have 1 or 2 params per record. Got: {spec_c}"
165-
if len(spec_c) == 1:
166-
return (spec_c[0], None, None)
167-
elif len(spec_c) == 2:
168-
s_opts = spec_c[1]
169-
assert not s_opts is None and isinstance(s_opts, dict), f"Invalid Spec. Second value must be a dict. Got {util.getClassName(s_opts)}"
170-
return (spec_c[0], s_opts.get('reverse', False), s_opts.get('clean', None))
163+
if len(spec_c) < 5: return mscol(*spec_c)
164+
return spec_c
171165

172166

173167
def dump_sort(stest_no, spec, rows, rows_as, row_as, expected, reverse):
@@ -179,13 +173,13 @@ def dump_sort(stest_no, spec, rows, rows_as, row_as, expected, reverse):
179173
sb.a(spec).a(" (a)")
180174
else:
181175
for i, spec_c in enumerate(spec):
182-
(key, desc, clean) = norm_spec_item(spec_c)
176+
(key, col_reverse, clean, default, required) = norm_spec_item(spec_c)
183177
if i > 0: sb.a(", ")
184178
if indexable:
185179
sb.a(STUDENT_COLS[key])
186180
else:
187181
sb.a(key)
188-
sb.a(' (d)' if desc else ' (a)')
182+
sb.a(' (d)' if col_reverse else ' (a)')
189183

190184
if reverse: sb.a(' (reversed)')
191185

0 commit comments

Comments
 (0)