derange


Namederange JSON
Version 0.2.2 PyPI version JSON
download
home_pageNone
SummaryCompress lists of integers to range objects
upload_time2024-12-01 12:36:50
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseMIT
keywords compression connected component consecutive distribution sort gaps and islands interval range sequential sorting sparse array streak
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            |repostatus| |ci-status| |coverage| |pyversions| |license|

.. |repostatus| image:: https://www.repostatus.org/badges/latest/active.svg
    :target: https://www.repostatus.org/#active
    :alt: Project Status: Active — The project has reached a stable, usable
          state and is being actively developed.

.. |ci-status| image:: https://github.com/jwodder/derange/actions/workflows/test.yml/badge.svg
    :target: https://github.com/jwodder/derange/actions/workflows/test.yml
    :alt: CI Status

.. |coverage| image:: https://codecov.io/gh/jwodder/derange/branch/master/graph/badge.svg
    :target: https://codecov.io/gh/jwodder/derange

.. |pyversions| image:: https://img.shields.io/pypi/pyversions/derange.svg
    :target: https://pypi.org/project/derange

.. |license| image:: https://img.shields.io/github/license/jwodder/derange.svg
    :target: https://opensource.org/licenses/MIT
    :alt: MIT License

`GitHub <https://github.com/jwodder/derange>`_
| `PyPI <https://pypi.org/project/derange>`_
| `Issues <https://github.com/jwodder/derange/issues>`_
| `Changelog <https://github.com/jwodder/derange/blob/master/CHANGELOG.md>`_

Do you have a list of integers?  Do you want to know what ranges of consecutive
values the list covers?  Do you need to solve a `gaps and islands
<https://stackoverflow.com/tags/gaps-and-islands/info>`_ problem outside of
SQL?  Maybe you have a list of dates and need to find the longest streak of
consecutive days on which something happened.  No?  Why not?  Well, either way,
the ``derange`` module is here for you, ready to solve all these problems and a
couple more.


Installation
============
``derange`` requires Python 3.8 or higher.  Just use `pip
<https://pip.pypa.io>`_ for Python 3 (You have pip, right?) to install it::

    python3 -m pip install derange


Examples
========
Condense commit years obtained from ``git log`` or the like into ``range``
objects:

>>> import derange
>>> derange.derange([2015, 2015, 2015, 2014, 2014, 2011, 2010, 2010, 2009, 2009])
[range(2009, 2012), range(2014, 2016)]

If the input is already sorted, you can condense it slightly faster with
``derange_sorted()``:

>>> derange.derange_sorted([2009, 2009, 2010, 2010, 2011, 2014, 2014, 2015, 2015, 2015])
[range(2009, 2012), range(2014, 2016)]

Organize non-integer values into closed intervals (represented as pairs of
endpoints) with ``deinterval()``:

>>> import datetime
>>> # deinterval() requires a callable for determining when two values are "adjacent":
>>> def within_24_hours(a,b):
...     return abs(a-b) <= datetime.timedelta(hours=24)
...
>>> timestamps = [
...     datetime.datetime(2017, 11, 2, 12, 0),
...     datetime.datetime(2017, 11, 3, 11, 0),
...     datetime.datetime(2017, 11, 4, 10, 0),
...     datetime.datetime(2017, 11, 5,  9, 0),
...     datetime.datetime(2017, 11, 6,  9, 0),
...     datetime.datetime(2017, 11, 7, 10, 0),
... ]
>>> derange.deinterval(within_24_hours, timestamps)
[(datetime.datetime(2017, 11, 2, 12, 0), datetime.datetime(2017, 11, 6, 9, 0)), (datetime.datetime(2017, 11, 7, 10, 0), datetime.datetime(2017, 11, 7, 10, 0))]

… which also has a ``deinterval_sorted()`` variant:

>>> derange.deinterval_sorted(within_24_hours, timestamps)
[(datetime.datetime(2017, 11, 2, 12, 0), datetime.datetime(2017, 11, 6, 9, 0)), (datetime.datetime(2017, 11, 7, 10, 0), datetime.datetime(2017, 11, 7, 10, 0))]
>>> derange.deinterval_sorted(within_24_hours, reversed(timestamps))
Traceback (most recent call last):
    ...
ValueError: sequence not in ascending order


API
===

.. code:: python

    derange.derange(iterable: Iterable[int]) -> List[range]

Convert a sequence of integers to a minimal list of ``range`` objects that
together contain all of the input elements.

Output is in strictly ascending order.  Input need not be in order (but see
also ``derange_sorted()``).  Duplicate input values are ignored.

.. code:: python

    derange.derange_sorted(iterable: Iterable[int]) -> List[range]

Convert a *non-decreasing* sequence of integers to a minimal list of ``range``
objects that together contain all of the input elements.  This is faster than
``derange()`` but only accepts sorted input.

.. code:: python

    derange.deinterval(
        adjacent: Callable[[T,T], bool],
        iterable: Iterable[T],
    ) -> List[Tuple[T,T]]

Convert a sequence of totally-ordered values to a minimal list of closed
intervals (represented as pairs of endpoints) that together contain all of the
input elements.  This is a generalization of ``derange()`` for arbitrary types.

