{ "info": { "author": "Louis Jacobowitz", "author_email": "ldjacobowitzer@gmail.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# `python-ranges`\n\nThis module provides data structures for representing\n\n- Continuous Ranges\n- Non-continuous Ranges (i.e. sets of continous Ranges)\n- `dict`-like structures that use ranges as keys\n\n## Introduction\n\nOne curious missing feature in Python (and several other programming languages) is \nthe absence of a Range data structure - a continuous set of values from some\nstarting point to some ending point. Instead, we have to make do with verbose\n`if`/`else` comparisons:\n\n```python\nif value >= start and value < end:\n # do something\n```\n\nAnd to have a graded sequence of ranges with different behavior for each, we have\nto chain these `if`/`elif`/`else` blocks together:\n\n```python\n# 2019 U.S. income tax brackets, filing Single\nincome = int(input(\"What is your income? $\"))\nif income < 9701:\n tax = 0.1 * income\nelif 9701 <= income < 39476:\n tax = 970 + 0.12 * (income - 9700)\nelif 39476 <= income < 84201:\n tax = 4543 + 0.22 * (income - 39475)\nelif 84201 <= income < 160726:\n tax = 14382.5 + 0.24 * (income - 84200)\nelif 160726 <= income < 204101:\n tax = 32748.5 + 0.32 * (income - 160725)\nelif 204101 <= income < 510301:\n tax = 46628.5 + 0.35 * (income - 204100)\nelse:\n tax = 153798.5 + 0.37 * (income - 510300)\nprint(f\"Your tax on that income is ${tax:.2f}\")\n```\n\nThis module, `ranges`, fixes this problem by introducing a data structure `Range` to\nrepresent a continuous range, and a `dict`-like data structure `RangeDict` to map\nranges to values. This makes simple range checks more intuitive:\n\n```python\nif value in Range(start, end):\n # do something\n```\n\nand does away with the tedious `if`/`elif`/`else` blocks:\n\n```python\n# 2019 U.S. income tax brackets, filing Single\ntax_info = RangeDict({\n Range(0, 9701): (0, 0.10, 0),\n Range(9701, 39476): (970, 0.12, 9700),\n Range(39476, 84201): (4543, 0.22, 39475),\n Range(84201, 160726): (14382.2, 0.24, 84200),\n Range(160726, 204101): (32748.5, 0.32, 160725),\n Range(204101, 510301): (46628.5, 0.35, 204100),\n Range(start=510301): (153798.5, 0.37, 510300),\n})\nincome = int(input(\"What is your income? $\"))\nbase, marginal_rate, bracket_floor = tax_info[income]\ntax = base + marginal_rate * (income - bracket_floor)\nprint(f\"Your tax on that income is ${tax:.2f}\")\n```\n\nThe `Range` data structure also accepts strings, dates, and any other data type, so\nlong as the start value is less than the end value (and so long as checking that\ndoesn't raise an error). \n\nSee [the in-depth documentation](https://python-ranges.readthedocs.io/en/latest/) for more details.\n\n## Installation\n\nInstall `python-ranges` via [pip](https://pip.pypa.io/en/stable/):\n\n```bash\n$ pip install python-ranges\n```\n\nDue to use of format strings in the code, this module will only work with\n**python 3.6 or higher**.\n\n## Usage\n\nSimply import `ranges` like any other python package, or import the `Range`, \n`RangeSet`, and `RangeDict` classes from it:\n\n```python\nimport ranges\n\nmy_range = ranges.Range(\"anaconda\", \"viper\")\n```\n\n```python\nfrom ranges import Range\n\nmy_range = Range(\"anaconda\", \"viper\")\n```\n\nThen, you can use these data types however you like.\n\n### `Range`\n\nTo make a Range, simply call `Range()` with start and end values. Both of these\nwork:\n\n```python\nrng1 = Range(1.5, 7)\nrng2 = Range(start=4, end=8.5)\n```\n\nYou can also use the `include_start` and `include_end` keyword arguments to specify\nwhether or not each end of the range should be inclusive. By default, the start\nis included and the end is excluded, just like python's built-in `range()` function.\n\nIf you use keyword arguments and don't specify either the `start` or the `end` of\nthe range, then the `Range`'s bounds will be negative or positive infinity,\nrespectively. `Range` uses a special notion of infinity that's compatible with\nnon-numeric data types - so `Range(start=\"journey\")` will include *any string* \nthat's lexicographically greater than \"journey\", and \n`Range(end=datetime.date(1989, 10, 4))` will include any date before October 4,\n1989, despite neither `str` nor `datetime` having any built-in notion of infinity.\n\nIf you're making a range of numbers, then you can also use a single string as an\nargument, with circle-brackets `()` meaning \"exclusive\" and square-brackets `[]`\nmeaning \"inclusive\":\n\n```python\nrng3 = Range(\"[1.5, 7)\")\nrng4 = Range(\"[1.5 .. 7)\")\n```\n\n`Range`'s interface is similar to the built-in `set`, and the following methods\nall act exactly how you'd expect:\n\n```python\nprint(rng1.union(rng2)) # [1.5, 8.5)\nprint(rng1.intersection(rng2)) # [4, 7)\nprint(rng1.difference(rng2)) # [1.5, 4)\nprint(rng1.symmetric_difference(rng2)) # {[1.5, 4), [7, 8.5)}\n```\n\nOf course, the operators `|`, `&`, `-`, and `^` can be used in place of those\nmethods, just like for python's built-in `set`s.\n\nSee [the documentation](https://python-ranges.readthedocs.io/en/latest/#ranges.Range) for more details.\n\n### `RangeSet`\n\nA `RangeSet` is just an ordered set of `Range`s, all of the same kind. Like `Range`,\nits interface is similar to the built-in `set`. Unlike `Range`, which isn't\nmutable, `RangeSet` can be modified just like `set` can, with the methods\n`.add()`, `.extend()`, `.discard()`, etc.\n\nTo construct a `RangeSet`, just call `RangeSet()` with a bunch of ranges (or\niterables containing ranges) as positional arguments:\n\n```python\nrngset1 = RangeSet(\"[1, 4.5]\", \"(6.5, 10)\")\nrngset2 = RangeSet([Range(2, 3), Range(7, 8)])\n```\n\n`Range` and `RangeSet` objects are mutually compatible for things like `union()`,\n`intersection()`, `difference()`, and `symmetric_difference()`. If you give these\nmethods a range-like object, it'll get automatically converted:\n\n```python\nprint(rngset1.union(Range(3, 8))) # {[1, 10)}\nprint(rngset1.intersection(\"[3, 8)\")) # {[3, 4.5], (6.5, 8)}\nprint(rngset1.symmetric_difference(\"[3, 8)\")) # {[1, 3), (4.5, 6], [8, 10)}\n```\n\nOf course, `RangeSet`s can operate with each other, too:\n\n```python\nprint(rngset1.difference(rngset2)) # {[1, 2), [3, 4.5], (6.5, 7), [8, 10)}\n```\n\nThe operators `|`, `&`, `^`, and `-` all work with `RangeSet` as they do with `set`,\nas do their associated assignment operators `|=`, `&=`, `^=`, and `-=`. \n\nFinally, you can iterate through a `RangeSet` to get all of its component ranges:\n\n```python\nfor rng in rngset1:\n print(rng)\n# [1, 4.5]\n# (6.5, 10)\n```\n\nSee [the documentation](https://python-ranges.readthedocs.io/en/latest/#ranges.RangeSet) for more details.\n\n### ` RangeDict`\n\nThis data structure is analagous to python's built-in `dict` data structure, except\nit uses `Range`s/`RangeSet`s as keys. As shown above, you can use `RangeDict` to\nconcisely express different behavior depending on which range a value falls into.\n\nTo make a `RangeDict`, call `RangeDict()` with an either a `dict` or an iterable\nof 2-tuples corresponding `Range`s or `RangeSet`s with values. You can also use\na tuple of `Range`s as a key. \nA `RangeDict` can handle any type of `Range`, or even multiple different types of \n`Range`s all at once:\n\n```python\nadvisors = RangeDict([\n (Range(end=\"I\"), \"Gilliam\"),\n (Range(\"I\", \"Q\"), \"Jones\"),\n (Range(start=\"Q\"), \"Chapman\"),\n])\n\nmixmatch = RangeDict({\n (Range(0, 8), Range(\"A\", \"I\")): \"Gilliam\",\n (Range(8, 16), Range(\"I\", \"Q\")): \"Jones\",\n (Range(start=16), Range(start=\"Q\")): \"Chapman\",\n})\n```\n\nSee [the documentation](https://python-ranges.readthedocs.io/en/latest/#ranges.RangeDict) for more details.\n\n## Support / Contributing\n\nIf you spot any bugs in this module, please \n[submit an issue](https://github.com/Superbird11/ranges/issues)\ndetailing what you\ndid, what you were expecting, and what you saw, and I'll make a prompt effort\nto isolate the root cause and fix it. The error should be reproducible. \n\nIf, looking through the code, you spot any other improvements that could be\nmade, then feel free to submit issues detailing those as well. Also feel free\nto submit a pull request with improvements to the code.\n\nThis module is extensively unit-tested. All code contributions should be\naccompanied by thorough unit tests for every conceivable use case of the new\nfunctionality. If you spot any use cases that aren't currently covered by the\nunit test suite, feel free to either \n[submit a GitHub issue](https://github.com/Superbird11/ranges/issues) \ndetailing them, or\nsimply add them yourself and submit a pull request.\n\n### Possible To-Do List:\n\n- Add a notion of a `PermissiveRangeSet` (name pending) which allows multiple types \nof `Range`s that are not necessarily mutually comparable. In the initial design I\nconsidered a number of ways to implement this, but ran into conceptual difficulties,\nmainly in terms of handling performance and algorithms. If you can build a \n`PermissiveRangeSet` or similar class that implements this functionality, along with\na suitable set of unit tests, then feel free to do so and submit a pull request (if\nyou do, please include the reasoning for your design decisions).\n- Rewrite `RangeSet.intersection()` to use a pair-stepping\nalgorithm (akin to the \"merge\" part of MergeSort - iterate through the two \n`_LinkedList` data structures simultaneously and only advance one element of one\nlist at a time) instead of the current \"compare every element with every other \nelement\" solution. Adding short-circuiting to this (returning early from the method\nonce it's clear that there is no longer work to be done, even if the entire list\nhas not yet been iterated through) would also be useful, and the two approaches\nsynergize nicely. This won't lower the complexity class below its current\nworst-case `O(n^2)`, but it could drastically improve performance.\n- Rewrite `RangeSet.isdisjoint()` to use pair-stepping and short-circuiting. The\nreasoning here is the same as for `RangeSet.intersection()`.\n- Add pretty-printing for `RangeSet` and especially `RangeDict`. The `pprint`\nmodule does not seem to work on them, unfortunately.\n\nAny open issues or bugs are also fair game for contribution. See \n[above](#errors--contributing) for directions.\n\n## License\n\n[MIT License](LICENSE.txt). Feel free to use `ranges` however you like.\n\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/superbird11/ranges", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "python-ranges", "package_url": "https://pypi.org/project/python-ranges/", "platform": "", "project_url": "https://pypi.org/project/python-ranges/", "project_urls": { "Documentation": "https://python-ranges.readthedocs.io/en/latest/", "GitHub": "https://github.com/Superbird11/ranges", "Homepage": "https://github.com/superbird11/ranges" }, "release_url": "https://pypi.org/project/python-ranges/0.1.3/", "requires_dist": null, "requires_python": ">=3.6", "summary": "Continuous Range, RangeSet, and RangeDict data structures", "version": "0.1.3" }, "last_serial": 5777736, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "f0442f8ee8b37a0dfcac5fc53d20d5d6", "sha256": "c52b8b14c3ac3913f0b26c28dc20a347d44f2d4210dd57ad9b123ca787412d81" }, "downloads": -1, "filename": "python_ranges-0.1.0-py3-none-any.whl", "has_sig": false, "md5_digest": "f0442f8ee8b37a0dfcac5fc53d20d5d6", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 26319, "upload_time": "2019-09-03T19:38:48", "url": "https://files.pythonhosted.org/packages/bf/cc/fe22d83492dc2a69fb7c9eb729a328ce18a7cec8b34c75387e4aae9d7638/python_ranges-0.1.0-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3f004a6a1d71571956c06b5868dac828", "sha256": "a2207ea2f6a58d4934e6367f813a94e2144196f2404274a740b2565efd5e180a" }, "downloads": -1, "filename": "python-ranges-0.1.0.tar.gz", "has_sig": false, "md5_digest": "3f004a6a1d71571956c06b5868dac828", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 24578, "upload_time": "2019-09-03T19:38:53", "url": "https://files.pythonhosted.org/packages/06/f0/6dca0b613c5f5bcd8f8dd32aaab1287224e20a8374490e2e4116ba4fac41/python-ranges-0.1.0.tar.gz" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "fa3d9bf8bd5c39074540412962c724ca", "sha256": "e39421b068b47f123835f0c761a106a10bdf4520530ae5eff6fe47ad99f9d29d" }, "downloads": -1, "filename": "python_ranges-0.1.1-py3-none-any.whl", "has_sig": false, "md5_digest": "fa3d9bf8bd5c39074540412962c724ca", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 29749, "upload_time": "2019-09-03T19:38:50", "url": "https://files.pythonhosted.org/packages/cd/b6/38378bb634a08bb441156286313e55ff4453311183beea94ac4c05d62eeb/python_ranges-0.1.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "83635d0405a4d29bf0efef097876baed", "sha256": "640119b97a861e8ccbface88c0ff87342a454b4856d5f715529b68aaec99d372" }, "downloads": -1, "filename": "python-ranges-0.1.1.tar.gz", "has_sig": false, "md5_digest": "83635d0405a4d29bf0efef097876baed", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 24917, "upload_time": "2019-09-03T19:38:55", "url": "https://files.pythonhosted.org/packages/d2/21/0fc177feb84ad671ca8273dcd126ee2b27b8a680e4fd70defd53b82bd8a8/python-ranges-0.1.1.tar.gz" } ], "0.1.2": [ { "comment_text": "", "digests": { "md5": "1288c2b395769a72925ed4c0d6859ddc", "sha256": "47e6fe5b897e2c7c5a9f8093b65b56b3db3bea86a181b8fa395fc31dff8db453" }, "downloads": -1, "filename": "python_ranges-0.1.2-py3-none-any.whl", "has_sig": false, "md5_digest": "1288c2b395769a72925ed4c0d6859ddc", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 29749, "upload_time": "2019-09-03T19:38:52", "url": "https://files.pythonhosted.org/packages/29/a4/4e768160f10dda202dd7a990e4d2c2b2dfc5c47b41ac37798a3737c65603/python_ranges-0.1.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "13e31008f0d286988a20a98651f255e7", "sha256": "ee2dc49da84e5639963c96d0940a6eaeb74c2cb4cf4689b25894459d2f47c647" }, "downloads": -1, "filename": "python-ranges-0.1.2.tar.gz", "has_sig": false, "md5_digest": "13e31008f0d286988a20a98651f255e7", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 24921, "upload_time": "2019-09-03T19:38:56", "url": "https://files.pythonhosted.org/packages/82/8e/27d1968b5c28c8e8c24c9422d93fac68df504176e0daae73b1ab2717cacc/python-ranges-0.1.2.tar.gz" } ], "0.1.3": [ { "comment_text": "", "digests": { "md5": "cb4e67cc66036855a192a9a48a67b0c1", "sha256": "50de3e2058670ba624913e962fc3af96d5418223568eeade551f1f9b1bd0bcc0" }, "downloads": -1, "filename": "python_ranges-0.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "cb4e67cc66036855a192a9a48a67b0c1", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 29831, "upload_time": "2019-09-03T20:14:02", "url": "https://files.pythonhosted.org/packages/8d/c6/3477917624aea280dc6bac6a0937117fe7ed01df2378bb0f65ce0c1b48d9/python_ranges-0.1.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bee091d8615f7abfd81a1e4b000d812b", "sha256": "7690bc04d441dc783d90e4188feb652630db202d682bb8a9f9c36bcd5f61ac1c" }, "downloads": -1, "filename": "python-ranges-0.1.3.tar.gz", "has_sig": false, "md5_digest": "bee091d8615f7abfd81a1e4b000d812b", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 25067, "upload_time": "2019-09-03T20:14:03", "url": "https://files.pythonhosted.org/packages/20/53/97e8b368ee786575586b2c386f1777828fd248051c5b794a2b597e849462/python-ranges-0.1.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "cb4e67cc66036855a192a9a48a67b0c1", "sha256": "50de3e2058670ba624913e962fc3af96d5418223568eeade551f1f9b1bd0bcc0" }, "downloads": -1, "filename": "python_ranges-0.1.3-py3-none-any.whl", "has_sig": false, "md5_digest": "cb4e67cc66036855a192a9a48a67b0c1", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 29831, "upload_time": "2019-09-03T20:14:02", "url": "https://files.pythonhosted.org/packages/8d/c6/3477917624aea280dc6bac6a0937117fe7ed01df2378bb0f65ce0c1b48d9/python_ranges-0.1.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "bee091d8615f7abfd81a1e4b000d812b", "sha256": "7690bc04d441dc783d90e4188feb652630db202d682bb8a9f9c36bcd5f61ac1c" }, "downloads": -1, "filename": "python-ranges-0.1.3.tar.gz", "has_sig": false, "md5_digest": "bee091d8615f7abfd81a1e4b000d812b", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 25067, "upload_time": "2019-09-03T20:14:03", "url": "https://files.pythonhosted.org/packages/20/53/97e8b368ee786575586b2c386f1777828fd248051c5b794a2b597e849462/python-ranges-0.1.3.tar.gz" } ] }