{"id":18087756,"url":"https://github.com/javascriptdude/multisort","last_synced_at":"2025-04-13T01:51:12.954Z","repository":{"id":62591518,"uuid":"488015622","full_name":"JavaScriptDude/multisort","owner":"JavaScriptDude","description":"NoneType Safe Multi Column Sorting For Python","archived":false,"fork":false,"pushed_at":"2023-10-20T01:59:22.000Z","size":43,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T06:04:42.476Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JavaScriptDude.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-05-02T23:02:43.000Z","updated_at":"2023-03-21T01:17:09.000Z","dependencies_parsed_at":"2025-04-13T01:51:02.247Z","dependency_job_id":null,"html_url":"https://github.com/JavaScriptDude/multisort","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavaScriptDude%2Fmultisort","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavaScriptDude%2Fmultisort/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavaScriptDude%2Fmultisort/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JavaScriptDude%2Fmultisort/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JavaScriptDude","download_url":"https://codeload.github.com/JavaScriptDude/multisort/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248654030,"owners_count":21140235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-31T17:05:19.648Z","updated_at":"2025-04-13T01:51:12.933Z","avatar_url":"https://github.com/JavaScriptDude.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"## `multisort` - NoneType Safe Multi Column Sorting For Python\n\nSimplified multi-column sorting of lists of tuples, dicts, lists or objects that are NoneType safe.\n\n### Installation\n\n```\npython3 -m pip install multisort\n```\n\n### Dependencies\nNone\n\n### Performance\nAverage over 10 iterations with 1000 rows.\nTest | Secs\n---|---\nsuperfast|0.0005\nmultisort|0.0035\npandas|0.0079\ncmp_func|0.0138\nreversor|0.037\n\nHands down the fastest is the `superfast` methdology shown below. You do not need this library to accomplish this as its just core python.\n\n`multisort` from this library gives reasonable performance for large data sets; eg. its better than pandas up to about 5,500 records. It is also much simpler to read and write, and it has error handling that does its best to give useful error messages.\n\n### Note on `NoneType` and sorting\nIf your data may contain None, it would be wise to ensure your sort algorithm is tuned to handle them. This is because sorted uses `\u003c` comparisons; which is not supported by `NoneType`. For example, the following error will result: `TypeError: '\u003e' not supported between instances of 'NoneType' and 'str'`. All examples given on this page are tuned to handle `None` values.\n\n### Methodologies\nMethod|Descr|Notes\n---|---|---\nmultisort|Simple one-liner designed after `multisort` [example from python docs](https://docs.python.org/3/howto/sorting.html#sort-stability-and-complex-sorts)|Second fastest of the bunch but most configurable and easy to read.\ncmp_func|Multi column sorting in the model `java.util.Comparator`|Reasonable speed|Enable multi column sorting with column specific reverse sorting|Medium speed. [Source](https://stackoverflow.com/a/56842689/286807)\nsuperfast|NoneType safe sample implementation of multi column sorting as mentioned in [example from python docs](https://docs.python.org/3/howto/sorting.html#sort-stability-and-complex-sorts)|Fastest by orders of magnitude but a bit more complex to write.\n\n\n\u003cbr\u003e\n\n### Dictionary Examples\nFor data:\n```\nrows_before = [\n     {'idx': 0, 'name': 'joh', 'grade': 'C', 'attend': 100}\n    ,{'idx': 1, 'name': 'jan', 'grade': 'a', 'attend': 80}\n    ,{'idx': 2, 'name': 'dav', 'grade': 'B', 'attend': 85}\n    ,{'idx': 3, 'name': 'bob' , 'grade': 'C', 'attend': 85}\n    ,{'idx': 4, 'name': 'jim' , 'grade': 'F', 'attend': 55}\n    ,{'idx': 5, 'name': 'joe' , 'grade': None, 'attend': 55}\n]\n```\n\n### `multisort`\nSort rows_before by _grade_, descending, then _attend_, ascending and put None first in results:\n```\nfrom multisort import multisort, mscol\nrows_sorted = multisort(rows_before, [\n        mscol('grade', reverse=False),\n        'attend'\n])\n```\n\n-or- without `mscol`\n\n```\nfrom multisort import multisort\nrows_sorted = multisort(rows_before, [\n        ('grade', False),\n        'attend'\n])\n```\n\nSort rows_before by _grade_, descending, then _attend_ and call upper() for _grade_:\n```\nfrom multisort import multisort, mscol\nrows_sorted = multisort(rows_before, [\n        mscol('grade', reverse=False, clean=lambda s: None if s is None else s.upper()),\n        'attend'\n])\n```\n\n-or- without `mscol`\n```\nfrom multisort import multisort\nrows_sorted = multisort(rows_before, [\n        ('grade', False, lambda s: None if s is None else s.upper()),\n        'attend'\n])\n```\n\n\n`multisort` parameters:\noption|dtype|description\n---|---|---\n`rows`|int or str|Key to access data. int for tuple or list\n`spec`|str, int, list|Sort specification. Can be as simple as a column key / index or `mscol`\n`reverse`|bool|Reverse order of final sort (defalt = False)\n\n\n`spec` entry options:\noption|position|dtype|description\n---|---|---|---\n`key`|0|int or str|Key to access data. int for tuple or list\n`reverse`|1|bool|Reverse sort of column\n`clean`|2|func|Function / lambda to clean the value. These calls can cause a significant slowdown.\n`default`|3|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`\n`required`|4|bool|Default True. If False, will substitute None or default if key not found (not applicable for list or tuple rows)\n\n\n\\* `spec` entries can be passed as:\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;type|description\n---|---\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`String`|Column name\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`tuple`|Tuple of 1 or more `spec` options in their order as listed (see `position`)\n\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`mscol()`|Importable helper to aid in readability. Suggested for three or more of the options.\n\n\n\u003cbr\u003e\u003cbr\u003e\n\n\n### `sorted` with `cmp_func`\nSort rows_before by _grade_, descending, then _attend_ and call upper() for _grade_:\n```\ndef cmp_student(a,b):\n    k='grade'; va=a[k]; vb=b[k]\n    if va != vb: \n        if va is None: return -1\n        if vb is None: return 1\n        return -1 if va \u003e vb else 1\n    k='attend'; va=a[k]; vb=b[k]; \n    if va != vb: return -1 if va \u003c vb else 1\n    return 0\nrows_sorted = sorted(rows_before, key=cmp_func(cmp_student), reverse=True)\n```\n\n\u003cbr\u003e\n\n### For reference: `superfast` methodology with list of dicts:\n```\ndef key_grade(student):\n    grade = student['grade']\n    return grade is None, grade\ndef key_attend(student):\n    attend = student['attend']\n    return attend is None, attend\nstudents_sorted = sorted(students, key=key_attend)\nstudents_sorted.sort(key=key_grade, reverse=True)\n```\n\n\u003cbr\u003e\n\n### Object Examples\nFor data:\n```\nclass Student():\n    def __init__(self, idx, name, grade, attend):\n        self.idx = idx\n        self.name = name\n        self.grade = grade\n        self.attend = attend\n    def __str__(self): return f\"name: {self.name}, grade: {self.grade}, attend: {self.attend}\"\n    def __repr__(self): return self.__str__()\n\nrows_before = [\n     Student(0, 'joh', 'C', 100)\n    ,Student(1, 'jan', 'a', 80)\n    ,Student(2, 'dav', 'B', 85)\n    ,Student(3, 'bob', 'C', 85)\n    ,Student(4, 'jim', 'F', 55)\n    ,Student(5, 'joe', None, 55)\n]\n```\n\u003cbr\u003e\n\n### `multisort`\n(Same syntax as with Dictionary example above)\n\n\u003cbr\u003e\n\n### `sorted` with `cmp_func`\nSort rows_before by _grade_, descending, then _attend_ and call upper() for _grade_:\n```\ndef cmp_student(a,b):\n    if a.grade != b.grade: \n        if a.grade is None: return -1\n        if b.grade is None: return 1\n        return -1 if a.grade \u003e b.grade else 1\n    if a.attend != b.attend: \n        return -1 if a.attend \u003c b.attend else 1\n    return 0\nrows_sorted = sorted(rows_before, key=cmp_func(cmp_student), reverse=True)\n```\n\n\n### List / Tuple Examples\nFor data:\n```\nrows_before = [\n     (0, 'joh', 'a'  , 100)\n    ,(1, 'joe', 'B'  , 80)\n    ,(2, 'dav', 'A'  , 85)\n    ,(3, 'bob', 'C'  , 85)\n    ,(4, 'jim', None , 55)\n    ,(5, 'jan', 'B'  , 70)\n]\n(COL_IDX, COL_NAME, COL_GRADE, COL_ATTEND) = range(0,4)\n```\n\n\u003cbr\u003e\n\n### `multisort`\n(Same syntax as with Dictionary example above)\n\n\u003cbr\u003e\n\n### `sorted` with `cmp_func`\nSort rows_before by _grade_, descending, then _attend_ and call upper() for _grade_:\n```\ndef cmp_student(a,b):\n    k=COL_GRADE; va=a[k]; vb=b[k]\n    if va != vb: \n        if va is None: return -1\n        if vb is None: return 1\n        return -1 if va \u003e vb else 1\n    k=COL_ATTEND; va=a[k]; vb=b[k]; \n    if va != vb: \n        return -1 if va \u003c vb else 1\n    return 0\nrows_sorted = sorted(rows_before, key=cmp_func(cmp_student), reverse=True)\n```\n\n\u003cbr\u003e\u003cbr\u003e\n\n### Basic sorting\n`multisort` can be used as a basic non-destructive sorter of lists where a traditional sort does so destructively:\n```\n_orig = [1, 4, 3, 6, 5]\n_orig.sort(reverse=True)\n```\nThis will sort `_orig` in-place\n\nIn builtin python, to do a non-destructive sort it takes two lines:\n```\n_orig = [1, 4, 3, 6, 5]\n_clone = [:]\n_clone.sort(reverse=True)\n```\n\nWith Multisort its just one line:\n```\n_orig = [1, 4, 3, 6, 5]\n_sorted = multisort(_orig, reverse=True)\n```\nWhere `_orig` is left unchanged\n\n\u003cbr\u003e\n\n### `multisort` library Test / Sample files (/tests)\nName|Descr|Other\n---|---|---\ntests/test_multisort.py|multisort unit tests|- \ntests/performance_tests.py|Tunable performance tests using asyncio | requires pandas\ntests/hand_test.py|Hand testing|-\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjavascriptdude%2Fmultisort","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjavascriptdude%2Fmultisort","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjavascriptdude%2Fmultisort/lists"}