aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2023-09-29 19:48:29 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2023-09-29 21:08:10 +0300
commitf9248350609752780ce95995e90a9aab61bbd82f (patch)
tree676484c8e2cd37654d2ba9449f18aa48e78724a5
parent009bb0299462be356e3d18de2642bfa8ec209511 (diff)
downloadydb-f9248350609752780ce95995e90a9aab61bbd82f.tar.gz
Intermediate changes
-rw-r--r--contrib/python/sortedcontainers/py2/.dist-info/METADATA (renamed from contrib/python/sortedcontainers/.dist-info/METADATA)0
-rw-r--r--contrib/python/sortedcontainers/py2/.dist-info/top_level.txt (renamed from contrib/python/sortedcontainers/.dist-info/top_level.txt)0
-rw-r--r--contrib/python/sortedcontainers/py2/LICENSE (renamed from contrib/python/sortedcontainers/LICENSE)0
-rw-r--r--contrib/python/sortedcontainers/py2/README.rst (renamed from contrib/python/sortedcontainers/README.rst)0
-rw-r--r--contrib/python/sortedcontainers/py2/sortedcontainers/__init__.py (renamed from contrib/python/sortedcontainers/sortedcontainers/__init__.py)0
-rw-r--r--contrib/python/sortedcontainers/py2/sortedcontainers/sorteddict.py (renamed from contrib/python/sortedcontainers/sortedcontainers/sorteddict.py)0
-rw-r--r--contrib/python/sortedcontainers/py2/sortedcontainers/sortedlist.py (renamed from contrib/python/sortedcontainers/sortedcontainers/sortedlist.py)0
-rw-r--r--contrib/python/sortedcontainers/py2/sortedcontainers/sortedset.py (renamed from contrib/python/sortedcontainers/sortedcontainers/sortedset.py)0
-rw-r--r--contrib/python/sortedcontainers/py2/ya.make25
-rw-r--r--contrib/python/sortedcontainers/py3/.dist-info/METADATA264
-rw-r--r--contrib/python/sortedcontainers/py3/.dist-info/top_level.txt1
-rw-r--r--contrib/python/sortedcontainers/py3/LICENSE13
-rw-r--r--contrib/python/sortedcontainers/py3/README.rst236
-rw-r--r--contrib/python/sortedcontainers/py3/sortedcontainers/__init__.py74
-rw-r--r--contrib/python/sortedcontainers/py3/sortedcontainers/sorteddict.py812
-rw-r--r--contrib/python/sortedcontainers/py3/sortedcontainers/sortedlist.py2646
-rw-r--r--contrib/python/sortedcontainers/py3/sortedcontainers/sortedset.py733
-rw-r--r--contrib/python/sortedcontainers/py3/ya.make25
-rw-r--r--contrib/python/sortedcontainers/ya.make27
19 files changed, 4839 insertions, 17 deletions
diff --git a/contrib/python/sortedcontainers/.dist-info/METADATA b/contrib/python/sortedcontainers/py2/.dist-info/METADATA
index dd28af42fb..dd28af42fb 100644
--- a/contrib/python/sortedcontainers/.dist-info/METADATA
+++ b/contrib/python/sortedcontainers/py2/.dist-info/METADATA
diff --git a/contrib/python/sortedcontainers/.dist-info/top_level.txt b/contrib/python/sortedcontainers/py2/.dist-info/top_level.txt
index 4693a5a5f3..4693a5a5f3 100644
--- a/contrib/python/sortedcontainers/.dist-info/top_level.txt
+++ b/contrib/python/sortedcontainers/py2/.dist-info/top_level.txt
diff --git a/contrib/python/sortedcontainers/LICENSE b/contrib/python/sortedcontainers/py2/LICENSE
index 668d8ecd66..668d8ecd66 100644
--- a/contrib/python/sortedcontainers/LICENSE
+++ b/contrib/python/sortedcontainers/py2/LICENSE
diff --git a/contrib/python/sortedcontainers/README.rst b/contrib/python/sortedcontainers/py2/README.rst
index 3e808386e7..3e808386e7 100644
--- a/contrib/python/sortedcontainers/README.rst
+++ b/contrib/python/sortedcontainers/py2/README.rst
diff --git a/contrib/python/sortedcontainers/sortedcontainers/__init__.py b/contrib/python/sortedcontainers/py2/sortedcontainers/__init__.py
index a141dd1de1..a141dd1de1 100644
--- a/contrib/python/sortedcontainers/sortedcontainers/__init__.py
+++ b/contrib/python/sortedcontainers/py2/sortedcontainers/__init__.py
diff --git a/contrib/python/sortedcontainers/sortedcontainers/sorteddict.py b/contrib/python/sortedcontainers/py2/sortedcontainers/sorteddict.py
index 910f260882..910f260882 100644
--- a/contrib/python/sortedcontainers/sortedcontainers/sorteddict.py
+++ b/contrib/python/sortedcontainers/py2/sortedcontainers/sorteddict.py
diff --git a/contrib/python/sortedcontainers/sortedcontainers/sortedlist.py b/contrib/python/sortedcontainers/py2/sortedcontainers/sortedlist.py
index e3b58eb92d..e3b58eb92d 100644
--- a/contrib/python/sortedcontainers/sortedcontainers/sortedlist.py
+++ b/contrib/python/sortedcontainers/py2/sortedcontainers/sortedlist.py
diff --git a/contrib/python/sortedcontainers/sortedcontainers/sortedset.py b/contrib/python/sortedcontainers/py2/sortedcontainers/sortedset.py
index be2b8999c5..be2b8999c5 100644
--- a/contrib/python/sortedcontainers/sortedcontainers/sortedset.py
+++ b/contrib/python/sortedcontainers/py2/sortedcontainers/sortedset.py
diff --git a/contrib/python/sortedcontainers/py2/ya.make b/contrib/python/sortedcontainers/py2/ya.make
new file mode 100644
index 0000000000..febfe94e35
--- /dev/null
+++ b/contrib/python/sortedcontainers/py2/ya.make
@@ -0,0 +1,25 @@
+# Generated by devtools/yamaker (pypi).
+
+PY2_LIBRARY()
+
+VERSION(2.4.0)
+
+LICENSE(Apache-2.0)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ sortedcontainers/__init__.py
+ sortedcontainers/sorteddict.py
+ sortedcontainers/sortedlist.py
+ sortedcontainers/sortedset.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/sortedcontainers/py2/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/sortedcontainers/py3/.dist-info/METADATA b/contrib/python/sortedcontainers/py3/.dist-info/METADATA
new file mode 100644
index 0000000000..dd28af42fb
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/.dist-info/METADATA
@@ -0,0 +1,264 @@
+Metadata-Version: 2.1
+Name: sortedcontainers
+Version: 2.4.0
+Summary: Sorted Containers -- Sorted List, Sorted Dict, Sorted Set
+Home-page: http://www.grantjenks.com/docs/sortedcontainers/
+Author: Grant Jenks
+Author-email: contact@grantjenks.com
+License: Apache 2.0
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Natural Language :: English
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.2
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+
+Python Sorted Containers
+========================
+
+`Sorted Containers`_ is an Apache2 licensed `sorted collections library`_,
+written in pure-Python, and fast as C-extensions.
+
+Python's standard library is great until you need a sorted collections
+type. Many will attest that you can get really far without one, but the moment
+you **really need** a sorted list, sorted dict, or sorted set, you're faced
+with a dozen different implementations, most using C-extensions without great
+documentation and benchmarking.
+
+In Python, we can do better. And we can do it in pure-Python!
+
+.. code-block:: python
+
+ >>> from sortedcontainers import SortedList
+ >>> sl = SortedList(['e', 'a', 'c', 'd', 'b'])
+ >>> sl
+ SortedList(['a', 'b', 'c', 'd', 'e'])
+ >>> sl *= 10_000_000
+ >>> sl.count('c')
+ 10000000
+ >>> sl[-3:]
+ ['e', 'e', 'e']
+ >>> from sortedcontainers import SortedDict
+ >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2})
+ >>> sd
+ SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.popitem(index=-1)
+ ('c', 3)
+ >>> from sortedcontainers import SortedSet
+ >>> ss = SortedSet('abracadabra')
+ >>> ss
+ SortedSet(['a', 'b', 'c', 'd', 'r'])
+ >>> ss.bisect_left('c')
+ 2
+
+All of the operations shown above run in faster than linear time. The above
+demo also takes nearly a gigabyte of memory to run. When the sorted list is
+multiplied by ten million, it stores ten million references to each of "a"
+through "e". Each reference requires eight bytes in the sorted
+container. That's pretty hard to beat as it's the cost of a pointer to each
+object. It's also 66% less overhead than a typical binary tree implementation
+(e.g. Red-Black Tree, AVL-Tree, AA-Tree, Splay-Tree, Treap, etc.) for which
+every node must also store two pointers to children nodes.
+
+`Sorted Containers`_ takes all of the work out of Python sorted collections -
+making your deployment and use of Python easy. There's no need to install a C
+compiler or pre-build and distribute custom extensions. Performance is a
+feature and testing has 100% coverage with unit tests and hours of stress.
+
+.. _`Sorted Containers`: http://www.grantjenks.com/docs/sortedcontainers/
+.. _`sorted collections library`: http://www.grantjenks.com/docs/sortedcontainers/
+
+Testimonials
+------------
+
+**Alex Martelli**, `Fellow of the Python Software Foundation`_
+
+"Good stuff! ... I like the `simple, effective implementation`_ idea of
+splitting the sorted containers into smaller "fragments" to avoid the O(N)
+insertion costs."
+
+**Jeff Knupp**, `author of Writing Idiomatic Python and Python Trainer`_
+
+"That last part, "fast as C-extensions," was difficult to believe. I would need
+some sort of `Performance Comparison`_ to be convinced this is true. The author
+includes this in the docs. It is."
+
+**Kevin Samuel**, `Python and Django Trainer`_
+
+I'm quite amazed, not just by the code quality (it's incredibly readable and
+has more comment than code, wow), but the actual amount of work you put at
+stuff that is *not* code: documentation, benchmarking, implementation
+explanations. Even the git log is clean and the unit tests run out of the box
+on Python 2 and 3.
+
+**Mark Summerfield**, a short plea for `Python Sorted Collections`_
+
+Python's "batteries included" standard library seems to have a battery
+missing. And the argument that "we never had it before" has worn thin. It is
+time that Python offered a full range of collection classes out of the box,
+including sorted ones.
+
+`Sorted Containers`_ is used in popular open source projects such as:
+`Zipline`_, an algorithmic trading library from Quantopian; `Angr`_, a binary
+analysis platform from UC Santa Barbara; `Trio`_, an async I/O library; and
+`Dask Distributed`_, a distributed computation library supported by Continuum
+Analytics.
+
+.. _`Fellow of the Python Software Foundation`: https://en.wikipedia.org/wiki/Alex_Martelli
+.. _`simple, effective implementation`: http://www.grantjenks.com/docs/sortedcontainers/implementation.html
+.. _`author of Writing Idiomatic Python and Python Trainer`: https://jeffknupp.com/
+.. _`Python and Django Trainer`: https://www.elephorm.com/formateur/kevin-samuel
+.. _`Python Sorted Collections`: http://www.qtrac.eu/pysorted.html
+.. _`Zipline`: https://github.com/quantopian/zipline
+.. _`Angr`: https://github.com/angr/angr
+.. _`Trio`: https://github.com/python-trio/trio
+.. _`Dask Distributed`: https://github.com/dask/distributed
+
+Features
+--------
+
+- Pure-Python
+- Fully documented
+- Benchmark comparison (alternatives, runtimes, load-factors)
+- 100% test coverage
+- Hours of stress testing
+- Performance matters (often faster than C implementations)
+- Compatible API (nearly identical to older blist and bintrees modules)
+- Feature-rich (e.g. get the five largest keys in a sorted dict: d.keys()[-5:])
+- Pragmatic design (e.g. SortedSet is a Python set with a SortedList index)
+- Developed on Python 3.7
+- Tested on CPython 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7 and PyPy, PyPy3
+
+.. image:: https://api.travis-ci.org/grantjenks/python-sortedcontainers.svg?branch=master
+ :target: http://www.grantjenks.com/docs/sortedcontainers/
+
+.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-sortedcontainers?branch=master&svg=true
+ :target: http://www.grantjenks.com/docs/sortedcontainers/
+
+Quickstart
+----------
+
+Installing `Sorted Containers`_ is simple with `pip
+<https://pypi.org/project/pip/>`_::
+
+ $ pip install sortedcontainers
+
+You can access documentation in the interpreter with Python's built-in `help`
+function. The `help` works on modules, classes and methods in `Sorted
+Containers`_.
+
+.. code-block:: python
+
+ >>> import sortedcontainers
+ >>> help(sortedcontainers)
+ >>> from sortedcontainers import SortedDict
+ >>> help(SortedDict)
+ >>> help(SortedDict.popitem)
+
+Documentation
+-------------
+
+Complete documentation for `Sorted Containers`_ is available at
+http://www.grantjenks.com/docs/sortedcontainers/
+
+User Guide
+..........
+
+The user guide provides an introduction to `Sorted Containers`_ and extensive
+performance comparisons and analysis.
+
+- `Introduction`_
+- `Performance Comparison`_
+- `Load Factor Performance Comparison`_
+- `Runtime Performance Comparison`_
+- `Simulated Workload Performance Comparison`_
+- `Performance at Scale`_
+
+.. _`Introduction`: http://www.grantjenks.com/docs/sortedcontainers/introduction.html
+.. _`Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance.html
+.. _`Load Factor Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-load.html
+.. _`Runtime Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-runtime.html
+.. _`Simulated Workload Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-workload.html
+.. _`Performance at Scale`: http://www.grantjenks.com/docs/sortedcontainers/performance-scale.html
+
+Community Guide
+...............
+
+The community guide provides information on the development of `Sorted
+Containers`_ along with support, implementation, and history details.
+
+- `Development and Support`_
+- `Implementation Details`_
+- `Release History`_
+
+.. _`Development and Support`: http://www.grantjenks.com/docs/sortedcontainers/development.html
+.. _`Implementation Details`: http://www.grantjenks.com/docs/sortedcontainers/implementation.html
+.. _`Release History`: http://www.grantjenks.com/docs/sortedcontainers/history.html
+
+API Documentation
+.................
+
+The API documentation provides information on specific functions, classes, and
+modules in the `Sorted Containers`_ package.
+
+- `Sorted List`_
+- `Sorted Dict`_
+- `Sorted Set`_
+
+.. _`Sorted List`: http://www.grantjenks.com/docs/sortedcontainers/sortedlist.html
+.. _`Sorted Dict`: http://www.grantjenks.com/docs/sortedcontainers/sorteddict.html
+.. _`Sorted Set`: http://www.grantjenks.com/docs/sortedcontainers/sortedset.html
+
+Talks
+-----
+
+- `Python Sorted Collections | PyCon 2016 Talk`_
+- `SF Python Holiday Party 2015 Lightning Talk`_
+- `DjangoCon 2015 Lightning Talk`_
+
+.. _`Python Sorted Collections | PyCon 2016 Talk`: http://www.grantjenks.com/docs/sortedcontainers/pycon-2016-talk.html
+.. _`SF Python Holiday Party 2015 Lightning Talk`: http://www.grantjenks.com/docs/sortedcontainers/sf-python-2015-lightning-talk.html
+.. _`DjangoCon 2015 Lightning Talk`: http://www.grantjenks.com/docs/sortedcontainers/djangocon-2015-lightning-talk.html
+
+Resources
+---------
+
+- `Sorted Containers Documentation`_
+- `Sorted Containers at PyPI`_
+- `Sorted Containers at Github`_
+- `Sorted Containers Issue Tracker`_
+
+.. _`Sorted Containers Documentation`: http://www.grantjenks.com/docs/sortedcontainers/
+.. _`Sorted Containers at PyPI`: https://pypi.org/project/sortedcontainers/
+.. _`Sorted Containers at Github`: https://github.com/grantjenks/python-sortedcontainers
+.. _`Sorted Containers Issue Tracker`: https://github.com/grantjenks/python-sortedcontainers/issues
+
+Sorted Containers License
+-------------------------
+
+Copyright 2014-2019 Grant Jenks
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
diff --git a/contrib/python/sortedcontainers/py3/.dist-info/top_level.txt b/contrib/python/sortedcontainers/py3/.dist-info/top_level.txt
new file mode 100644
index 0000000000..4693a5a5f3
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/.dist-info/top_level.txt
@@ -0,0 +1 @@
+sortedcontainers
diff --git a/contrib/python/sortedcontainers/py3/LICENSE b/contrib/python/sortedcontainers/py3/LICENSE
new file mode 100644
index 0000000000..668d8ecd66
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/LICENSE
@@ -0,0 +1,13 @@
+Copyright 2014-2019 Grant Jenks
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/contrib/python/sortedcontainers/py3/README.rst b/contrib/python/sortedcontainers/py3/README.rst
new file mode 100644
index 0000000000..3e808386e7
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/README.rst
@@ -0,0 +1,236 @@
+Python Sorted Containers
+========================
+
+`Sorted Containers`_ is an Apache2 licensed `sorted collections library`_,
+written in pure-Python, and fast as C-extensions.
+
+Python's standard library is great until you need a sorted collections
+type. Many will attest that you can get really far without one, but the moment
+you **really need** a sorted list, sorted dict, or sorted set, you're faced
+with a dozen different implementations, most using C-extensions without great
+documentation and benchmarking.
+
+In Python, we can do better. And we can do it in pure-Python!
+
+.. code-block:: python
+
+ >>> from sortedcontainers import SortedList
+ >>> sl = SortedList(['e', 'a', 'c', 'd', 'b'])
+ >>> sl
+ SortedList(['a', 'b', 'c', 'd', 'e'])
+ >>> sl *= 10_000_000
+ >>> sl.count('c')
+ 10000000
+ >>> sl[-3:]
+ ['e', 'e', 'e']
+ >>> from sortedcontainers import SortedDict
+ >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2})
+ >>> sd
+ SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.popitem(index=-1)
+ ('c', 3)
+ >>> from sortedcontainers import SortedSet
+ >>> ss = SortedSet('abracadabra')
+ >>> ss
+ SortedSet(['a', 'b', 'c', 'd', 'r'])
+ >>> ss.bisect_left('c')
+ 2
+
+All of the operations shown above run in faster than linear time. The above
+demo also takes nearly a gigabyte of memory to run. When the sorted list is
+multiplied by ten million, it stores ten million references to each of "a"
+through "e". Each reference requires eight bytes in the sorted
+container. That's pretty hard to beat as it's the cost of a pointer to each
+object. It's also 66% less overhead than a typical binary tree implementation
+(e.g. Red-Black Tree, AVL-Tree, AA-Tree, Splay-Tree, Treap, etc.) for which
+every node must also store two pointers to children nodes.
+
+`Sorted Containers`_ takes all of the work out of Python sorted collections -
+making your deployment and use of Python easy. There's no need to install a C
+compiler or pre-build and distribute custom extensions. Performance is a
+feature and testing has 100% coverage with unit tests and hours of stress.
+
+.. _`Sorted Containers`: http://www.grantjenks.com/docs/sortedcontainers/
+.. _`sorted collections library`: http://www.grantjenks.com/docs/sortedcontainers/
+
+Testimonials
+------------
+
+**Alex Martelli**, `Fellow of the Python Software Foundation`_
+
+"Good stuff! ... I like the `simple, effective implementation`_ idea of
+splitting the sorted containers into smaller "fragments" to avoid the O(N)
+insertion costs."
+
+**Jeff Knupp**, `author of Writing Idiomatic Python and Python Trainer`_
+
+"That last part, "fast as C-extensions," was difficult to believe. I would need
+some sort of `Performance Comparison`_ to be convinced this is true. The author
+includes this in the docs. It is."
+
+**Kevin Samuel**, `Python and Django Trainer`_
+
+I'm quite amazed, not just by the code quality (it's incredibly readable and
+has more comment than code, wow), but the actual amount of work you put at
+stuff that is *not* code: documentation, benchmarking, implementation
+explanations. Even the git log is clean and the unit tests run out of the box
+on Python 2 and 3.
+
+**Mark Summerfield**, a short plea for `Python Sorted Collections`_
+
+Python's "batteries included" standard library seems to have a battery
+missing. And the argument that "we never had it before" has worn thin. It is
+time that Python offered a full range of collection classes out of the box,
+including sorted ones.
+
+`Sorted Containers`_ is used in popular open source projects such as:
+`Zipline`_, an algorithmic trading library from Quantopian; `Angr`_, a binary
+analysis platform from UC Santa Barbara; `Trio`_, an async I/O library; and
+`Dask Distributed`_, a distributed computation library supported by Continuum
+Analytics.
+
+.. _`Fellow of the Python Software Foundation`: https://en.wikipedia.org/wiki/Alex_Martelli
+.. _`simple, effective implementation`: http://www.grantjenks.com/docs/sortedcontainers/implementation.html
+.. _`author of Writing Idiomatic Python and Python Trainer`: https://jeffknupp.com/
+.. _`Python and Django Trainer`: https://www.elephorm.com/formateur/kevin-samuel
+.. _`Python Sorted Collections`: http://www.qtrac.eu/pysorted.html
+.. _`Zipline`: https://github.com/quantopian/zipline
+.. _`Angr`: https://github.com/angr/angr
+.. _`Trio`: https://github.com/python-trio/trio
+.. _`Dask Distributed`: https://github.com/dask/distributed
+
+Features
+--------
+
+- Pure-Python
+- Fully documented
+- Benchmark comparison (alternatives, runtimes, load-factors)
+- 100% test coverage
+- Hours of stress testing
+- Performance matters (often faster than C implementations)
+- Compatible API (nearly identical to older blist and bintrees modules)
+- Feature-rich (e.g. get the five largest keys in a sorted dict: d.keys()[-5:])
+- Pragmatic design (e.g. SortedSet is a Python set with a SortedList index)
+- Developed on Python 3.7
+- Tested on CPython 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7 and PyPy, PyPy3
+
+.. image:: https://api.travis-ci.org/grantjenks/python-sortedcontainers.svg?branch=master
+ :target: http://www.grantjenks.com/docs/sortedcontainers/
+
+.. image:: https://ci.appveyor.com/api/projects/status/github/grantjenks/python-sortedcontainers?branch=master&svg=true
+ :target: http://www.grantjenks.com/docs/sortedcontainers/
+
+Quickstart
+----------
+
+Installing `Sorted Containers`_ is simple with `pip
+<https://pypi.org/project/pip/>`_::
+
+ $ pip install sortedcontainers
+
+You can access documentation in the interpreter with Python's built-in `help`
+function. The `help` works on modules, classes and methods in `Sorted
+Containers`_.
+
+.. code-block:: python
+
+ >>> import sortedcontainers
+ >>> help(sortedcontainers)
+ >>> from sortedcontainers import SortedDict
+ >>> help(SortedDict)
+ >>> help(SortedDict.popitem)
+
+Documentation
+-------------
+
+Complete documentation for `Sorted Containers`_ is available at
+http://www.grantjenks.com/docs/sortedcontainers/
+
+User Guide
+..........
+
+The user guide provides an introduction to `Sorted Containers`_ and extensive
+performance comparisons and analysis.
+
+- `Introduction`_
+- `Performance Comparison`_
+- `Load Factor Performance Comparison`_
+- `Runtime Performance Comparison`_
+- `Simulated Workload Performance Comparison`_
+- `Performance at Scale`_
+
+.. _`Introduction`: http://www.grantjenks.com/docs/sortedcontainers/introduction.html
+.. _`Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance.html
+.. _`Load Factor Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-load.html
+.. _`Runtime Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-runtime.html
+.. _`Simulated Workload Performance Comparison`: http://www.grantjenks.com/docs/sortedcontainers/performance-workload.html
+.. _`Performance at Scale`: http://www.grantjenks.com/docs/sortedcontainers/performance-scale.html
+
+Community Guide
+...............
+
+The community guide provides information on the development of `Sorted
+Containers`_ along with support, implementation, and history details.
+
+- `Development and Support`_
+- `Implementation Details`_
+- `Release History`_
+
+.. _`Development and Support`: http://www.grantjenks.com/docs/sortedcontainers/development.html
+.. _`Implementation Details`: http://www.grantjenks.com/docs/sortedcontainers/implementation.html
+.. _`Release History`: http://www.grantjenks.com/docs/sortedcontainers/history.html
+
+API Documentation
+.................
+
+The API documentation provides information on specific functions, classes, and
+modules in the `Sorted Containers`_ package.
+
+- `Sorted List`_
+- `Sorted Dict`_
+- `Sorted Set`_
+
+.. _`Sorted List`: http://www.grantjenks.com/docs/sortedcontainers/sortedlist.html
+.. _`Sorted Dict`: http://www.grantjenks.com/docs/sortedcontainers/sorteddict.html
+.. _`Sorted Set`: http://www.grantjenks.com/docs/sortedcontainers/sortedset.html
+
+Talks
+-----
+
+- `Python Sorted Collections | PyCon 2016 Talk`_
+- `SF Python Holiday Party 2015 Lightning Talk`_
+- `DjangoCon 2015 Lightning Talk`_
+
+.. _`Python Sorted Collections | PyCon 2016 Talk`: http://www.grantjenks.com/docs/sortedcontainers/pycon-2016-talk.html
+.. _`SF Python Holiday Party 2015 Lightning Talk`: http://www.grantjenks.com/docs/sortedcontainers/sf-python-2015-lightning-talk.html
+.. _`DjangoCon 2015 Lightning Talk`: http://www.grantjenks.com/docs/sortedcontainers/djangocon-2015-lightning-talk.html
+
+Resources
+---------
+
+- `Sorted Containers Documentation`_
+- `Sorted Containers at PyPI`_
+- `Sorted Containers at Github`_
+- `Sorted Containers Issue Tracker`_
+
+.. _`Sorted Containers Documentation`: http://www.grantjenks.com/docs/sortedcontainers/
+.. _`Sorted Containers at PyPI`: https://pypi.org/project/sortedcontainers/
+.. _`Sorted Containers at Github`: https://github.com/grantjenks/python-sortedcontainers
+.. _`Sorted Containers Issue Tracker`: https://github.com/grantjenks/python-sortedcontainers/issues
+
+Sorted Containers License
+-------------------------
+
+Copyright 2014-2019 Grant Jenks
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/contrib/python/sortedcontainers/py3/sortedcontainers/__init__.py b/contrib/python/sortedcontainers/py3/sortedcontainers/__init__.py
new file mode 100644
index 0000000000..a141dd1de1
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/sortedcontainers/__init__.py
@@ -0,0 +1,74 @@
+"""Sorted Containers -- Sorted List, Sorted Dict, Sorted Set
+
+Sorted Containers is an Apache2 licensed containers library, written in
+pure-Python, and fast as C-extensions.
+
+Python's standard library is great until you need a sorted collections
+type. Many will attest that you can get really far without one, but the moment
+you **really need** a sorted list, dict, or set, you're faced with a dozen
+different implementations, most using C-extensions without great documentation
+and benchmarking.
+
+In Python, we can do better. And we can do it in pure-Python!
+
+::
+
+ >>> from sortedcontainers import SortedList
+ >>> sl = SortedList(['e', 'a', 'c', 'd', 'b'])
+ >>> sl
+ SortedList(['a', 'b', 'c', 'd', 'e'])
+ >>> sl *= 1000000
+ >>> sl.count('c')
+ 1000000
+ >>> sl[-3:]
+ ['e', 'e', 'e']
+ >>> from sortedcontainers import SortedDict
+ >>> sd = SortedDict({'c': 3, 'a': 1, 'b': 2})
+ >>> sd
+ SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.popitem(index=-1)
+ ('c', 3)
+ >>> from sortedcontainers import SortedSet
+ >>> ss = SortedSet('abracadabra')
+ >>> ss
+ SortedSet(['a', 'b', 'c', 'd', 'r'])
+ >>> ss.bisect_left('c')
+ 2
+
+Sorted Containers takes all of the work out of Python sorted types - making
+your deployment and use of Python easy. There's no need to install a C compiler
+or pre-build and distribute custom extensions. Performance is a feature and
+testing has 100% coverage with unit tests and hours of stress.
+
+:copyright: (c) 2014-2019 by Grant Jenks.
+:license: Apache 2.0, see LICENSE for more details.
+
+"""
+
+
+from .sortedlist import SortedList, SortedKeyList, SortedListWithKey
+from .sortedset import SortedSet
+from .sorteddict import (
+ SortedDict,
+ SortedKeysView,
+ SortedItemsView,
+ SortedValuesView,
+)
+
+__all__ = [
+ 'SortedList',
+ 'SortedKeyList',
+ 'SortedListWithKey',
+ 'SortedDict',
+ 'SortedKeysView',
+ 'SortedItemsView',
+ 'SortedValuesView',
+ 'SortedSet',
+]
+
+__title__ = 'sortedcontainers'
+__version__ = '2.4.0'
+__build__ = 0x020400
+__author__ = 'Grant Jenks'
+__license__ = 'Apache 2.0'
+__copyright__ = '2014-2019, Grant Jenks'
diff --git a/contrib/python/sortedcontainers/py3/sortedcontainers/sorteddict.py b/contrib/python/sortedcontainers/py3/sortedcontainers/sorteddict.py
new file mode 100644
index 0000000000..910f260882
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/sortedcontainers/sorteddict.py
@@ -0,0 +1,812 @@
+"""Sorted Dict
+==============
+
+:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
+collections library, written in pure-Python, and fast as C-extensions. The
+:doc:`introduction<introduction>` is the best way to get started.
+
+Sorted dict implementations:
+
+.. currentmodule:: sortedcontainers
+
+* :class:`SortedDict`
+* :class:`SortedKeysView`
+* :class:`SortedItemsView`
+* :class:`SortedValuesView`
+
+"""
+
+import sys
+import warnings
+
+from itertools import chain
+
+from .sortedlist import SortedList, recursive_repr
+from .sortedset import SortedSet
+
+###############################################################################
+# BEGIN Python 2/3 Shims
+###############################################################################
+
+try:
+ from collections.abc import (
+ ItemsView, KeysView, Mapping, ValuesView, Sequence
+ )
+except ImportError:
+ from collections import ItemsView, KeysView, Mapping, ValuesView, Sequence
+
+###############################################################################
+# END Python 2/3 Shims
+###############################################################################
+
+
+class SortedDict(dict):
+ """Sorted dict is a sorted mutable mapping.
+
+ Sorted dict keys are maintained in sorted order. The design of sorted dict
+ is simple: sorted dict inherits from dict to store items and maintains a
+ sorted list of keys.
+
+ Sorted dict keys must be hashable and comparable. The hash and total
+ ordering of keys must not change while they are stored in the sorted dict.
+
+ Mutable mapping methods:
+
+ * :func:`SortedDict.__getitem__` (inherited from dict)
+ * :func:`SortedDict.__setitem__`
+ * :func:`SortedDict.__delitem__`
+ * :func:`SortedDict.__iter__`
+ * :func:`SortedDict.__len__` (inherited from dict)
+
+ Methods for adding items:
+
+ * :func:`SortedDict.setdefault`
+ * :func:`SortedDict.update`
+
+ Methods for removing items:
+
+ * :func:`SortedDict.clear`
+ * :func:`SortedDict.pop`
+ * :func:`SortedDict.popitem`
+
+ Methods for looking up items:
+
+ * :func:`SortedDict.__contains__` (inherited from dict)
+ * :func:`SortedDict.get` (inherited from dict)
+ * :func:`SortedDict.peekitem`
+
+ Methods for views:
+
+ * :func:`SortedDict.keys`
+ * :func:`SortedDict.items`
+ * :func:`SortedDict.values`
+
+ Methods for miscellany:
+
+ * :func:`SortedDict.copy`
+ * :func:`SortedDict.fromkeys`
+ * :func:`SortedDict.__reversed__`
+ * :func:`SortedDict.__eq__` (inherited from dict)
+ * :func:`SortedDict.__ne__` (inherited from dict)
+ * :func:`SortedDict.__repr__`
+ * :func:`SortedDict._check`
+
+ Sorted list methods available (applies to keys):
+
+ * :func:`SortedList.bisect_left`
+ * :func:`SortedList.bisect_right`
+ * :func:`SortedList.count`
+ * :func:`SortedList.index`
+ * :func:`SortedList.irange`
+ * :func:`SortedList.islice`
+ * :func:`SortedList._reset`
+
+ Additional sorted list methods available, if key-function used:
+
+ * :func:`SortedKeyList.bisect_key_left`
+ * :func:`SortedKeyList.bisect_key_right`
+ * :func:`SortedKeyList.irange_key`
+
+ Sorted dicts may only be compared for equality and inequality.
+
+ """
+ def __init__(self, *args, **kwargs):
+ """Initialize sorted dict instance.
+
+ Optional key-function argument defines a callable that, like the `key`
+ argument to the built-in `sorted` function, extracts a comparison key
+ from each dictionary key. If no function is specified, the default
+ compares the dictionary keys directly. The key-function argument must
+ be provided as a positional argument and must come before all other
+ arguments.
+
+ Optional iterable argument provides an initial sequence of pairs to
+ initialize the sorted dict. Each pair in the sequence defines the key
+ and corresponding value. If a key is seen more than once, the last
+ value associated with it is stored in the new sorted dict.
+
+ Optional mapping argument provides an initial mapping of items to
+ initialize the sorted dict.
+
+ If keyword arguments are given, the keywords themselves, with their
+ associated values, are added as items to the dictionary. If a key is
+ specified both in the positional argument and as a keyword argument,
+ the value associated with the keyword is stored in the
+ sorted dict.
+
+ Sorted dict keys must be hashable, per the requirement for Python's
+ dictionaries. Keys (or the result of the key-function) must also be
+ comparable, per the requirement for sorted lists.
+
+ >>> d = {'alpha': 1, 'beta': 2}
+ >>> SortedDict([('alpha', 1), ('beta', 2)]) == d
+ True
+ >>> SortedDict({'alpha': 1, 'beta': 2}) == d
+ True
+ >>> SortedDict(alpha=1, beta=2) == d
+ True
+
+ """
+ if args and (args[0] is None or callable(args[0])):
+ _key = self._key = args[0]
+ args = args[1:]
+ else:
+ _key = self._key = None
+
+ self._list = SortedList(key=_key)
+
+ # Reaching through ``self._list`` repeatedly adds unnecessary overhead
+ # so cache references to sorted list methods.
+
+ _list = self._list
+ self._list_add = _list.add
+ self._list_clear = _list.clear
+ self._list_iter = _list.__iter__
+ self._list_reversed = _list.__reversed__
+ self._list_pop = _list.pop
+ self._list_remove = _list.remove
+ self._list_update = _list.update
+
+ # Expose some sorted list methods publicly.
+
+ self.bisect_left = _list.bisect_left
+ self.bisect = _list.bisect_right
+ self.bisect_right = _list.bisect_right
+ self.index = _list.index
+ self.irange = _list.irange
+ self.islice = _list.islice
+ self._reset = _list._reset
+
+ if _key is not None:
+ self.bisect_key_left = _list.bisect_key_left
+ self.bisect_key_right = _list.bisect_key_right
+ self.bisect_key = _list.bisect_key
+ self.irange_key = _list.irange_key
+
+ self._update(*args, **kwargs)
+
+
+ @property
+ def key(self):
+ """Function used to extract comparison key from keys.
+
+ Sorted dict compares keys directly when the key function is none.
+
+ """
+ return self._key
+
+
+ @property
+ def iloc(self):
+ """Cached reference of sorted keys view.
+
+ Deprecated in version 2 of Sorted Containers. Use
+ :func:`SortedDict.keys` instead.
+
+ """
+ # pylint: disable=attribute-defined-outside-init
+ try:
+ return self._iloc
+ except AttributeError:
+ warnings.warn(
+ 'sorted_dict.iloc is deprecated.'
+ ' Use SortedDict.keys() instead.',
+ DeprecationWarning,
+ stacklevel=2,
+ )
+ _iloc = self._iloc = SortedKeysView(self)
+ return _iloc
+
+
+ def clear(self):
+
+ """Remove all items from sorted dict.
+
+ Runtime complexity: `O(n)`
+
+ """
+ dict.clear(self)
+ self._list_clear()
+
+
+ def __delitem__(self, key):
+ """Remove item from sorted dict identified by `key`.
+
+ ``sd.__delitem__(key)`` <==> ``del sd[key]``
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> del sd['b']
+ >>> sd
+ SortedDict({'a': 1, 'c': 3})
+ >>> del sd['z']
+ Traceback (most recent call last):
+ ...
+ KeyError: 'z'
+
+ :param key: `key` for item lookup
+ :raises KeyError: if key not found
+
+ """
+ dict.__delitem__(self, key)
+ self._list_remove(key)
+
+
+ def __iter__(self):
+ """Return an iterator over the keys of the sorted dict.
+
+ ``sd.__iter__()`` <==> ``iter(sd)``
+
+ Iterating the sorted dict while adding or deleting items may raise a
+ :exc:`RuntimeError` or fail to iterate over all keys.
+
+ """
+ return self._list_iter()
+
+
+ def __reversed__(self):
+ """Return a reverse iterator over the keys of the sorted dict.
+
+ ``sd.__reversed__()`` <==> ``reversed(sd)``
+
+ Iterating the sorted dict while adding or deleting items may raise a
+ :exc:`RuntimeError` or fail to iterate over all keys.
+
+ """
+ return self._list_reversed()
+
+
+ def __setitem__(self, key, value):
+ """Store item in sorted dict with `key` and corresponding `value`.
+
+ ``sd.__setitem__(key, value)`` <==> ``sd[key] = value``
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict()
+ >>> sd['c'] = 3
+ >>> sd['a'] = 1
+ >>> sd['b'] = 2
+ >>> sd
+ SortedDict({'a': 1, 'b': 2, 'c': 3})
+
+ :param key: key for item
+ :param value: value for item
+
+ """
+ if key not in self:
+ self._list_add(key)
+ dict.__setitem__(self, key, value)
+
+ _setitem = __setitem__
+
+
+ def __or__(self, other):
+ if not isinstance(other, Mapping):
+ return NotImplemented
+ items = chain(self.items(), other.items())
+ return self.__class__(self._key, items)
+
+
+ def __ror__(self, other):
+ if not isinstance(other, Mapping):
+ return NotImplemented
+ items = chain(other.items(), self.items())
+ return self.__class__(self._key, items)
+
+
+ def __ior__(self, other):
+ self._update(other)
+ return self
+
+
+ def copy(self):
+ """Return a shallow copy of the sorted dict.
+
+ Runtime complexity: `O(n)`
+
+ :return: new sorted dict
+
+ """
+ return self.__class__(self._key, self.items())
+
+ __copy__ = copy
+
+
+ @classmethod
+ def fromkeys(cls, iterable, value=None):
+ """Return a new sorted dict initailized from `iterable` and `value`.
+
+ Items in the sorted dict have keys from `iterable` and values equal to
+ `value`.
+
+ Runtime complexity: `O(n*log(n))`
+
+ :return: new sorted dict
+
+ """
+ return cls((key, value) for key in iterable)
+
+
+ def keys(self):
+ """Return new sorted keys view of the sorted dict's keys.
+
+ See :class:`SortedKeysView` for details.
+
+ :return: new sorted keys view
+
+ """
+ return SortedKeysView(self)
+
+
+ def items(self):
+ """Return new sorted items view of the sorted dict's items.
+
+ See :class:`SortedItemsView` for details.
+
+ :return: new sorted items view
+
+ """
+ return SortedItemsView(self)
+
+
+ def values(self):
+ """Return new sorted values view of the sorted dict's values.
+
+ See :class:`SortedValuesView` for details.
+
+ :return: new sorted values view
+
+ """
+ return SortedValuesView(self)
+
+
+ if sys.hexversion < 0x03000000:
+ def __make_raise_attributeerror(original, alternate):
+ # pylint: disable=no-self-argument
+ message = (
+ 'SortedDict.{original}() is not implemented.'
+ ' Use SortedDict.{alternate}() instead.'
+ ).format(original=original, alternate=alternate)
+ def method(self):
+ # pylint: disable=missing-docstring,unused-argument
+ raise AttributeError(message)
+ method.__name__ = original # pylint: disable=non-str-assignment-to-dunder-name
+ method.__doc__ = message
+ return property(method)
+
+ iteritems = __make_raise_attributeerror('iteritems', 'items')
+ iterkeys = __make_raise_attributeerror('iterkeys', 'keys')
+ itervalues = __make_raise_attributeerror('itervalues', 'values')
+ viewitems = __make_raise_attributeerror('viewitems', 'items')
+ viewkeys = __make_raise_attributeerror('viewkeys', 'keys')
+ viewvalues = __make_raise_attributeerror('viewvalues', 'values')
+
+
+ class _NotGiven(object):
+ # pylint: disable=too-few-public-methods
+ def __repr__(self):
+ return '<not-given>'
+
+ __not_given = _NotGiven()
+
+ def pop(self, key, default=__not_given):
+ """Remove and return value for item identified by `key`.
+
+ If the `key` is not found then return `default` if given. If `default`
+ is not given then raise :exc:`KeyError`.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.pop('c')
+ 3
+ >>> sd.pop('z', 26)
+ 26
+ >>> sd.pop('y')
+ Traceback (most recent call last):
+ ...
+ KeyError: 'y'
+
+ :param key: `key` for item
+ :param default: `default` value if key not found (optional)
+ :return: value for item
+ :raises KeyError: if `key` not found and `default` not given
+
+ """
+ if key in self:
+ self._list_remove(key)
+ return dict.pop(self, key)
+ else:
+ if default is self.__not_given:
+ raise KeyError(key)
+ return default
+
+
+ def popitem(self, index=-1):
+ """Remove and return ``(key, value)`` pair at `index` from sorted dict.
+
+ Optional argument `index` defaults to -1, the last item in the sorted
+ dict. Specify ``index=0`` for the first item in the sorted dict.
+
+ If the sorted dict is empty, raises :exc:`KeyError`.
+
+ If the `index` is out of range, raises :exc:`IndexError`.
+
+ Runtime complexity: `O(log(n))`
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.popitem()
+ ('c', 3)
+ >>> sd.popitem(0)
+ ('a', 1)
+ >>> sd.popitem(100)
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ :param int index: `index` of item (default -1)
+ :return: key and value pair
+ :raises KeyError: if sorted dict is empty
+ :raises IndexError: if `index` out of range
+
+ """
+ if not self:
+ raise KeyError('popitem(): dictionary is empty')
+
+ key = self._list_pop(index)
+ value = dict.pop(self, key)
+ return (key, value)
+
+
+ def peekitem(self, index=-1):
+ """Return ``(key, value)`` pair at `index` in sorted dict.
+
+ Optional argument `index` defaults to -1, the last item in the sorted
+ dict. Specify ``index=0`` for the first item in the sorted dict.
+
+ Unlike :func:`SortedDict.popitem`, the sorted dict is not modified.
+
+ If the `index` is out of range, raises :exc:`IndexError`.
+
+ Runtime complexity: `O(log(n))`
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> sd.peekitem()
+ ('c', 3)
+ >>> sd.peekitem(0)
+ ('a', 1)
+ >>> sd.peekitem(100)
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ :param int index: index of item (default -1)
+ :return: key and value pair
+ :raises IndexError: if `index` out of range
+
+ """
+ key = self._list[index]
+ return key, self[key]
+
+
+ def setdefault(self, key, default=None):
+ """Return value for item identified by `key` in sorted dict.
+
+ If `key` is in the sorted dict then return its value. If `key` is not
+ in the sorted dict then insert `key` with value `default` and return
+ `default`.
+
+ Optional argument `default` defaults to none.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict()
+ >>> sd.setdefault('a', 1)
+ 1
+ >>> sd.setdefault('a', 10)
+ 1
+ >>> sd
+ SortedDict({'a': 1})
+
+ :param key: key for item
+ :param default: value for item (default None)
+ :return: value for item identified by `key`
+
+ """
+ if key in self:
+ return self[key]
+ dict.__setitem__(self, key, default)
+ self._list_add(key)
+ return default
+
+
+ def update(self, *args, **kwargs):
+ """Update sorted dict with items from `args` and `kwargs`.
+
+ Overwrites existing items.
+
+ Optional arguments `args` and `kwargs` may be a mapping, an iterable of
+ pairs or keyword arguments. See :func:`SortedDict.__init__` for
+ details.
+
+ :param args: mapping or iterable of pairs
+ :param kwargs: keyword arguments mapping
+
+ """
+ if not self:
+ dict.update(self, *args, **kwargs)
+ self._list_update(dict.__iter__(self))
+ return
+
+ if not kwargs and len(args) == 1 and isinstance(args[0], dict):
+ pairs = args[0]
+ else:
+ pairs = dict(*args, **kwargs)
+
+ if (10 * len(pairs)) > len(self):
+ dict.update(self, pairs)
+ self._list_clear()
+ self._list_update(dict.__iter__(self))
+ else:
+ for key in pairs:
+ self._setitem(key, pairs[key])
+
+ _update = update
+
+
+ def __reduce__(self):
+ """Support for pickle.
+
+ The tricks played with caching references in
+ :func:`SortedDict.__init__` confuse pickle so customize the reducer.
+
+ """
+ items = dict.copy(self)
+ return (type(self), (self._key, items))
+
+
+ @recursive_repr()
+ def __repr__(self):
+ """Return string representation of sorted dict.
+
+ ``sd.__repr__()`` <==> ``repr(sd)``
+
+ :return: string representation
+
+ """
+ _key = self._key
+ type_name = type(self).__name__
+ key_arg = '' if _key is None else '{0!r}, '.format(_key)
+ item_format = '{0!r}: {1!r}'.format
+ items = ', '.join(item_format(key, self[key]) for key in self._list)
+ return '{0}({1}{{{2}}})'.format(type_name, key_arg, items)
+
+
+ def _check(self):
+ """Check invariants of sorted dict.
+
+ Runtime complexity: `O(n)`
+
+ """
+ _list = self._list
+ _list._check()
+ assert len(self) == len(_list)
+ assert all(key in self for key in _list)
+
+
+def _view_delitem(self, index):
+ """Remove item at `index` from sorted dict.
+
+ ``view.__delitem__(index)`` <==> ``del view[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> view = sd.keys()
+ >>> del view[0]
+ >>> sd
+ SortedDict({'b': 2, 'c': 3})
+ >>> del view[-1]
+ >>> sd
+ SortedDict({'b': 2})
+ >>> del view[:]
+ >>> sd
+ SortedDict({})
+
+ :param index: integer or slice for indexing
+ :raises IndexError: if index out of range
+
+ """
+ _mapping = self._mapping
+ _list = _mapping._list
+ dict_delitem = dict.__delitem__
+ if isinstance(index, slice):
+ keys = _list[index]
+ del _list[index]
+ for key in keys:
+ dict_delitem(_mapping, key)
+ else:
+ key = _list.pop(index)
+ dict_delitem(_mapping, key)
+
+
+class SortedKeysView(KeysView, Sequence):
+ """Sorted keys view is a dynamic view of the sorted dict's keys.
+
+ When the sorted dict's keys change, the view reflects those changes.
+
+ The keys view implements the set and sequence abstract base classes.
+
+ """
+ __slots__ = ()
+
+
+ @classmethod
+ def _from_iterable(cls, it):
+ return SortedSet(it)
+
+
+ def __getitem__(self, index):
+ """Lookup key at `index` in sorted keys views.
+
+ ``skv.__getitem__(index)`` <==> ``skv[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> skv = sd.keys()
+ >>> skv[0]
+ 'a'
+ >>> skv[-1]
+ 'c'
+ >>> skv[:]
+ ['a', 'b', 'c']
+ >>> skv[100]
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ :param index: integer or slice for indexing
+ :return: key or list of keys
+ :raises IndexError: if index out of range
+
+ """
+ return self._mapping._list[index]
+
+
+ __delitem__ = _view_delitem
+
+
+class SortedItemsView(ItemsView, Sequence):
+ """Sorted items view is a dynamic view of the sorted dict's items.
+
+ When the sorted dict's items change, the view reflects those changes.
+
+ The items view implements the set and sequence abstract base classes.
+
+ """
+ __slots__ = ()
+
+
+ @classmethod
+ def _from_iterable(cls, it):
+ return SortedSet(it)
+
+
+ def __getitem__(self, index):
+ """Lookup item at `index` in sorted items view.
+
+ ``siv.__getitem__(index)`` <==> ``siv[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> siv = sd.items()
+ >>> siv[0]
+ ('a', 1)
+ >>> siv[-1]
+ ('c', 3)
+ >>> siv[:]
+ [('a', 1), ('b', 2), ('c', 3)]
+ >>> siv[100]
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ :param index: integer or slice for indexing
+ :return: item or list of items
+ :raises IndexError: if index out of range
+
+ """
+ _mapping = self._mapping
+ _mapping_list = _mapping._list
+
+ if isinstance(index, slice):
+ keys = _mapping_list[index]
+ return [(key, _mapping[key]) for key in keys]
+
+ key = _mapping_list[index]
+ return key, _mapping[key]
+
+
+ __delitem__ = _view_delitem
+
+
+class SortedValuesView(ValuesView, Sequence):
+ """Sorted values view is a dynamic view of the sorted dict's values.
+
+ When the sorted dict's values change, the view reflects those changes.
+
+ The values view implements the sequence abstract base class.
+
+ """
+ __slots__ = ()
+
+
+ def __getitem__(self, index):
+ """Lookup value at `index` in sorted values view.
+
+ ``siv.__getitem__(index)`` <==> ``siv[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sd = SortedDict({'a': 1, 'b': 2, 'c': 3})
+ >>> svv = sd.values()
+ >>> svv[0]
+ 1
+ >>> svv[-1]
+ 3
+ >>> svv[:]
+ [1, 2, 3]
+ >>> svv[100]
+ Traceback (most recent call last):
+ ...
+ IndexError: list index out of range
+
+ :param index: integer or slice for indexing
+ :return: value or list of values
+ :raises IndexError: if index out of range
+
+ """
+ _mapping = self._mapping
+ _mapping_list = _mapping._list
+
+ if isinstance(index, slice):
+ keys = _mapping_list[index]
+ return [_mapping[key] for key in keys]
+
+ key = _mapping_list[index]
+ return _mapping[key]
+
+
+ __delitem__ = _view_delitem
diff --git a/contrib/python/sortedcontainers/py3/sortedcontainers/sortedlist.py b/contrib/python/sortedcontainers/py3/sortedcontainers/sortedlist.py
new file mode 100644
index 0000000000..e3b58eb92d
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/sortedcontainers/sortedlist.py
@@ -0,0 +1,2646 @@
+"""Sorted List
+==============
+
+:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
+collections library, written in pure-Python, and fast as C-extensions. The
+:doc:`introduction<introduction>` is the best way to get started.
+
+Sorted list implementations:
+
+.. currentmodule:: sortedcontainers
+
+* :class:`SortedList`
+* :class:`SortedKeyList`
+
+"""
+# pylint: disable=too-many-lines
+from __future__ import print_function
+
+import sys
+import traceback
+
+from bisect import bisect_left, bisect_right, insort
+from itertools import chain, repeat, starmap
+from math import log
+from operator import add, eq, ne, gt, ge, lt, le, iadd
+from textwrap import dedent
+
+###############################################################################
+# BEGIN Python 2/3 Shims
+###############################################################################
+
+try:
+ from collections.abc import Sequence, MutableSequence
+except ImportError:
+ from collections import Sequence, MutableSequence
+
+from functools import wraps
+from sys import hexversion
+
+if hexversion < 0x03000000:
+ from itertools import imap as map # pylint: disable=redefined-builtin
+ from itertools import izip as zip # pylint: disable=redefined-builtin
+ try:
+ from thread import get_ident
+ except ImportError:
+ from dummy_thread import get_ident
+else:
+ from functools import reduce
+ try:
+ from _thread import get_ident
+ except ImportError:
+ from _dummy_thread import get_ident
+
+
+def recursive_repr(fillvalue='...'):
+ "Decorator to make a repr function return fillvalue for a recursive call."
+ # pylint: disable=missing-docstring
+ # Copied from reprlib in Python 3
+ # https://hg.python.org/cpython/file/3.6/Lib/reprlib.py
+
+ def decorating_function(user_function):
+ repr_running = set()
+
+ @wraps(user_function)
+ def wrapper(self):
+ key = id(self), get_ident()
+ if key in repr_running:
+ return fillvalue
+ repr_running.add(key)
+ try:
+ result = user_function(self)
+ finally:
+ repr_running.discard(key)
+ return result
+
+ return wrapper
+
+ return decorating_function
+
+###############################################################################
+# END Python 2/3 Shims
+###############################################################################
+
+
+class SortedList(MutableSequence):
+ """Sorted list is a sorted mutable sequence.
+
+ Sorted list values are maintained in sorted order.
+
+ Sorted list values must be comparable. The total ordering of values must
+ not change while they are stored in the sorted list.
+
+ Methods for adding values:
+
+ * :func:`SortedList.add`
+ * :func:`SortedList.update`
+ * :func:`SortedList.__add__`
+ * :func:`SortedList.__iadd__`
+ * :func:`SortedList.__mul__`
+ * :func:`SortedList.__imul__`
+
+ Methods for removing values:
+
+ * :func:`SortedList.clear`
+ * :func:`SortedList.discard`
+ * :func:`SortedList.remove`
+ * :func:`SortedList.pop`
+ * :func:`SortedList.__delitem__`
+
+ Methods for looking up values:
+
+ * :func:`SortedList.bisect_left`
+ * :func:`SortedList.bisect_right`
+ * :func:`SortedList.count`
+ * :func:`SortedList.index`
+ * :func:`SortedList.__contains__`
+ * :func:`SortedList.__getitem__`
+
+ Methods for iterating values:
+
+ * :func:`SortedList.irange`
+ * :func:`SortedList.islice`
+ * :func:`SortedList.__iter__`
+ * :func:`SortedList.__reversed__`
+
+ Methods for miscellany:
+
+ * :func:`SortedList.copy`
+ * :func:`SortedList.__len__`
+ * :func:`SortedList.__repr__`
+ * :func:`SortedList._check`
+ * :func:`SortedList._reset`
+
+ Sorted lists use lexicographical ordering semantics when compared to other
+ sequences.
+
+ Some methods of mutable sequences are not supported and will raise
+ not-implemented error.
+
+ """
+ DEFAULT_LOAD_FACTOR = 1000
+
+
+ def __init__(self, iterable=None, key=None):
+ """Initialize sorted list instance.
+
+ Optional `iterable` argument provides an initial iterable of values to
+ initialize the sorted list.
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> sl = SortedList()
+ >>> sl
+ SortedList([])
+ >>> sl = SortedList([3, 1, 2, 5, 4])
+ >>> sl
+ SortedList([1, 2, 3, 4, 5])
+
+ :param iterable: initial values (optional)
+
+ """
+ assert key is None
+ self._len = 0
+ self._load = self.DEFAULT_LOAD_FACTOR
+ self._lists = []
+ self._maxes = []
+ self._index = []
+ self._offset = 0
+
+ if iterable is not None:
+ self._update(iterable)
+
+
+ def __new__(cls, iterable=None, key=None):
+ """Create new sorted list or sorted-key list instance.
+
+ Optional `key`-function argument will return an instance of subtype
+ :class:`SortedKeyList`.
+
+ >>> sl = SortedList()
+ >>> isinstance(sl, SortedList)
+ True
+ >>> sl = SortedList(key=lambda x: -x)
+ >>> isinstance(sl, SortedList)
+ True
+ >>> isinstance(sl, SortedKeyList)
+ True
+
+ :param iterable: initial values (optional)
+ :param key: function used to extract comparison key (optional)
+ :return: sorted list or sorted-key list instance
+
+ """
+ # pylint: disable=unused-argument
+ if key is None:
+ return object.__new__(cls)
+ else:
+ if cls is SortedList:
+ return object.__new__(SortedKeyList)
+ else:
+ raise TypeError('inherit SortedKeyList for key argument')
+
+
+ @property
+ def key(self): # pylint: disable=useless-return
+ """Function used to extract comparison key from values.
+
+ Sorted list compares values directly so the key function is none.
+
+ """
+ return None
+
+
+ def _reset(self, load):
+ """Reset sorted list load factor.
+
+ The `load` specifies the load-factor of the list. The default load
+ factor of 1000 works well for lists from tens to tens-of-millions of
+ values. Good practice is to use a value that is the cube root of the
+ list size. With billions of elements, the best load factor depends on
+ your usage. It's best to leave the load factor at the default until you
+ start benchmarking.
+
+ See :doc:`implementation` and :doc:`performance-scale` for more
+ information.
+
+ Runtime complexity: `O(n)`
+
+ :param int load: load-factor for sorted list sublists
+
+ """
+ values = reduce(iadd, self._lists, [])
+ self._clear()
+ self._load = load
+ self._update(values)
+
+
+ def clear(self):
+ """Remove all values from sorted list.
+
+ Runtime complexity: `O(n)`
+
+ """
+ self._len = 0
+ del self._lists[:]
+ del self._maxes[:]
+ del self._index[:]
+ self._offset = 0
+
+ _clear = clear
+
+
+ def add(self, value):
+ """Add `value` to sorted list.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList()
+ >>> sl.add(3)
+ >>> sl.add(1)
+ >>> sl.add(2)
+ >>> sl
+ SortedList([1, 2, 3])
+
+ :param value: value to add to sorted list
+
+ """
+ _lists = self._lists
+ _maxes = self._maxes
+
+ if _maxes:
+ pos = bisect_right(_maxes, value)
+
+ if pos == len(_maxes):
+ pos -= 1
+ _lists[pos].append(value)
+ _maxes[pos] = value
+ else:
+ insort(_lists[pos], value)
+
+ self._expand(pos)
+ else:
+ _lists.append([value])
+ _maxes.append(value)
+
+ self._len += 1
+
+
+ def _expand(self, pos):
+ """Split sublists with length greater than double the load-factor.
+
+ Updates the index when the sublist length is less than double the load
+ level. This requires incrementing the nodes in a traversal from the
+ leaf node to the root. For an example traversal see
+ ``SortedList._loc``.
+
+ """
+ _load = self._load
+ _lists = self._lists
+ _index = self._index
+
+ if len(_lists[pos]) > (_load << 1):
+ _maxes = self._maxes
+
+ _lists_pos = _lists[pos]
+ half = _lists_pos[_load:]
+ del _lists_pos[_load:]
+ _maxes[pos] = _lists_pos[-1]
+
+ _lists.insert(pos + 1, half)
+ _maxes.insert(pos + 1, half[-1])
+
+ del _index[:]
+ else:
+ if _index:
+ child = self._offset + pos
+ while child:
+ _index[child] += 1
+ child = (child - 1) >> 1
+ _index[0] += 1
+
+
+ def update(self, iterable):
+ """Update sorted list by adding all values from `iterable`.
+
+ Runtime complexity: `O(k*log(n))` -- approximate.
+
+ >>> sl = SortedList()
+ >>> sl.update([3, 1, 2])
+ >>> sl
+ SortedList([1, 2, 3])
+
+ :param iterable: iterable of values to add
+
+ """
+ _lists = self._lists
+ _maxes = self._maxes
+ values = sorted(iterable)
+
+ if _maxes:
+ if len(values) * 4 >= self._len:
+ _lists.append(values)
+ values = reduce(iadd, _lists, [])
+ values.sort()
+ self._clear()
+ else:
+ _add = self.add
+ for val in values:
+ _add(val)
+ return
+
+ _load = self._load
+ _lists.extend(values[pos:(pos + _load)]
+ for pos in range(0, len(values), _load))
+ _maxes.extend(sublist[-1] for sublist in _lists)
+ self._len = len(values)
+ del self._index[:]
+
+ _update = update
+
+
+ def __contains__(self, value):
+ """Return true if `value` is an element of the sorted list.
+
+ ``sl.__contains__(value)`` <==> ``value in sl``
+
+ Runtime complexity: `O(log(n))`
+
+ >>> sl = SortedList([1, 2, 3, 4, 5])
+ >>> 3 in sl
+ True
+
+ :param value: search for value in sorted list
+ :return: true if `value` in sorted list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return False
+
+ pos = bisect_left(_maxes, value)
+
+ if pos == len(_maxes):
+ return False
+
+ _lists = self._lists
+ idx = bisect_left(_lists[pos], value)
+
+ return _lists[pos][idx] == value
+
+
+ def discard(self, value):
+ """Remove `value` from sorted list if it is a member.
+
+ If `value` is not a member, do nothing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList([1, 2, 3, 4, 5])
+ >>> sl.discard(5)
+ >>> sl.discard(0)
+ >>> sl == [1, 2, 3, 4]
+ True
+
+ :param value: `value` to discard from sorted list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return
+
+ pos = bisect_left(_maxes, value)
+
+ if pos == len(_maxes):
+ return
+
+ _lists = self._lists
+ idx = bisect_left(_lists[pos], value)
+
+ if _lists[pos][idx] == value:
+ self._delete(pos, idx)
+
+
+ def remove(self, value):
+ """Remove `value` from sorted list; `value` must be a member.
+
+ If `value` is not a member, raise ValueError.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList([1, 2, 3, 4, 5])
+ >>> sl.remove(5)
+ >>> sl == [1, 2, 3, 4]
+ True
+ >>> sl.remove(0)
+ Traceback (most recent call last):
+ ...
+ ValueError: 0 not in list
+
+ :param value: `value` to remove from sorted list
+ :raises ValueError: if `value` is not in sorted list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ raise ValueError('{0!r} not in list'.format(value))
+
+ pos = bisect_left(_maxes, value)
+
+ if pos == len(_maxes):
+ raise ValueError('{0!r} not in list'.format(value))
+
+ _lists = self._lists
+ idx = bisect_left(_lists[pos], value)
+
+ if _lists[pos][idx] == value:
+ self._delete(pos, idx)
+ else:
+ raise ValueError('{0!r} not in list'.format(value))
+
+
+ def _delete(self, pos, idx):
+ """Delete value at the given `(pos, idx)`.
+
+ Combines lists that are less than half the load level.
+
+ Updates the index when the sublist length is more than half the load
+ level. This requires decrementing the nodes in a traversal from the
+ leaf node to the root. For an example traversal see
+ ``SortedList._loc``.
+
+ :param int pos: lists index
+ :param int idx: sublist index
+
+ """
+ _lists = self._lists
+ _maxes = self._maxes
+ _index = self._index
+
+ _lists_pos = _lists[pos]
+
+ del _lists_pos[idx]
+ self._len -= 1
+
+ len_lists_pos = len(_lists_pos)
+
+ if len_lists_pos > (self._load >> 1):
+ _maxes[pos] = _lists_pos[-1]
+
+ if _index:
+ child = self._offset + pos
+ while child > 0:
+ _index[child] -= 1
+ child = (child - 1) >> 1
+ _index[0] -= 1
+ elif len(_lists) > 1:
+ if not pos:
+ pos += 1
+
+ prev = pos - 1
+ _lists[prev].extend(_lists[pos])
+ _maxes[prev] = _lists[prev][-1]
+
+ del _lists[pos]
+ del _maxes[pos]
+ del _index[:]
+
+ self._expand(prev)
+ elif len_lists_pos:
+ _maxes[pos] = _lists_pos[-1]
+ else:
+ del _lists[pos]
+ del _maxes[pos]
+ del _index[:]
+
+
+ def _loc(self, pos, idx):
+ """Convert an index pair (lists index, sublist index) into a single
+ index number that corresponds to the position of the value in the
+ sorted list.
+
+ Many queries require the index be built. Details of the index are
+ described in ``SortedList._build_index``.
+
+ Indexing requires traversing the tree from a leaf node to the root. The
+ parent of each node is easily computable at ``(pos - 1) // 2``.
+
+ Left-child nodes are always at odd indices and right-child nodes are
+ always at even indices.
+
+ When traversing up from a right-child node, increment the total by the
+ left-child node.
+
+ The final index is the sum from traversal and the index in the sublist.
+
+ For example, using the index from ``SortedList._build_index``::
+
+ _index = 14 5 9 3 2 4 5
+ _offset = 3
+
+ Tree::
+
+ 14
+ 5 9
+ 3 2 4 5
+
+ Converting an index pair (2, 3) into a single index involves iterating
+ like so:
+
+ 1. Starting at the leaf node: offset + alpha = 3 + 2 = 5. We identify
+ the node as a left-child node. At such nodes, we simply traverse to
+ the parent.
+
+ 2. At node 9, position 2, we recognize the node as a right-child node
+ and accumulate the left-child in our total. Total is now 5 and we
+ traverse to the parent at position 0.
+
+ 3. Iteration ends at the root.
+
+ The index is then the sum of the total and sublist index: 5 + 3 = 8.
+
+ :param int pos: lists index
+ :param int idx: sublist index
+ :return: index in sorted list
+
+ """
+ if not pos:
+ return idx
+
+ _index = self._index
+
+ if not _index:
+ self._build_index()
+
+ total = 0
+
+ # Increment pos to point in the index to len(self._lists[pos]).
+
+ pos += self._offset
+
+ # Iterate until reaching the root of the index tree at pos = 0.
+
+ while pos:
+
+ # Right-child nodes are at odd indices. At such indices
+ # account the total below the left child node.
+
+ if not pos & 1:
+ total += _index[pos - 1]
+
+ # Advance pos to the parent node.
+
+ pos = (pos - 1) >> 1
+
+ return total + idx
+
+
+ def _pos(self, idx):
+ """Convert an index into an index pair (lists index, sublist index)
+ that can be used to access the corresponding lists position.
+
+ Many queries require the index be built. Details of the index are
+ described in ``SortedList._build_index``.
+
+ Indexing requires traversing the tree to a leaf node. Each node has two
+ children which are easily computable. Given an index, pos, the
+ left-child is at ``pos * 2 + 1`` and the right-child is at ``pos * 2 +
+ 2``.
+
+ When the index is less than the left-child, traversal moves to the
+ left sub-tree. Otherwise, the index is decremented by the left-child
+ and traversal moves to the right sub-tree.
+
+ At a child node, the indexing pair is computed from the relative
+ position of the child node as compared with the offset and the remaining
+ index.
+
+ For example, using the index from ``SortedList._build_index``::
+
+ _index = 14 5 9 3 2 4 5
+ _offset = 3
+
+ Tree::
+
+ 14
+ 5 9
+ 3 2 4 5
+
+ Indexing position 8 involves iterating like so:
+
+ 1. Starting at the root, position 0, 8 is compared with the left-child
+ node (5) which it is greater than. When greater the index is
+ decremented and the position is updated to the right child node.
+
+ 2. At node 9 with index 3, we again compare the index to the left-child
+ node with value 4. Because the index is the less than the left-child
+ node, we simply traverse to the left.
+
+ 3. At node 4 with index 3, we recognize that we are at a leaf node and
+ stop iterating.
+
+ 4. To compute the sublist index, we subtract the offset from the index
+ of the leaf node: 5 - 3 = 2. To compute the index in the sublist, we
+ simply use the index remaining from iteration. In this case, 3.
+
+ The final index pair from our example is (2, 3) which corresponds to
+ index 8 in the sorted list.
+
+ :param int idx: index in sorted list
+ :return: (lists index, sublist index) pair
+
+ """
+ if idx < 0:
+ last_len = len(self._lists[-1])
+
+ if (-idx) <= last_len:
+ return len(self._lists) - 1, last_len + idx
+
+ idx += self._len
+
+ if idx < 0:
+ raise IndexError('list index out of range')
+ elif idx >= self._len:
+ raise IndexError('list index out of range')
+
+ if idx < len(self._lists[0]):
+ return 0, idx
+
+ _index = self._index
+
+ if not _index:
+ self._build_index()
+
+ pos = 0
+ child = 1
+ len_index = len(_index)
+
+ while child < len_index:
+ index_child = _index[child]
+
+ if idx < index_child:
+ pos = child
+ else:
+ idx -= index_child
+ pos = child + 1
+
+ child = (pos << 1) + 1
+
+ return (pos - self._offset, idx)
+
+
+ def _build_index(self):
+ """Build a positional index for indexing the sorted list.
+
+ Indexes are represented as binary trees in a dense array notation
+ similar to a binary heap.
+
+ For example, given a lists representation storing integers::
+
+ 0: [1, 2, 3]
+ 1: [4, 5]
+ 2: [6, 7, 8, 9]
+ 3: [10, 11, 12, 13, 14]
+
+ The first transformation maps the sub-lists by their length. The
+ first row of the index is the length of the sub-lists::
+
+ 0: [3, 2, 4, 5]
+
+ Each row after that is the sum of consecutive pairs of the previous
+ row::
+
+ 1: [5, 9]
+ 2: [14]
+
+ Finally, the index is built by concatenating these lists together::
+
+ _index = [14, 5, 9, 3, 2, 4, 5]
+
+ An offset storing the start of the first row is also stored::
+
+ _offset = 3
+
+ When built, the index can be used for efficient indexing into the list.
+ See the comment and notes on ``SortedList._pos`` for details.
+
+ """
+ row0 = list(map(len, self._lists))
+
+ if len(row0) == 1:
+ self._index[:] = row0
+ self._offset = 0
+ return
+
+ head = iter(row0)
+ tail = iter(head)
+ row1 = list(starmap(add, zip(head, tail)))
+
+ if len(row0) & 1:
+ row1.append(row0[-1])
+
+ if len(row1) == 1:
+ self._index[:] = row1 + row0
+ self._offset = 1
+ return
+
+ size = 2 ** (int(log(len(row1) - 1, 2)) + 1)
+ row1.extend(repeat(0, size - len(row1)))
+ tree = [row0, row1]
+
+ while len(tree[-1]) > 1:
+ head = iter(tree[-1])
+ tail = iter(head)
+ row = list(starmap(add, zip(head, tail)))
+ tree.append(row)
+
+ reduce(iadd, reversed(tree), self._index)
+ self._offset = size * 2 - 1
+
+
+ def __delitem__(self, index):
+ """Remove value at `index` from sorted list.
+
+ ``sl.__delitem__(index)`` <==> ``del sl[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList('abcde')
+ >>> del sl[2]
+ >>> sl
+ SortedList(['a', 'b', 'd', 'e'])
+ >>> del sl[:2]
+ >>> sl
+ SortedList(['d', 'e'])
+
+ :param index: integer or slice for indexing
+ :raises IndexError: if index out of range
+
+ """
+ if isinstance(index, slice):
+ start, stop, step = index.indices(self._len)
+
+ if step == 1 and start < stop:
+ if start == 0 and stop == self._len:
+ return self._clear()
+ elif self._len <= 8 * (stop - start):
+ values = self._getitem(slice(None, start))
+ if stop < self._len:
+ values += self._getitem(slice(stop, None))
+ self._clear()
+ return self._update(values)
+
+ indices = range(start, stop, step)
+
+ # Delete items from greatest index to least so
+ # that the indices remain valid throughout iteration.
+
+ if step > 0:
+ indices = reversed(indices)
+
+ _pos, _delete = self._pos, self._delete
+
+ for index in indices:
+ pos, idx = _pos(index)
+ _delete(pos, idx)
+ else:
+ pos, idx = self._pos(index)
+ self._delete(pos, idx)
+
+
+ def __getitem__(self, index):
+ """Lookup value at `index` in sorted list.
+
+ ``sl.__getitem__(index)`` <==> ``sl[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList('abcde')
+ >>> sl[1]
+ 'b'
+ >>> sl[-1]
+ 'e'
+ >>> sl[2:5]
+ ['c', 'd', 'e']
+
+ :param index: integer or slice for indexing
+ :return: value or list of values
+ :raises IndexError: if index out of range
+
+ """
+ _lists = self._lists
+
+ if isinstance(index, slice):
+ start, stop, step = index.indices(self._len)
+
+ if step == 1 and start < stop:
+ # Whole slice optimization: start to stop slices the whole
+ # sorted list.
+
+ if start == 0 and stop == self._len:
+ return reduce(iadd, self._lists, [])
+
+ start_pos, start_idx = self._pos(start)
+ start_list = _lists[start_pos]
+ stop_idx = start_idx + stop - start
+
+ # Small slice optimization: start index and stop index are
+ # within the start list.
+
+ if len(start_list) >= stop_idx:
+ return start_list[start_idx:stop_idx]
+
+ if stop == self._len:
+ stop_pos = len(_lists) - 1
+ stop_idx = len(_lists[stop_pos])
+ else:
+ stop_pos, stop_idx = self._pos(stop)
+
+ prefix = _lists[start_pos][start_idx:]
+ middle = _lists[(start_pos + 1):stop_pos]
+ result = reduce(iadd, middle, prefix)
+ result += _lists[stop_pos][:stop_idx]
+
+ return result
+
+ if step == -1 and start > stop:
+ result = self._getitem(slice(stop + 1, start + 1))
+ result.reverse()
+ return result
+
+ # Return a list because a negative step could
+ # reverse the order of the items and this could
+ # be the desired behavior.
+
+ indices = range(start, stop, step)
+ return list(self._getitem(index) for index in indices)
+ else:
+ if self._len:
+ if index == 0:
+ return _lists[0][0]
+ elif index == -1:
+ return _lists[-1][-1]
+ else:
+ raise IndexError('list index out of range')
+
+ if 0 <= index < len(_lists[0]):
+ return _lists[0][index]
+
+ len_last = len(_lists[-1])
+
+ if -len_last < index < 0:
+ return _lists[-1][len_last + index]
+
+ pos, idx = self._pos(index)
+ return _lists[pos][idx]
+
+ _getitem = __getitem__
+
+
+ def __setitem__(self, index, value):
+ """Raise not-implemented error.
+
+ ``sl.__setitem__(index, value)`` <==> ``sl[index] = value``
+
+ :raises NotImplementedError: use ``del sl[index]`` and
+ ``sl.add(value)`` instead
+
+ """
+ message = 'use ``del sl[index]`` and ``sl.add(value)`` instead'
+ raise NotImplementedError(message)
+
+
+ def __iter__(self):
+ """Return an iterator over the sorted list.
+
+ ``sl.__iter__()`` <==> ``iter(sl)``
+
+ Iterating the sorted list while adding or deleting values may raise a
+ :exc:`RuntimeError` or fail to iterate over all values.
+
+ """
+ return chain.from_iterable(self._lists)
+
+
+ def __reversed__(self):
+ """Return a reverse iterator over the sorted list.
+
+ ``sl.__reversed__()`` <==> ``reversed(sl)``
+
+ Iterating the sorted list while adding or deleting values may raise a
+ :exc:`RuntimeError` or fail to iterate over all values.
+
+ """
+ return chain.from_iterable(map(reversed, reversed(self._lists)))
+
+
+ def reverse(self):
+ """Raise not-implemented error.
+
+ Sorted list maintains values in ascending sort order. Values may not be
+ reversed in-place.
+
+ Use ``reversed(sl)`` for an iterator over values in descending sort
+ order.
+
+ Implemented to override `MutableSequence.reverse` which provides an
+ erroneous default implementation.
+
+ :raises NotImplementedError: use ``reversed(sl)`` instead
+
+ """
+ raise NotImplementedError('use ``reversed(sl)`` instead')
+
+
+ def islice(self, start=None, stop=None, reverse=False):
+ """Return an iterator that slices sorted list from `start` to `stop`.
+
+ The `start` and `stop` index are treated inclusive and exclusive,
+ respectively.
+
+ Both `start` and `stop` default to `None` which is automatically
+ inclusive of the beginning and end of the sorted list.
+
+ When `reverse` is `True` the values are yielded from the iterator in
+ reverse order; `reverse` defaults to `False`.
+
+ >>> sl = SortedList('abcdefghij')
+ >>> it = sl.islice(2, 6)
+ >>> list(it)
+ ['c', 'd', 'e', 'f']
+
+ :param int start: start index (inclusive)
+ :param int stop: stop index (exclusive)
+ :param bool reverse: yield values in reverse order
+ :return: iterator
+
+ """
+ _len = self._len
+
+ if not _len:
+ return iter(())
+
+ start, stop, _ = slice(start, stop).indices(self._len)
+
+ if start >= stop:
+ return iter(())
+
+ _pos = self._pos
+
+ min_pos, min_idx = _pos(start)
+
+ if stop == _len:
+ max_pos = len(self._lists) - 1
+ max_idx = len(self._lists[-1])
+ else:
+ max_pos, max_idx = _pos(stop)
+
+ return self._islice(min_pos, min_idx, max_pos, max_idx, reverse)
+
+
+ def _islice(self, min_pos, min_idx, max_pos, max_idx, reverse):
+ """Return an iterator that slices sorted list using two index pairs.
+
+ The index pairs are (min_pos, min_idx) and (max_pos, max_idx), the
+ first inclusive and the latter exclusive. See `_pos` for details on how
+ an index is converted to an index pair.
+
+ When `reverse` is `True`, values are yielded from the iterator in
+ reverse order.
+
+ """
+ _lists = self._lists
+
+ if min_pos > max_pos:
+ return iter(())
+
+ if min_pos == max_pos:
+ if reverse:
+ indices = reversed(range(min_idx, max_idx))
+ return map(_lists[min_pos].__getitem__, indices)
+
+ indices = range(min_idx, max_idx)
+ return map(_lists[min_pos].__getitem__, indices)
+
+ next_pos = min_pos + 1
+
+ if next_pos == max_pos:
+ if reverse:
+ min_indices = range(min_idx, len(_lists[min_pos]))
+ max_indices = range(max_idx)
+ return chain(
+ map(_lists[max_pos].__getitem__, reversed(max_indices)),
+ map(_lists[min_pos].__getitem__, reversed(min_indices)),
+ )
+
+ min_indices = range(min_idx, len(_lists[min_pos]))
+ max_indices = range(max_idx)
+ return chain(
+ map(_lists[min_pos].__getitem__, min_indices),
+ map(_lists[max_pos].__getitem__, max_indices),
+ )
+
+ if reverse:
+ min_indices = range(min_idx, len(_lists[min_pos]))
+ sublist_indices = range(next_pos, max_pos)
+ sublists = map(_lists.__getitem__, reversed(sublist_indices))
+ max_indices = range(max_idx)
+ return chain(
+ map(_lists[max_pos].__getitem__, reversed(max_indices)),
+ chain.from_iterable(map(reversed, sublists)),
+ map(_lists[min_pos].__getitem__, reversed(min_indices)),
+ )
+
+ min_indices = range(min_idx, len(_lists[min_pos]))
+ sublist_indices = range(next_pos, max_pos)
+ sublists = map(_lists.__getitem__, sublist_indices)
+ max_indices = range(max_idx)
+ return chain(
+ map(_lists[min_pos].__getitem__, min_indices),
+ chain.from_iterable(sublists),
+ map(_lists[max_pos].__getitem__, max_indices),
+ )
+
+
+ def irange(self, minimum=None, maximum=None, inclusive=(True, True),
+ reverse=False):
+ """Create an iterator of values between `minimum` and `maximum`.
+
+ Both `minimum` and `maximum` default to `None` which is automatically
+ inclusive of the beginning and end of the sorted list.
+
+ The argument `inclusive` is a pair of booleans that indicates whether
+ the minimum and maximum ought to be included in the range,
+ respectively. The default is ``(True, True)`` such that the range is
+ inclusive of both minimum and maximum.
+
+ When `reverse` is `True` the values are yielded from the iterator in
+ reverse order; `reverse` defaults to `False`.
+
+ >>> sl = SortedList('abcdefghij')
+ >>> it = sl.irange('c', 'f')
+ >>> list(it)
+ ['c', 'd', 'e', 'f']
+
+ :param minimum: minimum value to start iterating
+ :param maximum: maximum value to stop iterating
+ :param inclusive: pair of booleans
+ :param bool reverse: yield values in reverse order
+ :return: iterator
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return iter(())
+
+ _lists = self._lists
+
+ # Calculate the minimum (pos, idx) pair. By default this location
+ # will be inclusive in our calculation.
+
+ if minimum is None:
+ min_pos = 0
+ min_idx = 0
+ else:
+ if inclusive[0]:
+ min_pos = bisect_left(_maxes, minimum)
+
+ if min_pos == len(_maxes):
+ return iter(())
+
+ min_idx = bisect_left(_lists[min_pos], minimum)
+ else:
+ min_pos = bisect_right(_maxes, minimum)
+
+ if min_pos == len(_maxes):
+ return iter(())
+
+ min_idx = bisect_right(_lists[min_pos], minimum)
+
+ # Calculate the maximum (pos, idx) pair. By default this location
+ # will be exclusive in our calculation.
+
+ if maximum is None:
+ max_pos = len(_maxes) - 1
+ max_idx = len(_lists[max_pos])
+ else:
+ if inclusive[1]:
+ max_pos = bisect_right(_maxes, maximum)
+
+ if max_pos == len(_maxes):
+ max_pos -= 1
+ max_idx = len(_lists[max_pos])
+ else:
+ max_idx = bisect_right(_lists[max_pos], maximum)
+ else:
+ max_pos = bisect_left(_maxes, maximum)
+
+ if max_pos == len(_maxes):
+ max_pos -= 1
+ max_idx = len(_lists[max_pos])
+ else:
+ max_idx = bisect_left(_lists[max_pos], maximum)
+
+ return self._islice(min_pos, min_idx, max_pos, max_idx, reverse)
+
+
+ def __len__(self):
+ """Return the size of the sorted list.
+
+ ``sl.__len__()`` <==> ``len(sl)``
+
+ :return: size of sorted list
+
+ """
+ return self._len
+
+
+ def bisect_left(self, value):
+ """Return an index to insert `value` in the sorted list.
+
+ If the `value` is already present, the insertion point will be before
+ (to the left of) any existing values.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList([10, 11, 12, 13, 14])
+ >>> sl.bisect_left(12)
+ 2
+
+ :param value: insertion index of value in sorted list
+ :return: index
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ pos = bisect_left(_maxes, value)
+
+ if pos == len(_maxes):
+ return self._len
+
+ idx = bisect_left(self._lists[pos], value)
+ return self._loc(pos, idx)
+
+
+ def bisect_right(self, value):
+ """Return an index to insert `value` in the sorted list.
+
+ Similar to `bisect_left`, but if `value` is already present, the
+ insertion point will be after (to the right of) any existing values.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList([10, 11, 12, 13, 14])
+ >>> sl.bisect_right(12)
+ 3
+
+ :param value: insertion index of value in sorted list
+ :return: index
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ pos = bisect_right(_maxes, value)
+
+ if pos == len(_maxes):
+ return self._len
+
+ idx = bisect_right(self._lists[pos], value)
+ return self._loc(pos, idx)
+
+ bisect = bisect_right
+ _bisect_right = bisect_right
+
+
+ def count(self, value):
+ """Return number of occurrences of `value` in the sorted list.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
+ >>> sl.count(3)
+ 3
+
+ :param value: value to count in sorted list
+ :return: count
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ pos_left = bisect_left(_maxes, value)
+
+ if pos_left == len(_maxes):
+ return 0
+
+ _lists = self._lists
+ idx_left = bisect_left(_lists[pos_left], value)
+ pos_right = bisect_right(_maxes, value)
+
+ if pos_right == len(_maxes):
+ return self._len - self._loc(pos_left, idx_left)
+
+ idx_right = bisect_right(_lists[pos_right], value)
+
+ if pos_left == pos_right:
+ return idx_right - idx_left
+
+ right = self._loc(pos_right, idx_right)
+ left = self._loc(pos_left, idx_left)
+ return right - left
+
+
+ def copy(self):
+ """Return a shallow copy of the sorted list.
+
+ Runtime complexity: `O(n)`
+
+ :return: new sorted list
+
+ """
+ return self.__class__(self)
+
+ __copy__ = copy
+
+
+ def append(self, value):
+ """Raise not-implemented error.
+
+ Implemented to override `MutableSequence.append` which provides an
+ erroneous default implementation.
+
+ :raises NotImplementedError: use ``sl.add(value)`` instead
+
+ """
+ raise NotImplementedError('use ``sl.add(value)`` instead')
+
+
+ def extend(self, values):
+ """Raise not-implemented error.
+
+ Implemented to override `MutableSequence.extend` which provides an
+ erroneous default implementation.
+
+ :raises NotImplementedError: use ``sl.update(values)`` instead
+
+ """
+ raise NotImplementedError('use ``sl.update(values)`` instead')
+
+
+ def insert(self, index, value):
+ """Raise not-implemented error.
+
+ :raises NotImplementedError: use ``sl.add(value)`` instead
+
+ """
+ raise NotImplementedError('use ``sl.add(value)`` instead')
+
+
+ def pop(self, index=-1):
+ """Remove and return value at `index` in sorted list.
+
+ Raise :exc:`IndexError` if the sorted list is empty or index is out of
+ range.
+
+ Negative indices are supported.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList('abcde')
+ >>> sl.pop()
+ 'e'
+ >>> sl.pop(2)
+ 'c'
+ >>> sl
+ SortedList(['a', 'b', 'd'])
+
+ :param int index: index of value (default -1)
+ :return: value
+ :raises IndexError: if index is out of range
+
+ """
+ if not self._len:
+ raise IndexError('pop index out of range')
+
+ _lists = self._lists
+
+ if index == 0:
+ val = _lists[0][0]
+ self._delete(0, 0)
+ return val
+
+ if index == -1:
+ pos = len(_lists) - 1
+ loc = len(_lists[pos]) - 1
+ val = _lists[pos][loc]
+ self._delete(pos, loc)
+ return val
+
+ if 0 <= index < len(_lists[0]):
+ val = _lists[0][index]
+ self._delete(0, index)
+ return val
+
+ len_last = len(_lists[-1])
+
+ if -len_last < index < 0:
+ pos = len(_lists) - 1
+ loc = len_last + index
+ val = _lists[pos][loc]
+ self._delete(pos, loc)
+ return val
+
+ pos, idx = self._pos(index)
+ val = _lists[pos][idx]
+ self._delete(pos, idx)
+ return val
+
+
+ def index(self, value, start=None, stop=None):
+ """Return first index of value in sorted list.
+
+ Raise ValueError if `value` is not present.
+
+ Index must be between `start` and `stop` for the `value` to be
+ considered present. The default value, None, for `start` and `stop`
+ indicate the beginning and end of the sorted list.
+
+ Negative indices are supported.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> sl = SortedList('abcde')
+ >>> sl.index('d')
+ 3
+ >>> sl.index('z')
+ Traceback (most recent call last):
+ ...
+ ValueError: 'z' is not in list
+
+ :param value: value in sorted list
+ :param int start: start index (default None, start of sorted list)
+ :param int stop: stop index (default None, end of sorted list)
+ :return: index of value
+ :raises ValueError: if value is not present
+
+ """
+ _len = self._len
+
+ if not _len:
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ if start is None:
+ start = 0
+ if start < 0:
+ start += _len
+ if start < 0:
+ start = 0
+
+ if stop is None:
+ stop = _len
+ if stop < 0:
+ stop += _len
+ if stop > _len:
+ stop = _len
+
+ if stop <= start:
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ _maxes = self._maxes
+ pos_left = bisect_left(_maxes, value)
+
+ if pos_left == len(_maxes):
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ _lists = self._lists
+ idx_left = bisect_left(_lists[pos_left], value)
+
+ if _lists[pos_left][idx_left] != value:
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ stop -= 1
+ left = self._loc(pos_left, idx_left)
+
+ if start <= left:
+ if left <= stop:
+ return left
+ else:
+ right = self._bisect_right(value) - 1
+
+ if start <= right:
+ return start
+
+ raise ValueError('{0!r} is not in list'.format(value))
+
+
+ def __add__(self, other):
+ """Return new sorted list containing all values in both sequences.
+
+ ``sl.__add__(other)`` <==> ``sl + other``
+
+ Values in `other` do not need to be in sorted order.
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> sl1 = SortedList('bat')
+ >>> sl2 = SortedList('cat')
+ >>> sl1 + sl2
+ SortedList(['a', 'a', 'b', 'c', 't', 't'])
+
+ :param other: other iterable
+ :return: new sorted list
+
+ """
+ values = reduce(iadd, self._lists, [])
+ values.extend(other)
+ return self.__class__(values)
+
+ __radd__ = __add__
+
+
+ def __iadd__(self, other):
+ """Update sorted list with values from `other`.
+
+ ``sl.__iadd__(other)`` <==> ``sl += other``
+
+ Values in `other` do not need to be in sorted order.
+
+ Runtime complexity: `O(k*log(n))` -- approximate.
+
+ >>> sl = SortedList('bat')
+ >>> sl += 'cat'
+ >>> sl
+ SortedList(['a', 'a', 'b', 'c', 't', 't'])
+
+ :param other: other iterable
+ :return: existing sorted list
+
+ """
+ self._update(other)
+ return self
+
+
+ def __mul__(self, num):
+ """Return new sorted list with `num` shallow copies of values.
+
+ ``sl.__mul__(num)`` <==> ``sl * num``
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> sl = SortedList('abc')
+ >>> sl * 3
+ SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'])
+
+ :param int num: count of shallow copies
+ :return: new sorted list
+
+ """
+ values = reduce(iadd, self._lists, []) * num
+ return self.__class__(values)
+
+ __rmul__ = __mul__
+
+
+ def __imul__(self, num):
+ """Update the sorted list with `num` shallow copies of values.
+
+ ``sl.__imul__(num)`` <==> ``sl *= num``
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> sl = SortedList('abc')
+ >>> sl *= 3
+ >>> sl
+ SortedList(['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c'])
+
+ :param int num: count of shallow copies
+ :return: existing sorted list
+
+ """
+ values = reduce(iadd, self._lists, []) * num
+ self._clear()
+ self._update(values)
+ return self
+
+
+ def __make_cmp(seq_op, symbol, doc):
+ "Make comparator method."
+ def comparer(self, other):
+ "Compare method for sorted list and sequence."
+ if not isinstance(other, Sequence):
+ return NotImplemented
+
+ self_len = self._len
+ len_other = len(other)
+
+ if self_len != len_other:
+ if seq_op is eq:
+ return False
+ if seq_op is ne:
+ return True
+
+ for alpha, beta in zip(self, other):
+ if alpha != beta:
+ return seq_op(alpha, beta)
+
+ return seq_op(self_len, len_other)
+
+ seq_op_name = seq_op.__name__
+ comparer.__name__ = '__{0}__'.format(seq_op_name)
+ doc_str = """Return true if and only if sorted list is {0} `other`.
+
+ ``sl.__{1}__(other)`` <==> ``sl {2} other``
+
+ Comparisons use lexicographical order as with sequences.
+
+ Runtime complexity: `O(n)`
+
+ :param other: `other` sequence
+ :return: true if sorted list is {0} `other`
+
+ """
+ comparer.__doc__ = dedent(doc_str.format(doc, seq_op_name, symbol))
+ return comparer
+
+
+ __eq__ = __make_cmp(eq, '==', 'equal to')
+ __ne__ = __make_cmp(ne, '!=', 'not equal to')
+ __lt__ = __make_cmp(lt, '<', 'less than')
+ __gt__ = __make_cmp(gt, '>', 'greater than')
+ __le__ = __make_cmp(le, '<=', 'less than or equal to')
+ __ge__ = __make_cmp(ge, '>=', 'greater than or equal to')
+ __make_cmp = staticmethod(__make_cmp)
+
+
+ def __reduce__(self):
+ values = reduce(iadd, self._lists, [])
+ return (type(self), (values,))
+
+
+ @recursive_repr()
+ def __repr__(self):
+ """Return string representation of sorted list.
+
+ ``sl.__repr__()`` <==> ``repr(sl)``
+
+ :return: string representation
+
+ """
+ return '{0}({1!r})'.format(type(self).__name__, list(self))
+
+
+ def _check(self):
+ """Check invariants of sorted list.
+
+ Runtime complexity: `O(n)`
+
+ """
+ try:
+ assert self._load >= 4
+ assert len(self._maxes) == len(self._lists)
+ assert self._len == sum(len(sublist) for sublist in self._lists)
+
+ # Check all sublists are sorted.
+
+ for sublist in self._lists:
+ for pos in range(1, len(sublist)):
+ assert sublist[pos - 1] <= sublist[pos]
+
+ # Check beginning/end of sublists are sorted.
+
+ for pos in range(1, len(self._lists)):
+ assert self._lists[pos - 1][-1] <= self._lists[pos][0]
+
+ # Check _maxes index is the last value of each sublist.
+
+ for pos in range(len(self._maxes)):
+ assert self._maxes[pos] == self._lists[pos][-1]
+
+ # Check sublist lengths are less than double load-factor.
+
+ double = self._load << 1
+ assert all(len(sublist) <= double for sublist in self._lists)
+
+ # Check sublist lengths are greater than half load-factor for all
+ # but the last sublist.
+
+ half = self._load >> 1
+ for pos in range(0, len(self._lists) - 1):
+ assert len(self._lists[pos]) >= half
+
+ if self._index:
+ assert self._len == self._index[0]
+ assert len(self._index) == self._offset + len(self._lists)
+
+ # Check index leaf nodes equal length of sublists.
+
+ for pos in range(len(self._lists)):
+ leaf = self._index[self._offset + pos]
+ assert leaf == len(self._lists[pos])
+
+ # Check index branch nodes are the sum of their children.
+
+ for pos in range(self._offset):
+ child = (pos << 1) + 1
+ if child >= len(self._index):
+ assert self._index[pos] == 0
+ elif child + 1 == len(self._index):
+ assert self._index[pos] == self._index[child]
+ else:
+ child_sum = self._index[child] + self._index[child + 1]
+ assert child_sum == self._index[pos]
+ except:
+ traceback.print_exc(file=sys.stdout)
+ print('len', self._len)
+ print('load', self._load)
+ print('offset', self._offset)
+ print('len_index', len(self._index))
+ print('index', self._index)
+ print('len_maxes', len(self._maxes))
+ print('maxes', self._maxes)
+ print('len_lists', len(self._lists))
+ print('lists', self._lists)
+ raise
+
+
+def identity(value):
+ "Identity function."
+ return value
+
+
+class SortedKeyList(SortedList):
+ """Sorted-key list is a subtype of sorted list.
+
+ The sorted-key list maintains values in comparison order based on the
+ result of a key function applied to every value.
+
+ All the same methods that are available in :class:`SortedList` are also
+ available in :class:`SortedKeyList`.
+
+ Additional methods provided:
+
+ * :attr:`SortedKeyList.key`
+ * :func:`SortedKeyList.bisect_key_left`
+ * :func:`SortedKeyList.bisect_key_right`
+ * :func:`SortedKeyList.irange_key`
+
+ Some examples below use:
+
+ >>> from operator import neg
+ >>> neg
+ <built-in function neg>
+ >>> neg(1)
+ -1
+
+ """
+ def __init__(self, iterable=None, key=identity):
+ """Initialize sorted-key list instance.
+
+ Optional `iterable` argument provides an initial iterable of values to
+ initialize the sorted-key list.
+
+ Optional `key` argument defines a callable that, like the `key`
+ argument to Python's `sorted` function, extracts a comparison key from
+ each value. The default is the identity function.
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList(key=neg)
+ >>> skl
+ SortedKeyList([], key=<built-in function neg>)
+ >>> skl = SortedKeyList([3, 1, 2], key=neg)
+ >>> skl
+ SortedKeyList([3, 2, 1], key=<built-in function neg>)
+
+ :param iterable: initial values (optional)
+ :param key: function used to extract comparison key (optional)
+
+ """
+ self._key = key
+ self._len = 0
+ self._load = self.DEFAULT_LOAD_FACTOR
+ self._lists = []
+ self._keys = []
+ self._maxes = []
+ self._index = []
+ self._offset = 0
+
+ if iterable is not None:
+ self._update(iterable)
+
+
+ def __new__(cls, iterable=None, key=identity):
+ return object.__new__(cls)
+
+
+ @property
+ def key(self):
+ "Function used to extract comparison key from values."
+ return self._key
+
+
+ def clear(self):
+ """Remove all values from sorted-key list.
+
+ Runtime complexity: `O(n)`
+
+ """
+ self._len = 0
+ del self._lists[:]
+ del self._keys[:]
+ del self._maxes[:]
+ del self._index[:]
+
+ _clear = clear
+
+
+ def add(self, value):
+ """Add `value` to sorted-key list.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList(key=neg)
+ >>> skl.add(3)
+ >>> skl.add(1)
+ >>> skl.add(2)
+ >>> skl
+ SortedKeyList([3, 2, 1], key=<built-in function neg>)
+
+ :param value: value to add to sorted-key list
+
+ """
+ _lists = self._lists
+ _keys = self._keys
+ _maxes = self._maxes
+
+ key = self._key(value)
+
+ if _maxes:
+ pos = bisect_right(_maxes, key)
+
+ if pos == len(_maxes):
+ pos -= 1
+ _lists[pos].append(value)
+ _keys[pos].append(key)
+ _maxes[pos] = key
+ else:
+ idx = bisect_right(_keys[pos], key)
+ _lists[pos].insert(idx, value)
+ _keys[pos].insert(idx, key)
+
+ self._expand(pos)
+ else:
+ _lists.append([value])
+ _keys.append([key])
+ _maxes.append(key)
+
+ self._len += 1
+
+
+ def _expand(self, pos):
+ """Split sublists with length greater than double the load-factor.
+
+ Updates the index when the sublist length is less than double the load
+ level. This requires incrementing the nodes in a traversal from the
+ leaf node to the root. For an example traversal see
+ ``SortedList._loc``.
+
+ """
+ _lists = self._lists
+ _keys = self._keys
+ _index = self._index
+
+ if len(_keys[pos]) > (self._load << 1):
+ _maxes = self._maxes
+ _load = self._load
+
+ _lists_pos = _lists[pos]
+ _keys_pos = _keys[pos]
+ half = _lists_pos[_load:]
+ half_keys = _keys_pos[_load:]
+ del _lists_pos[_load:]
+ del _keys_pos[_load:]
+ _maxes[pos] = _keys_pos[-1]
+
+ _lists.insert(pos + 1, half)
+ _keys.insert(pos + 1, half_keys)
+ _maxes.insert(pos + 1, half_keys[-1])
+
+ del _index[:]
+ else:
+ if _index:
+ child = self._offset + pos
+ while child:
+ _index[child] += 1
+ child = (child - 1) >> 1
+ _index[0] += 1
+
+
+ def update(self, iterable):
+ """Update sorted-key list by adding all values from `iterable`.
+
+ Runtime complexity: `O(k*log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList(key=neg)
+ >>> skl.update([3, 1, 2])
+ >>> skl
+ SortedKeyList([3, 2, 1], key=<built-in function neg>)
+
+ :param iterable: iterable of values to add
+
+ """
+ _lists = self._lists
+ _keys = self._keys
+ _maxes = self._maxes
+ values = sorted(iterable, key=self._key)
+
+ if _maxes:
+ if len(values) * 4 >= self._len:
+ _lists.append(values)
+ values = reduce(iadd, _lists, [])
+ values.sort(key=self._key)
+ self._clear()
+ else:
+ _add = self.add
+ for val in values:
+ _add(val)
+ return
+
+ _load = self._load
+ _lists.extend(values[pos:(pos + _load)]
+ for pos in range(0, len(values), _load))
+ _keys.extend(list(map(self._key, _list)) for _list in _lists)
+ _maxes.extend(sublist[-1] for sublist in _keys)
+ self._len = len(values)
+ del self._index[:]
+
+ _update = update
+
+
+ def __contains__(self, value):
+ """Return true if `value` is an element of the sorted-key list.
+
+ ``skl.__contains__(value)`` <==> ``value in skl``
+
+ Runtime complexity: `O(log(n))`
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg)
+ >>> 3 in skl
+ True
+
+ :param value: search for value in sorted-key list
+ :return: true if `value` in sorted-key list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return False
+
+ key = self._key(value)
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ return False
+
+ _lists = self._lists
+ _keys = self._keys
+
+ idx = bisect_left(_keys[pos], key)
+
+ len_keys = len(_keys)
+ len_sublist = len(_keys[pos])
+
+ while True:
+ if _keys[pos][idx] != key:
+ return False
+ if _lists[pos][idx] == value:
+ return True
+ idx += 1
+ if idx == len_sublist:
+ pos += 1
+ if pos == len_keys:
+ return False
+ len_sublist = len(_keys[pos])
+ idx = 0
+
+
+ def discard(self, value):
+ """Remove `value` from sorted-key list if it is a member.
+
+ If `value` is not a member, do nothing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.discard(1)
+ >>> skl.discard(0)
+ >>> skl == [5, 4, 3, 2]
+ True
+
+ :param value: `value` to discard from sorted-key list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return
+
+ key = self._key(value)
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ return
+
+ _lists = self._lists
+ _keys = self._keys
+ idx = bisect_left(_keys[pos], key)
+ len_keys = len(_keys)
+ len_sublist = len(_keys[pos])
+
+ while True:
+ if _keys[pos][idx] != key:
+ return
+ if _lists[pos][idx] == value:
+ self._delete(pos, idx)
+ return
+ idx += 1
+ if idx == len_sublist:
+ pos += 1
+ if pos == len_keys:
+ return
+ len_sublist = len(_keys[pos])
+ idx = 0
+
+
+ def remove(self, value):
+ """Remove `value` from sorted-key list; `value` must be a member.
+
+ If `value` is not a member, raise ValueError.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([1, 2, 3, 4, 5], key=neg)
+ >>> skl.remove(5)
+ >>> skl == [4, 3, 2, 1]
+ True
+ >>> skl.remove(0)
+ Traceback (most recent call last):
+ ...
+ ValueError: 0 not in list
+
+ :param value: `value` to remove from sorted-key list
+ :raises ValueError: if `value` is not in sorted-key list
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ raise ValueError('{0!r} not in list'.format(value))
+
+ key = self._key(value)
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ raise ValueError('{0!r} not in list'.format(value))
+
+ _lists = self._lists
+ _keys = self._keys
+ idx = bisect_left(_keys[pos], key)
+ len_keys = len(_keys)
+ len_sublist = len(_keys[pos])
+
+ while True:
+ if _keys[pos][idx] != key:
+ raise ValueError('{0!r} not in list'.format(value))
+ if _lists[pos][idx] == value:
+ self._delete(pos, idx)
+ return
+ idx += 1
+ if idx == len_sublist:
+ pos += 1
+ if pos == len_keys:
+ raise ValueError('{0!r} not in list'.format(value))
+ len_sublist = len(_keys[pos])
+ idx = 0
+
+
+ def _delete(self, pos, idx):
+ """Delete value at the given `(pos, idx)`.
+
+ Combines lists that are less than half the load level.
+
+ Updates the index when the sublist length is more than half the load
+ level. This requires decrementing the nodes in a traversal from the
+ leaf node to the root. For an example traversal see
+ ``SortedList._loc``.
+
+ :param int pos: lists index
+ :param int idx: sublist index
+
+ """
+ _lists = self._lists
+ _keys = self._keys
+ _maxes = self._maxes
+ _index = self._index
+ keys_pos = _keys[pos]
+ lists_pos = _lists[pos]
+
+ del keys_pos[idx]
+ del lists_pos[idx]
+ self._len -= 1
+
+ len_keys_pos = len(keys_pos)
+
+ if len_keys_pos > (self._load >> 1):
+ _maxes[pos] = keys_pos[-1]
+
+ if _index:
+ child = self._offset + pos
+ while child > 0:
+ _index[child] -= 1
+ child = (child - 1) >> 1
+ _index[0] -= 1
+ elif len(_keys) > 1:
+ if not pos:
+ pos += 1
+
+ prev = pos - 1
+ _keys[prev].extend(_keys[pos])
+ _lists[prev].extend(_lists[pos])
+ _maxes[prev] = _keys[prev][-1]
+
+ del _lists[pos]
+ del _keys[pos]
+ del _maxes[pos]
+ del _index[:]
+
+ self._expand(prev)
+ elif len_keys_pos:
+ _maxes[pos] = keys_pos[-1]
+ else:
+ del _lists[pos]
+ del _keys[pos]
+ del _maxes[pos]
+ del _index[:]
+
+
+ def irange(self, minimum=None, maximum=None, inclusive=(True, True),
+ reverse=False):
+ """Create an iterator of values between `minimum` and `maximum`.
+
+ Both `minimum` and `maximum` default to `None` which is automatically
+ inclusive of the beginning and end of the sorted-key list.
+
+ The argument `inclusive` is a pair of booleans that indicates whether
+ the minimum and maximum ought to be included in the range,
+ respectively. The default is ``(True, True)`` such that the range is
+ inclusive of both minimum and maximum.
+
+ When `reverse` is `True` the values are yielded from the iterator in
+ reverse order; `reverse` defaults to `False`.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg)
+ >>> it = skl.irange(14.5, 11.5)
+ >>> list(it)
+ [14, 13, 12]
+
+ :param minimum: minimum value to start iterating
+ :param maximum: maximum value to stop iterating
+ :param inclusive: pair of booleans
+ :param bool reverse: yield values in reverse order
+ :return: iterator
+
+ """
+ min_key = self._key(minimum) if minimum is not None else None
+ max_key = self._key(maximum) if maximum is not None else None
+ return self._irange_key(
+ min_key=min_key, max_key=max_key,
+ inclusive=inclusive, reverse=reverse,
+ )
+
+
+ def irange_key(self, min_key=None, max_key=None, inclusive=(True, True),
+ reverse=False):
+ """Create an iterator of values between `min_key` and `max_key`.
+
+ Both `min_key` and `max_key` default to `None` which is automatically
+ inclusive of the beginning and end of the sorted-key list.
+
+ The argument `inclusive` is a pair of booleans that indicates whether
+ the minimum and maximum ought to be included in the range,
+ respectively. The default is ``(True, True)`` such that the range is
+ inclusive of both minimum and maximum.
+
+ When `reverse` is `True` the values are yielded from the iterator in
+ reverse order; `reverse` defaults to `False`.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([11, 12, 13, 14, 15], key=neg)
+ >>> it = skl.irange_key(-14, -12)
+ >>> list(it)
+ [14, 13, 12]
+
+ :param min_key: minimum key to start iterating
+ :param max_key: maximum key to stop iterating
+ :param inclusive: pair of booleans
+ :param bool reverse: yield values in reverse order
+ :return: iterator
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return iter(())
+
+ _keys = self._keys
+
+ # Calculate the minimum (pos, idx) pair. By default this location
+ # will be inclusive in our calculation.
+
+ if min_key is None:
+ min_pos = 0
+ min_idx = 0
+ else:
+ if inclusive[0]:
+ min_pos = bisect_left(_maxes, min_key)
+
+ if min_pos == len(_maxes):
+ return iter(())
+
+ min_idx = bisect_left(_keys[min_pos], min_key)
+ else:
+ min_pos = bisect_right(_maxes, min_key)
+
+ if min_pos == len(_maxes):
+ return iter(())
+
+ min_idx = bisect_right(_keys[min_pos], min_key)
+
+ # Calculate the maximum (pos, idx) pair. By default this location
+ # will be exclusive in our calculation.
+
+ if max_key is None:
+ max_pos = len(_maxes) - 1
+ max_idx = len(_keys[max_pos])
+ else:
+ if inclusive[1]:
+ max_pos = bisect_right(_maxes, max_key)
+
+ if max_pos == len(_maxes):
+ max_pos -= 1
+ max_idx = len(_keys[max_pos])
+ else:
+ max_idx = bisect_right(_keys[max_pos], max_key)
+ else:
+ max_pos = bisect_left(_maxes, max_key)
+
+ if max_pos == len(_maxes):
+ max_pos -= 1
+ max_idx = len(_keys[max_pos])
+ else:
+ max_idx = bisect_left(_keys[max_pos], max_key)
+
+ return self._islice(min_pos, min_idx, max_pos, max_idx, reverse)
+
+ _irange_key = irange_key
+
+
+ def bisect_left(self, value):
+ """Return an index to insert `value` in the sorted-key list.
+
+ If the `value` is already present, the insertion point will be before
+ (to the left of) any existing values.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.bisect_left(1)
+ 4
+
+ :param value: insertion index of value in sorted-key list
+ :return: index
+
+ """
+ return self._bisect_key_left(self._key(value))
+
+
+ def bisect_right(self, value):
+ """Return an index to insert `value` in the sorted-key list.
+
+ Similar to `bisect_left`, but if `value` is already present, the
+ insertion point will be after (to the right of) any existing values.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.bisect_right(1)
+ 5
+
+ :param value: insertion index of value in sorted-key list
+ :return: index
+
+ """
+ return self._bisect_key_right(self._key(value))
+
+ bisect = bisect_right
+
+
+ def bisect_key_left(self, key):
+ """Return an index to insert `key` in the sorted-key list.
+
+ If the `key` is already present, the insertion point will be before (to
+ the left of) any existing keys.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.bisect_key_left(-1)
+ 4
+
+ :param key: insertion index of key in sorted-key list
+ :return: index
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ return self._len
+
+ idx = bisect_left(self._keys[pos], key)
+
+ return self._loc(pos, idx)
+
+ _bisect_key_left = bisect_key_left
+
+
+ def bisect_key_right(self, key):
+ """Return an index to insert `key` in the sorted-key list.
+
+ Similar to `bisect_key_left`, but if `key` is already present, the
+ insertion point will be after (to the right of) any existing keys.
+
+ Similar to the `bisect` module in the standard library.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.bisect_key_right(-1)
+ 5
+
+ :param key: insertion index of key in sorted-key list
+ :return: index
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ pos = bisect_right(_maxes, key)
+
+ if pos == len(_maxes):
+ return self._len
+
+ idx = bisect_right(self._keys[pos], key)
+
+ return self._loc(pos, idx)
+
+ bisect_key = bisect_key_right
+ _bisect_key_right = bisect_key_right
+
+
+ def count(self, value):
+ """Return number of occurrences of `value` in the sorted-key list.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([4, 4, 4, 4, 3, 3, 3, 2, 2, 1], key=neg)
+ >>> skl.count(2)
+ 2
+
+ :param value: value to count in sorted-key list
+ :return: count
+
+ """
+ _maxes = self._maxes
+
+ if not _maxes:
+ return 0
+
+ key = self._key(value)
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ return 0
+
+ _lists = self._lists
+ _keys = self._keys
+ idx = bisect_left(_keys[pos], key)
+ total = 0
+ len_keys = len(_keys)
+ len_sublist = len(_keys[pos])
+
+ while True:
+ if _keys[pos][idx] != key:
+ return total
+ if _lists[pos][idx] == value:
+ total += 1
+ idx += 1
+ if idx == len_sublist:
+ pos += 1
+ if pos == len_keys:
+ return total
+ len_sublist = len(_keys[pos])
+ idx = 0
+
+
+ def copy(self):
+ """Return a shallow copy of the sorted-key list.
+
+ Runtime complexity: `O(n)`
+
+ :return: new sorted-key list
+
+ """
+ return self.__class__(self, key=self._key)
+
+ __copy__ = copy
+
+
+ def index(self, value, start=None, stop=None):
+ """Return first index of value in sorted-key list.
+
+ Raise ValueError if `value` is not present.
+
+ Index must be between `start` and `stop` for the `value` to be
+ considered present. The default value, None, for `start` and `stop`
+ indicate the beginning and end of the sorted-key list.
+
+ Negative indices are supported.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([5, 4, 3, 2, 1], key=neg)
+ >>> skl.index(2)
+ 3
+ >>> skl.index(0)
+ Traceback (most recent call last):
+ ...
+ ValueError: 0 is not in list
+
+ :param value: value in sorted-key list
+ :param int start: start index (default None, start of sorted-key list)
+ :param int stop: stop index (default None, end of sorted-key list)
+ :return: index of value
+ :raises ValueError: if value is not present
+
+ """
+ _len = self._len
+
+ if not _len:
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ if start is None:
+ start = 0
+ if start < 0:
+ start += _len
+ if start < 0:
+ start = 0
+
+ if stop is None:
+ stop = _len
+ if stop < 0:
+ stop += _len
+ if stop > _len:
+ stop = _len
+
+ if stop <= start:
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ _maxes = self._maxes
+ key = self._key(value)
+ pos = bisect_left(_maxes, key)
+
+ if pos == len(_maxes):
+ raise ValueError('{0!r} is not in list'.format(value))
+
+ stop -= 1
+ _lists = self._lists
+ _keys = self._keys
+ idx = bisect_left(_keys[pos], key)
+ len_keys = len(_keys)
+ len_sublist = len(_keys[pos])
+
+ while True:
+ if _keys[pos][idx] != key:
+ raise ValueError('{0!r} is not in list'.format(value))
+ if _lists[pos][idx] == value:
+ loc = self._loc(pos, idx)
+ if start <= loc <= stop:
+ return loc
+ elif loc > stop:
+ break
+ idx += 1
+ if idx == len_sublist:
+ pos += 1
+ if pos == len_keys:
+ raise ValueError('{0!r} is not in list'.format(value))
+ len_sublist = len(_keys[pos])
+ idx = 0
+
+ raise ValueError('{0!r} is not in list'.format(value))
+
+
+ def __add__(self, other):
+ """Return new sorted-key list containing all values in both sequences.
+
+ ``skl.__add__(other)`` <==> ``skl + other``
+
+ Values in `other` do not need to be in sorted-key order.
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> from operator import neg
+ >>> skl1 = SortedKeyList([5, 4, 3], key=neg)
+ >>> skl2 = SortedKeyList([2, 1, 0], key=neg)
+ >>> skl1 + skl2
+ SortedKeyList([5, 4, 3, 2, 1, 0], key=<built-in function neg>)
+
+ :param other: other iterable
+ :return: new sorted-key list
+
+ """
+ values = reduce(iadd, self._lists, [])
+ values.extend(other)
+ return self.__class__(values, key=self._key)
+
+ __radd__ = __add__
+
+
+ def __mul__(self, num):
+ """Return new sorted-key list with `num` shallow copies of values.
+
+ ``skl.__mul__(num)`` <==> ``skl * num``
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> from operator import neg
+ >>> skl = SortedKeyList([3, 2, 1], key=neg)
+ >>> skl * 2
+ SortedKeyList([3, 3, 2, 2, 1, 1], key=<built-in function neg>)
+
+ :param int num: count of shallow copies
+ :return: new sorted-key list
+
+ """
+ values = reduce(iadd, self._lists, []) * num
+ return self.__class__(values, key=self._key)
+
+
+ def __reduce__(self):
+ values = reduce(iadd, self._lists, [])
+ return (type(self), (values, self.key))
+
+
+ @recursive_repr()
+ def __repr__(self):
+ """Return string representation of sorted-key list.
+
+ ``skl.__repr__()`` <==> ``repr(skl)``
+
+ :return: string representation
+
+ """
+ type_name = type(self).__name__
+ return '{0}({1!r}, key={2!r})'.format(type_name, list(self), self._key)
+
+
+ def _check(self):
+ """Check invariants of sorted-key list.
+
+ Runtime complexity: `O(n)`
+
+ """
+ try:
+ assert self._load >= 4
+ assert len(self._maxes) == len(self._lists) == len(self._keys)
+ assert self._len == sum(len(sublist) for sublist in self._lists)
+
+ # Check all sublists are sorted.
+
+ for sublist in self._keys:
+ for pos in range(1, len(sublist)):
+ assert sublist[pos - 1] <= sublist[pos]
+
+ # Check beginning/end of sublists are sorted.
+
+ for pos in range(1, len(self._keys)):
+ assert self._keys[pos - 1][-1] <= self._keys[pos][0]
+
+ # Check _keys matches _key mapped to _lists.
+
+ for val_sublist, key_sublist in zip(self._lists, self._keys):
+ assert len(val_sublist) == len(key_sublist)
+ for val, key in zip(val_sublist, key_sublist):
+ assert self._key(val) == key
+
+ # Check _maxes index is the last value of each sublist.
+
+ for pos in range(len(self._maxes)):
+ assert self._maxes[pos] == self._keys[pos][-1]
+
+ # Check sublist lengths are less than double load-factor.
+
+ double = self._load << 1
+ assert all(len(sublist) <= double for sublist in self._lists)
+
+ # Check sublist lengths are greater than half load-factor for all
+ # but the last sublist.
+
+ half = self._load >> 1
+ for pos in range(0, len(self._lists) - 1):
+ assert len(self._lists[pos]) >= half
+
+ if self._index:
+ assert self._len == self._index[0]
+ assert len(self._index) == self._offset + len(self._lists)
+
+ # Check index leaf nodes equal length of sublists.
+
+ for pos in range(len(self._lists)):
+ leaf = self._index[self._offset + pos]
+ assert leaf == len(self._lists[pos])
+
+ # Check index branch nodes are the sum of their children.
+
+ for pos in range(self._offset):
+ child = (pos << 1) + 1
+ if child >= len(self._index):
+ assert self._index[pos] == 0
+ elif child + 1 == len(self._index):
+ assert self._index[pos] == self._index[child]
+ else:
+ child_sum = self._index[child] + self._index[child + 1]
+ assert child_sum == self._index[pos]
+ except:
+ traceback.print_exc(file=sys.stdout)
+ print('len', self._len)
+ print('load', self._load)
+ print('offset', self._offset)
+ print('len_index', len(self._index))
+ print('index', self._index)
+ print('len_maxes', len(self._maxes))
+ print('maxes', self._maxes)
+ print('len_keys', len(self._keys))
+ print('keys', self._keys)
+ print('len_lists', len(self._lists))
+ print('lists', self._lists)
+ raise
+
+
+SortedListWithKey = SortedKeyList
diff --git a/contrib/python/sortedcontainers/py3/sortedcontainers/sortedset.py b/contrib/python/sortedcontainers/py3/sortedcontainers/sortedset.py
new file mode 100644
index 0000000000..be2b8999c5
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/sortedcontainers/sortedset.py
@@ -0,0 +1,733 @@
+"""Sorted Set
+=============
+
+:doc:`Sorted Containers<index>` is an Apache2 licensed Python sorted
+collections library, written in pure-Python, and fast as C-extensions. The
+:doc:`introduction<introduction>` is the best way to get started.
+
+Sorted set implementations:
+
+.. currentmodule:: sortedcontainers
+
+* :class:`SortedSet`
+
+"""
+
+from itertools import chain
+from operator import eq, ne, gt, ge, lt, le
+from textwrap import dedent
+
+from .sortedlist import SortedList, recursive_repr
+
+###############################################################################
+# BEGIN Python 2/3 Shims
+###############################################################################
+
+try:
+ from collections.abc import MutableSet, Sequence, Set
+except ImportError:
+ from collections import MutableSet, Sequence, Set
+
+###############################################################################
+# END Python 2/3 Shims
+###############################################################################
+
+
+class SortedSet(MutableSet, Sequence):
+ """Sorted set is a sorted mutable set.
+
+ Sorted set values are maintained in sorted order. The design of sorted set
+ is simple: sorted set uses a set for set-operations and maintains a sorted
+ list of values.
+
+ Sorted set values must be hashable and comparable. The hash and total
+ ordering of values must not change while they are stored in the sorted set.
+
+ Mutable set methods:
+
+ * :func:`SortedSet.__contains__`
+ * :func:`SortedSet.__iter__`
+ * :func:`SortedSet.__len__`
+ * :func:`SortedSet.add`
+ * :func:`SortedSet.discard`
+
+ Sequence methods:
+
+ * :func:`SortedSet.__getitem__`
+ * :func:`SortedSet.__delitem__`
+ * :func:`SortedSet.__reversed__`
+
+ Methods for removing values:
+
+ * :func:`SortedSet.clear`
+ * :func:`SortedSet.pop`
+ * :func:`SortedSet.remove`
+
+ Set-operation methods:
+
+ * :func:`SortedSet.difference`
+ * :func:`SortedSet.difference_update`
+ * :func:`SortedSet.intersection`
+ * :func:`SortedSet.intersection_update`
+ * :func:`SortedSet.symmetric_difference`
+ * :func:`SortedSet.symmetric_difference_update`
+ * :func:`SortedSet.union`
+ * :func:`SortedSet.update`
+
+ Methods for miscellany:
+
+ * :func:`SortedSet.copy`
+ * :func:`SortedSet.count`
+ * :func:`SortedSet.__repr__`
+ * :func:`SortedSet._check`
+
+ Sorted list methods available:
+
+ * :func:`SortedList.bisect_left`
+ * :func:`SortedList.bisect_right`
+ * :func:`SortedList.index`
+ * :func:`SortedList.irange`
+ * :func:`SortedList.islice`
+ * :func:`SortedList._reset`
+
+ Additional sorted list methods available, if key-function used:
+
+ * :func:`SortedKeyList.bisect_key_left`
+ * :func:`SortedKeyList.bisect_key_right`
+ * :func:`SortedKeyList.irange_key`
+
+ Sorted set comparisons use subset and superset relations. Two sorted sets
+ are equal if and only if every element of each sorted set is contained in
+ the other (each is a subset of the other). A sorted set is less than
+ another sorted set if and only if the first sorted set is a proper subset
+ of the second sorted set (is a subset, but is not equal). A sorted set is
+ greater than another sorted set if and only if the first sorted set is a
+ proper superset of the second sorted set (is a superset, but is not equal).
+
+ """
+ def __init__(self, iterable=None, key=None):
+ """Initialize sorted set instance.
+
+ Optional `iterable` argument provides an initial iterable of values to
+ initialize the sorted set.
+
+ Optional `key` argument defines a callable that, like the `key`
+ argument to Python's `sorted` function, extracts a comparison key from
+ each value. The default, none, compares values directly.
+
+ Runtime complexity: `O(n*log(n))`
+
+ >>> ss = SortedSet([3, 1, 2, 5, 4])
+ >>> ss
+ SortedSet([1, 2, 3, 4, 5])
+ >>> from operator import neg
+ >>> ss = SortedSet([3, 1, 2, 5, 4], neg)
+ >>> ss
+ SortedSet([5, 4, 3, 2, 1], key=<built-in function neg>)
+
+ :param iterable: initial values (optional)
+ :param key: function used to extract comparison key (optional)
+
+ """
+ self._key = key
+
+ # SortedSet._fromset calls SortedSet.__init__ after initializing the
+ # _set attribute. So only create a new set if the _set attribute is not
+ # already present.
+
+ if not hasattr(self, '_set'):
+ self._set = set()
+
+ self._list = SortedList(self._set, key=key)
+
+ # Expose some set methods publicly.
+
+ _set = self._set
+ self.isdisjoint = _set.isdisjoint
+ self.issubset = _set.issubset
+ self.issuperset = _set.issuperset
+
+ # Expose some sorted list methods publicly.
+
+ _list = self._list
+ self.bisect_left = _list.bisect_left
+ self.bisect = _list.bisect
+ self.bisect_right = _list.bisect_right
+ self.index = _list.index
+ self.irange = _list.irange
+ self.islice = _list.islice
+ self._reset = _list._reset
+
+ if key is not None:
+ self.bisect_key_left = _list.bisect_key_left
+ self.bisect_key_right = _list.bisect_key_right
+ self.bisect_key = _list.bisect_key
+ self.irange_key = _list.irange_key
+
+ if iterable is not None:
+ self._update(iterable)
+
+
+ @classmethod
+ def _fromset(cls, values, key=None):
+ """Initialize sorted set from existing set.
+
+ Used internally by set operations that return a new set.
+
+ """
+ sorted_set = object.__new__(cls)
+ sorted_set._set = values
+ sorted_set.__init__(key=key)
+ return sorted_set
+
+
+ @property
+ def key(self):
+ """Function used to extract comparison key from values.
+
+ Sorted set compares values directly when the key function is none.
+
+ """
+ return self._key
+
+
+ def __contains__(self, value):
+ """Return true if `value` is an element of the sorted set.
+
+ ``ss.__contains__(value)`` <==> ``value in ss``
+
+ Runtime complexity: `O(1)`
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> 3 in ss
+ True
+
+ :param value: search for value in sorted set
+ :return: true if `value` in sorted set
+
+ """
+ return value in self._set
+
+
+ def __getitem__(self, index):
+ """Lookup value at `index` in sorted set.
+
+ ``ss.__getitem__(index)`` <==> ``ss[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet('abcde')
+ >>> ss[2]
+ 'c'
+ >>> ss[-1]
+ 'e'
+ >>> ss[2:5]
+ ['c', 'd', 'e']
+
+ :param index: integer or slice for indexing
+ :return: value or list of values
+ :raises IndexError: if index out of range
+
+ """
+ return self._list[index]
+
+
+ def __delitem__(self, index):
+ """Remove value at `index` from sorted set.
+
+ ``ss.__delitem__(index)`` <==> ``del ss[index]``
+
+ Supports slicing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet('abcde')
+ >>> del ss[2]
+ >>> ss
+ SortedSet(['a', 'b', 'd', 'e'])
+ >>> del ss[:2]
+ >>> ss
+ SortedSet(['d', 'e'])
+
+ :param index: integer or slice for indexing
+ :raises IndexError: if index out of range
+
+ """
+ _set = self._set
+ _list = self._list
+ if isinstance(index, slice):
+ values = _list[index]
+ _set.difference_update(values)
+ else:
+ value = _list[index]
+ _set.remove(value)
+ del _list[index]
+
+
+ def __make_cmp(set_op, symbol, doc):
+ "Make comparator method."
+ def comparer(self, other):
+ "Compare method for sorted set and set."
+ if isinstance(other, SortedSet):
+ return set_op(self._set, other._set)
+ elif isinstance(other, Set):
+ return set_op(self._set, other)
+ return NotImplemented
+
+ set_op_name = set_op.__name__
+ comparer.__name__ = '__{0}__'.format(set_op_name)
+ doc_str = """Return true if and only if sorted set is {0} `other`.
+
+ ``ss.__{1}__(other)`` <==> ``ss {2} other``
+
+ Comparisons use subset and superset semantics as with sets.
+
+ Runtime complexity: `O(n)`
+
+ :param other: `other` set
+ :return: true if sorted set is {0} `other`
+
+ """
+ comparer.__doc__ = dedent(doc_str.format(doc, set_op_name, symbol))
+ return comparer
+
+
+ __eq__ = __make_cmp(eq, '==', 'equal to')
+ __ne__ = __make_cmp(ne, '!=', 'not equal to')
+ __lt__ = __make_cmp(lt, '<', 'a proper subset of')
+ __gt__ = __make_cmp(gt, '>', 'a proper superset of')
+ __le__ = __make_cmp(le, '<=', 'a subset of')
+ __ge__ = __make_cmp(ge, '>=', 'a superset of')
+ __make_cmp = staticmethod(__make_cmp)
+
+
+ def __len__(self):
+ """Return the size of the sorted set.
+
+ ``ss.__len__()`` <==> ``len(ss)``
+
+ :return: size of sorted set
+
+ """
+ return len(self._set)
+
+
+ def __iter__(self):
+ """Return an iterator over the sorted set.
+
+ ``ss.__iter__()`` <==> ``iter(ss)``
+
+ Iterating the sorted set while adding or deleting values may raise a
+ :exc:`RuntimeError` or fail to iterate over all values.
+
+ """
+ return iter(self._list)
+
+
+ def __reversed__(self):
+ """Return a reverse iterator over the sorted set.
+
+ ``ss.__reversed__()`` <==> ``reversed(ss)``
+
+ Iterating the sorted set while adding or deleting values may raise a
+ :exc:`RuntimeError` or fail to iterate over all values.
+
+ """
+ return reversed(self._list)
+
+
+ def add(self, value):
+ """Add `value` to sorted set.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet()
+ >>> ss.add(3)
+ >>> ss.add(1)
+ >>> ss.add(2)
+ >>> ss
+ SortedSet([1, 2, 3])
+
+ :param value: value to add to sorted set
+
+ """
+ _set = self._set
+ if value not in _set:
+ _set.add(value)
+ self._list.add(value)
+
+ _add = add
+
+
+ def clear(self):
+ """Remove all values from sorted set.
+
+ Runtime complexity: `O(n)`
+
+ """
+ self._set.clear()
+ self._list.clear()
+
+
+ def copy(self):
+ """Return a shallow copy of the sorted set.
+
+ Runtime complexity: `O(n)`
+
+ :return: new sorted set
+
+ """
+ return self._fromset(set(self._set), key=self._key)
+
+ __copy__ = copy
+
+
+ def count(self, value):
+ """Return number of occurrences of `value` in the sorted set.
+
+ Runtime complexity: `O(1)`
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.count(3)
+ 1
+
+ :param value: value to count in sorted set
+ :return: count
+
+ """
+ return 1 if value in self._set else 0
+
+
+ def discard(self, value):
+ """Remove `value` from sorted set if it is a member.
+
+ If `value` is not a member, do nothing.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.discard(5)
+ >>> ss.discard(0)
+ >>> ss == set([1, 2, 3, 4])
+ True
+
+ :param value: `value` to discard from sorted set
+
+ """
+ _set = self._set
+ if value in _set:
+ _set.remove(value)
+ self._list.remove(value)
+
+ _discard = discard
+
+
+ def pop(self, index=-1):
+ """Remove and return value at `index` in sorted set.
+
+ Raise :exc:`IndexError` if the sorted set is empty or index is out of
+ range.
+
+ Negative indices are supported.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet('abcde')
+ >>> ss.pop()
+ 'e'
+ >>> ss.pop(2)
+ 'c'
+ >>> ss
+ SortedSet(['a', 'b', 'd'])
+
+ :param int index: index of value (default -1)
+ :return: value
+ :raises IndexError: if index is out of range
+
+ """
+ # pylint: disable=arguments-differ
+ value = self._list.pop(index)
+ self._set.remove(value)
+ return value
+
+
+ def remove(self, value):
+ """Remove `value` from sorted set; `value` must be a member.
+
+ If `value` is not a member, raise :exc:`KeyError`.
+
+ Runtime complexity: `O(log(n))` -- approximate.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.remove(5)
+ >>> ss == set([1, 2, 3, 4])
+ True
+ >>> ss.remove(0)
+ Traceback (most recent call last):
+ ...
+ KeyError: 0
+
+ :param value: `value` to remove from sorted set
+ :raises KeyError: if `value` is not in sorted set
+
+ """
+ self._set.remove(value)
+ self._list.remove(value)
+
+
+ def difference(self, *iterables):
+ """Return the difference of two or more sets as a new sorted set.
+
+ The `difference` method also corresponds to operator ``-``.
+
+ ``ss.__sub__(iterable)`` <==> ``ss - iterable``
+
+ The difference is all values that are in this sorted set but not the
+ other `iterables`.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.difference([4, 5, 6, 7])
+ SortedSet([1, 2, 3])
+
+ :param iterables: iterable arguments
+ :return: new sorted set
+
+ """
+ diff = self._set.difference(*iterables)
+ return self._fromset(diff, key=self._key)
+
+ __sub__ = difference
+
+
+ def difference_update(self, *iterables):
+ """Remove all values of `iterables` from this sorted set.
+
+ The `difference_update` method also corresponds to operator ``-=``.
+
+ ``ss.__isub__(iterable)`` <==> ``ss -= iterable``
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> _ = ss.difference_update([4, 5, 6, 7])
+ >>> ss
+ SortedSet([1, 2, 3])
+
+ :param iterables: iterable arguments
+ :return: itself
+
+ """
+ _set = self._set
+ _list = self._list
+ values = set(chain(*iterables))
+ if (4 * len(values)) > len(_set):
+ _set.difference_update(values)
+ _list.clear()
+ _list.update(_set)
+ else:
+ _discard = self._discard
+ for value in values:
+ _discard(value)
+ return self
+
+ __isub__ = difference_update
+
+
+ def intersection(self, *iterables):
+ """Return the intersection of two or more sets as a new sorted set.
+
+ The `intersection` method also corresponds to operator ``&``.
+
+ ``ss.__and__(iterable)`` <==> ``ss & iterable``
+
+ The intersection is all values that are in this sorted set and each of
+ the other `iterables`.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.intersection([4, 5, 6, 7])
+ SortedSet([4, 5])
+
+ :param iterables: iterable arguments
+ :return: new sorted set
+
+ """
+ intersect = self._set.intersection(*iterables)
+ return self._fromset(intersect, key=self._key)
+
+ __and__ = intersection
+ __rand__ = __and__
+
+
+ def intersection_update(self, *iterables):
+ """Update the sorted set with the intersection of `iterables`.
+
+ The `intersection_update` method also corresponds to operator ``&=``.
+
+ ``ss.__iand__(iterable)`` <==> ``ss &= iterable``
+
+ Keep only values found in itself and all `iterables`.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> _ = ss.intersection_update([4, 5, 6, 7])
+ >>> ss
+ SortedSet([4, 5])
+
+ :param iterables: iterable arguments
+ :return: itself
+
+ """
+ _set = self._set
+ _list = self._list
+ _set.intersection_update(*iterables)
+ _list.clear()
+ _list.update(_set)
+ return self
+
+ __iand__ = intersection_update
+
+
+ def symmetric_difference(self, other):
+ """Return the symmetric difference with `other` as a new sorted set.
+
+ The `symmetric_difference` method also corresponds to operator ``^``.
+
+ ``ss.__xor__(other)`` <==> ``ss ^ other``
+
+ The symmetric difference is all values tha are in exactly one of the
+ sets.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.symmetric_difference([4, 5, 6, 7])
+ SortedSet([1, 2, 3, 6, 7])
+
+ :param other: `other` iterable
+ :return: new sorted set
+
+ """
+ diff = self._set.symmetric_difference(other)
+ return self._fromset(diff, key=self._key)
+
+ __xor__ = symmetric_difference
+ __rxor__ = __xor__
+
+
+ def symmetric_difference_update(self, other):
+ """Update the sorted set with the symmetric difference with `other`.
+
+ The `symmetric_difference_update` method also corresponds to operator
+ ``^=``.
+
+ ``ss.__ixor__(other)`` <==> ``ss ^= other``
+
+ Keep only values found in exactly one of itself and `other`.
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> _ = ss.symmetric_difference_update([4, 5, 6, 7])
+ >>> ss
+ SortedSet([1, 2, 3, 6, 7])
+
+ :param other: `other` iterable
+ :return: itself
+
+ """
+ _set = self._set
+ _list = self._list
+ _set.symmetric_difference_update(other)
+ _list.clear()
+ _list.update(_set)
+ return self
+
+ __ixor__ = symmetric_difference_update
+
+
+ def union(self, *iterables):
+ """Return new sorted set with values from itself and all `iterables`.
+
+ The `union` method also corresponds to operator ``|``.
+
+ ``ss.__or__(iterable)`` <==> ``ss | iterable``
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> ss.union([4, 5, 6, 7])
+ SortedSet([1, 2, 3, 4, 5, 6, 7])
+
+ :param iterables: iterable arguments
+ :return: new sorted set
+
+ """
+ return self.__class__(chain(iter(self), *iterables), key=self._key)
+
+ __or__ = union
+ __ror__ = __or__
+
+
+ def update(self, *iterables):
+ """Update the sorted set adding values from all `iterables`.
+
+ The `update` method also corresponds to operator ``|=``.
+
+ ``ss.__ior__(iterable)`` <==> ``ss |= iterable``
+
+ >>> ss = SortedSet([1, 2, 3, 4, 5])
+ >>> _ = ss.update([4, 5, 6, 7])
+ >>> ss
+ SortedSet([1, 2, 3, 4, 5, 6, 7])
+
+ :param iterables: iterable arguments
+ :return: itself
+
+ """
+ _set = self._set
+ _list = self._list
+ values = set(chain(*iterables))
+ if (4 * len(values)) > len(_set):
+ _list = self._list
+ _set.update(values)
+ _list.clear()
+ _list.update(_set)
+ else:
+ _add = self._add
+ for value in values:
+ _add(value)
+ return self
+
+ __ior__ = update
+ _update = update
+
+
+ def __reduce__(self):
+ """Support for pickle.
+
+ The tricks played with exposing methods in :func:`SortedSet.__init__`
+ confuse pickle so customize the reducer.
+
+ """
+ return (type(self), (self._set, self._key))
+
+
+ @recursive_repr()
+ def __repr__(self):
+ """Return string representation of sorted set.
+
+ ``ss.__repr__()`` <==> ``repr(ss)``
+
+ :return: string representation
+
+ """
+ _key = self._key
+ key = '' if _key is None else ', key={0!r}'.format(_key)
+ type_name = type(self).__name__
+ return '{0}({1!r}{2})'.format(type_name, list(self), key)
+
+
+ def _check(self):
+ """Check invariants of sorted set.
+
+ Runtime complexity: `O(n)`
+
+ """
+ _set = self._set
+ _list = self._list
+ _list._check()
+ assert len(_set) == len(_list)
+ assert all(value in _set for value in _list)
diff --git a/contrib/python/sortedcontainers/py3/ya.make b/contrib/python/sortedcontainers/py3/ya.make
new file mode 100644
index 0000000000..63d2ec3f5a
--- /dev/null
+++ b/contrib/python/sortedcontainers/py3/ya.make
@@ -0,0 +1,25 @@
+# Generated by devtools/yamaker (pypi).
+
+PY3_LIBRARY()
+
+VERSION(2.4.0)
+
+LICENSE(Apache-2.0)
+
+NO_LINT()
+
+PY_SRCS(
+ TOP_LEVEL
+ sortedcontainers/__init__.py
+ sortedcontainers/sorteddict.py
+ sortedcontainers/sortedlist.py
+ sortedcontainers/sortedset.py
+)
+
+RESOURCE_FILES(
+ PREFIX contrib/python/sortedcontainers/py3/
+ .dist-info/METADATA
+ .dist-info/top_level.txt
+)
+
+END()
diff --git a/contrib/python/sortedcontainers/ya.make b/contrib/python/sortedcontainers/ya.make
index 0b5960226d..617c0fd110 100644
--- a/contrib/python/sortedcontainers/ya.make
+++ b/contrib/python/sortedcontainers/ya.make
@@ -1,25 +1,18 @@
-# Generated by devtools/yamaker (pypi).
-
PY23_LIBRARY()
-VERSION(2.4.0)
+LICENSE(Service-Py23-Proxy)
-LICENSE(Apache-2.0)
+IF (PYTHON2)
+ PEERDIR(contrib/python/sortedcontainers/py2)
+ELSE()
+ PEERDIR(contrib/python/sortedcontainers/py3)
+ENDIF()
NO_LINT()
-PY_SRCS(
- TOP_LEVEL
- sortedcontainers/__init__.py
- sortedcontainers/sorteddict.py
- sortedcontainers/sortedlist.py
- sortedcontainers/sortedset.py
-)
+END()
-RESOURCE_FILES(
- PREFIX contrib/python/sortedcontainers/
- .dist-info/METADATA
- .dist-info/top_level.txt
+RECURSE(
+ py2
+ py3
)
-
-END()