Two input values will be placed in the same interval iff they are directly
adjacent or there exists a chain of adjacent input values connecting them,
where adjacency is defined by the given ``adjacent`` callable.

``adjacent`` will be called with two elements of ``iterable`` at a time to test
whether they should be placed in the same interval.  The binary relation
implied by ``adjacent`` must be reflexive and symmetric, and for all ``x < y <
z``, if ``adjacent(x, z)`` is true, then both ``adjacent(x, y)`` and
``adjacent(y, z)`` must also be true.

Output is in strictly ascending order.  Input need not be in order (but see
also ``deinterval_sorted()``).  Duplicate input values are ignored.

Note that, unlike with ``range`` objects, intervals returned from
``deinterval()`` contain their upper bounds.

.. code:: python

    derange.deinterval_sorted(
        adjacent: Callable[[T,T], bool],
        iterable: Iterable[T],
    ) -> List[Tuple[T,T]]

Convert a *non-decreasing* sequence of totally-ordered values to a minimal list
of closed intervals that together contain all of the input elements.  This is
faster than ``deinterval()`` but only accepts sorted input.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "derange",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "compression, connected component, consecutive, distribution sort, gaps and islands, interval, range, sequential, sorting, sparse array, streak",
    "author": null,
    "author_email": "John Thorvald Wodder II <derange@varonathe.org>",
    "download_url": "https://files.pythonhosted.org/packages/b0/44/494faaed0f858a814ea379d2feb6e97dc37cade82e7350d02cb1298b4512/derange-0.2.2.tar.gz",
    "platform": null,
    "description": "|repostatus| |ci-status| |coverage| |pyversions| |license|\n\n.. |repostatus| image:: https://www.repostatus.org/badges/latest/active.svg\n    :target: https://www.repostatus.org/#active\n    :alt: Project Status: Active \u2014 The project has reached a stable, usable\n          state and is being actively developed.\n\n.. |ci-status| image:: https://github.com/jwodder/derange/actions/workflows/test.yml/badge.svg\n    :target: https://github.com/jwodder/derange/actions/workflows/test.yml\n    :alt: CI Status\n\n.. |coverage| image:: https://codecov.io/gh/jwodder/derange/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/jwodder/derange\n\n.. |pyversions| image:: https://img.shields.io/pypi/pyversions/derange.svg\n    :target: https://pypi.org/project/derange\n\n.. |license| image:: https://img.shields.io/github/license/jwodder/derange.svg\n    :target: https://opensource.org/licenses/MIT\n    :alt: MIT License\n\n`GitHub <https://github.com/jwodder/derange>`_\n| `PyPI <https://pypi.org/project/derange>`_\n| `Issues <https://github.com/jwodder/derange/issues>`_\n| `Changelog <https://github.com/jwodder/derange/blob/master/CHANGELOG.md>`_\n\nDo you have a list of integers?  Do you want to know what ranges of consecutive\nvalues the list covers?  Do you need to solve a `gaps and islands\n<https://stackoverflow.com/tags/gaps-and-islands/info>`_ problem outside of\nSQL?  Maybe you have a list of dates and need to find the longest streak of\nconsecutive days on which something happened.  No?  Why not?  Well, either way,\nthe ``derange`` module is here for you, ready to solve all these problems and a\ncouple more.\n\n\nInstallation\n============\n``derange`` requires Python 3.8 or higher.  Just use `pip\n<https://pip.pypa.io>`_ for Python 3 (You have pip, right?) to install it::\n\n    python3 -m pip install derange\n\n\nExamples\n========\nCondense commit years obtained from ``git log`` or the like into ``range``\nobjects:\n\n>>> import derange\n>>> derange.derange([2015, 2015, 2015, 2014, 2014, 2011, 2010, 2010, 2009, 2009])\n[range(2009, 2012), range(2014, 2016)]\n\nIf the input is already sorted, you can condense it slightly faster with\n``derange_sorted()``:\n\n>>> derange.derange_sorted([2009, 2009, 2010, 2010, 2011, 2014, 2014, 2015, 2015, 2015])\n[range(2009, 2012), range(2014, 2016)]\n\nOrganize non-integer values into closed intervals (represented as pairs of\nendpoints) with ``deinterval()``:\n\n>>> import datetime\n>>> # deinterval() requires a callable for determining when two values are \"adjacent\":\n>>> def within_24_hours(a,b):\n...     return abs(a-b) <= datetime.timedelta(hours=24)\n...\n>>> timestamps = [\n...     datetime.datetime(2017, 11, 2, 12, 0),\n...     datetime.datetime(2017, 11, 3, 11, 0),\n...     datetime.datetime(2017, 11, 4, 10, 0),\n...     datetime.datetime(2017, 11, 5,  9, 0),\n...     datetime.datetime(2017, 11, 6,  9, 0),\n...     datetime.datetime(2017, 11, 7, 10, 0),\n... ]\n>>> derange.deinterval(within_24_hours, timestamps)\n[(datetime.datetime(2017, 11, 2, 12, 0), datetime.datetime(2017, 11, 6, 9, 0)), (datetime.datetime(2017, 11, 7, 10, 0), datetime.datetime(2017, 11, 7, 10, 0))]\n\n\u2026 which also has a ``deinterval_sorted()`` variant:\n\n>>> derange.deinterval_sorted(within_24_hours, timestamps)\n[(datetime.datetime(2017, 11, 2, 12, 0), datetime.datetime(2017, 11, 6, 9, 0)), (datetime.datetime(2017, 11, 7, 10, 0), datetime.datetime(2017, 11, 7, 10, 0))]\n>>> derange.deinterval_sorted(within_24_hours, reversed(timestamps))\nTraceback (most recent call last):\n    ...\nValueError: sequence not in ascending order\n\n\nAPI\n===\n\n.. code:: python\n\n    derange.derange(iterable: Iterable[int]) -> List[range]\n\nConvert a sequence of integers to a minimal list of ``range`` objects that\ntogether contain all of the input elements.\n\nOutput is in strictly ascending order.  Input need not be in order (but see\nalso ``derange_sorted()``).  Duplicate input values are ignored.\n\n.. code:: python\n\n    derange.derange_sorted(iterable: Iterable[int]) -> List[range]\n\nConvert a *non-decreasing* sequence of integers to a minimal list of ``range``\nobjects that together contain all of the input elements.  This is faster than\n``derange()`` but only accepts sorted input.\n\n.. code:: python\n\n    derange.deinterval(\n        adjacent: Callable[[T,T], bool],\n        iterable: Iterable[T],\n    ) -> List[Tuple[T,T]]\n\nConvert a sequence of totally-ordered values to a minimal list of closed\nintervals (represented as pairs of endpoints) that together contain all of the\ninput elements.  This is a generalization of ``derange()`` for arbitrary types.\n\nTwo input values will be placed in the same interval iff they are directly\nadjacent or there exists a chain of adjacent input values connecting them,\nwhere adjacency is defined by the given ``adjacent`` callable.\n\n``adjacent`` will be called with two elements of ``iterable`` at a time to test\nwhether they should be placed in the same interval.  The binary relation\nimplied by ``adjacent`` must be reflexive and symmetric, and for all ``x < y <\nz``, if ``adjacent(x, z)`` is true, then both ``adjacent(x, y)`` and\n``adjacent(y, z)`` must also be true.\n\nOutput is in strictly ascending order.  Input need not be in order (but see\nalso ``deinterval_sorted()``).  Duplicate input values are ignored.\n\nNote that, unlike with ``range`` objects, intervals returned from\n``deinterval()`` contain their upper bounds.\n\n.. code:: python\n\n    derange.deinterval_sorted(\n        adjacent: Callable[[T,T], bool],\n        iterable: Iterable[T],\n    ) -> List[Tuple[T,T]]\n\nConvert a *non-decreasing* sequence of totally-ordered values to a minimal list\nof closed intervals that together contain all of the input elements.  This is\nfaster than ``deinterval()`` but only accepts sorted input.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Compress lists of integers to range objects",
    "version": "0.2.2",
    "project_urls": {
        "Bug Tracker": "https://github.com/jwodder/derange/issues",
        "Source Code": "https://github.com/jwodder/derange"
    },
    "split_keywords": [
        "compression",
        " connected component",
        " consecutive",
        " distribution sort",
        " gaps and islands",
        " interval",
        " range",
        " sequential",
        " sorting",
        " sparse array",
        " streak"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "ebff772aad717d2dfbdd7b542c684d5584a622ab4d38c16e56381830abd9d7b7",
                "md5": "afa95a22e609a12dc00aac8b83eb9c8c",
                "sha256": "9debeae0e8448db6e5124b77d3583df27fc6bcea9a5a1e2b5309ca3b1ebf4dc3"
            },
            "downloads": -1,
            "filename": "derange-0.2.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "afa95a22e609a12dc00aac8b83eb9c8c",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 6499,
            "upload_time": "2024-12-01T12:36:44",
            "upload_time_iso_8601": "2024-12-01T12:36:44.318294Z",
            "url": "https://files.pythonhosted.org/packages/eb/ff/772aad717d2dfbdd7b542c684d5584a622ab4d38c16e56381830abd9d7b7/derange-0.2.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b044494faaed0f858a814ea379d2feb6e97dc37cade82e7350d02cb1298b4512",
                "md5": "6aedfc7acf4004a8092c8cd1b8674fd6",
                "sha256": "2222fa464ace9f94f65ed03a6cd384bbe8aad5f399f5c16c50e76c7880db504e"
            },
            "downloads": -1,
            "filename": "derange-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "6aedfc7acf4004a8092c8cd1b8674fd6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 7895,
            "upload_time": "2024-12-01T12:36:50",
            "upload_time_iso_8601": "2024-12-01T12:36:50.067062Z",
            "url": "https://files.pythonhosted.org/packages/b0/44/494faaed0f858a814ea379d2feb6e97dc37cade82e7350d02cb1298b4512/derange-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-01 12:36:50",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "jwodder",
    "github_project": "derange",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "derange"
}
        
Elapsed time: 0.40116s