├── .python-version ├── .tool-versions ├── requirements.txt ├── setup.cfg ├── .flake8 ├── .gitignore ├── docs ├── index.rst ├── Makefile └── conf.py ├── .readthedocs.yaml ├── .github └── workflows │ ├── python-upload.yml │ └── python.yml ├── LICENSE.txt ├── setup.py ├── time_sparse_list.py ├── README.rst ├── sparse_list.py └── test_sparse_list.py /.python-version: -------------------------------------------------------------------------------- 1 | 2.7.10 2 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | python 3.11.4 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | pytest 3 | pytest-cov 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = docs/ 3 | max-line-length = 120 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.pyc 2 | /.coverage 3 | /MANIFEST 4 | /build/ 5 | /coverage.xml 6 | /dist/ 7 | /docs/_build 8 | /htmlcov/ 9 | /sparse_list.egg-info/ 10 | /test-results-*.xml 11 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Sparse List documentation master file, created by 2 | sphinx-quickstart on Wed Sep 4 19:16:04 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Sparse List's documentation! 7 | ======================================= 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | 15 | 16 | Indices and tables 17 | ================== 18 | 19 | * :ref:`genindex` 20 | * :ref:`modindex` 21 | * :ref:`search` 22 | 23 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | 18 | # We recommend specifying your dependencies to enable reproducible builds: 19 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | # python: 21 | # install: 22 | # - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /.github/workflows/python-upload.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Upload Python Package 3 | 4 | on: 5 | release: 6 | types: [created] 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Set up Python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: '3.x' 19 | 20 | - name: Install dependencies 21 | run: | 22 | python -m pip install --upgrade pip 23 | pip install setuptools wheel twine 24 | 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 29 | run: | 30 | python setup.py sdist bdist_wheel 31 | twine upload dist/* 32 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Pete Johns 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: 3 | # https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 4 | 5 | name: Python 6 | 7 | on: [push, pull_request] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | python-version: ['3.8', '3.9', '3.10', '3.11'] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: Set up Python ${{ matrix.python-version }} 22 | uses: actions/setup-python@v2 23 | with: 24 | python-version: ${{ matrix.python-version }} 25 | 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 30 | 31 | - name: Lint with flake8 32 | run: flake8 33 | 34 | - name: Test with pytest 35 | run: pytest --cov=sparse_list --cov-report=xml --cov-report=html --junitxml=test-results-${{ matrix.python-version }}.xml 36 | 37 | - name: Upload pytest test results 38 | uses: actions/upload-artifact@v2 39 | with: 40 | name: pytest-results-${{ matrix.python-version }} 41 | path: | 42 | coverage.xml 43 | htmlcov/ 44 | test-results-${{ matrix.python-version }}.xml 45 | if: ${{ always() }} 46 | 47 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | 4 | version = '1.0' 5 | github_url = 'https://github.com/johnsyweb/python_sparse_list' 6 | paj = 'Pete Johns' 7 | paj_email = 'paj+pypi@johnsy.com' 8 | 9 | setup( 10 | name='sparse_list', 11 | py_modules=['sparse_list'], 12 | version=version, 13 | description='A list where most values will be None (or some other default)', 14 | author=paj, 15 | author_email=paj_email, 16 | maintainer=paj, 17 | maintainer_email=paj_email, 18 | url=github_url, 19 | download_url='{}/tarball/{}'.format(github_url, version), 20 | keywords=['sparse', 'list', 'container', 'iterable'], 21 | classifiers=[ 22 | "Development Status :: 4 - Beta", 23 | "Intended Audience :: Developers", 24 | "License :: OSI Approved :: MIT License", 25 | "Operating System :: OS Independent", 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 3", 28 | "Programming Language :: Python :: 3.6", 29 | "Programming Language :: Python :: 3.7", 30 | "Programming Language :: Python :: 3.8", 31 | "Programming Language :: Python :: 3.9", 32 | "Programming Language :: Python :: Implementation :: CPython", 33 | "Programming Language :: Python :: Implementation :: PyPy", 34 | "Topic :: Software Development", 35 | "Topic :: Software Development :: Libraries", 36 | "Topic :: Software Development :: Libraries :: Python Modules", 37 | "Topic :: Utilities", 38 | ], 39 | long_description=(''.join( 40 | [open(f).read() for f in ('README.rst',) if os.path.isfile(f)] 41 | )), 42 | license='MIT' 43 | ) 44 | -------------------------------------------------------------------------------- /time_sparse_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import benchmark 4 | import sparse_list 5 | 6 | 7 | class Benchmark_Repr(benchmark.Benchmark): 8 | def setUp(self): 9 | self.sparse_list = sparse_list.SparseList(1000) 10 | self.list = [None] * 1000 11 | 12 | def test_sparse_list(self): 13 | repr(self.sparse_list) 14 | 15 | def test_list(self): 16 | repr(self.list) 17 | 18 | 19 | class Benchmark_Insert(benchmark.Benchmark): 20 | def setUp(self): 21 | self.sparse_list = sparse_list.SparseList(1000) 22 | self.list = [None] * 1000 23 | 24 | def test_sparse_list(self): 25 | self.sparse_list[100] = 'apple' 26 | 27 | def test_list(self): 28 | self.list[100] = 'apple' 29 | 30 | 31 | class Benchmark_Retrieval(benchmark.Benchmark): 32 | def setUp(self): 33 | self.sparse_list = sparse_list.SparseList(1000) 34 | self.list = [None] * 1000 35 | 36 | def test_sparse_list(self): 37 | self.sparse_list[100] 38 | 39 | def test_list(self): 40 | self.list[100] 41 | 42 | 43 | class Benchmark_Slice_Deletion(benchmark.Benchmark): 44 | def setUp(self): 45 | self.sparse_list = sparse_list.SparseList(range(1000)) 46 | self.list = list(range(1000)) 47 | 48 | def test_sparse_list(self): 49 | del self.sparse_list[1::2] 50 | 51 | def test_list(self): 52 | del self.list[1::2] 53 | 54 | 55 | class Benchmark_Deletion(benchmark.Benchmark): 56 | def setUp(self): 57 | self.sparse_list = sparse_list.SparseList(range(1000)) 58 | self.list = list(range(1000)) 59 | 60 | def test_sparse_list(self): 61 | del self.sparse_list[100] 62 | 63 | def test_list(self): 64 | del self.list[100] 65 | 66 | 67 | if __name__ == '__main__': 68 | benchmark.main(format="markdown", numberFormat="%.4g") 69 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | `Sparse List `__ |Build Status| 2 | ========================================================================= 3 | 4 | Inspired by the post `Populating a sparse list with random 5 | 1's `__ on 6 | `StackOverflow `__. 7 | 8 | A "sparse list" is a list where most values will be None (or some other default) 9 | and for reasons of memory efficiency you don't wish to store these (cf. `Sparse 10 | array `__). 11 | 12 | This implementation has a similar interface to Python's built-in list 13 | but stores the data in a dictionary to conserve memory. 14 | 15 | Installation 16 | ------------ 17 | 18 | `sparse_list `__ is 19 | available from `The Python Package Index (PyPI) `__ . 20 | 21 | Installation is simply: 22 | 23 | :: 24 | 25 | $ pip install sparse_list 26 | 27 | Usage 28 | ----- 29 | 30 | See the 31 | `unit-tests `__! 32 | 33 | Contributing 34 | ------------ 35 | 36 | 1. Fork it 37 | 2. Create your feature branch (``git checkout -b my-new-feature``) 38 | 3. Commit your changes (``git commit -am 'Add some feature'``) 39 | 4. Ensure the tests pass for all Pythons in 40 | `.python.yml `__ 41 | 5. Push to the branch (``git push origin my-new-feature``) 42 | 6. Create new Pull Request 43 | 44 | Thanks 45 | ------ 46 | 47 | If you find this stuff useful, please follow this repository on 48 | `GitHub `__. If you 49 | have something to say, you can contact 50 | `johnsyweb `__ on 51 | `Mastodon `__ and 52 | `GitHub `__. 53 | 54 | 55 | Many thanks 56 | ----------- 57 | 58 | I'm grateful for contributions to what was a solo project (hooray for 59 | `GitHub :octocat:) `__! If you'd like to thank the 60 | contributors, you can find their details here: 61 | 62 | https://github.com/johnsyweb/python_sparse_list/graphs/contributors 63 | 64 | .. |Build Status| image:: https://github.com/johnsyweb/python_sparse_list/actions/workflows/python.yml/badge.svg 65 | :target: https://github.com/johnsyweb/python_sparse_list/actions/workflows/python.yml 66 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SparseList.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SparseList.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/SparseList" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SparseList" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /sparse_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Inspired by the post "Populating a sparse list with random 1's" on 4 | StackOverflow: 5 | 6 | http://stackoverflow.com/q/17522753/78845 7 | 8 | A "sparse list" is a list where most values will be None (or some other 9 | default) and for reasons of memory efficiency you don't wish to store these. 10 | cf. Sparse array: 11 | 12 | http://en.wikipedia.org/wiki/Sparse_array 13 | ''' 14 | 15 | 16 | class SparseList(object): 17 | ''' 18 | This implementation has a similar interface to Python's built-in list but 19 | stores the data in a dictionary to conserve memory. 20 | ''' 21 | 22 | def __init__(self, arg, default_value=None): 23 | self.default = default_value 24 | self.elements = {} 25 | self.size = 0 26 | if isinstance(arg, int): 27 | self.size = int(arg) 28 | elif isinstance(arg, dict): 29 | self.__initialise_from_dict(arg) 30 | else: 31 | self.__initialise_from_iterable(arg) 32 | 33 | def __len__(self): 34 | return self.size 35 | 36 | def population(self): 37 | return len(self.elements) 38 | 39 | def __setitem__(self, index, value): 40 | try: 41 | if index.start: 42 | self.size = max(self.size, index.start + len(value)) 43 | s = slice(index.start, index.stop, index.step).indices(self.size) 44 | for v, i in enumerate(range(*s)): 45 | self.__setitem__(i, value[v]) 46 | except AttributeError: 47 | if value != self.default: 48 | self.elements[index] = value 49 | self.size = max(index + 1, self.size) 50 | 51 | def __getitem__(self, index): 52 | try: 53 | s = slice(index.start, index.stop, index.step).indices(self.size) 54 | indices = range(*s) 55 | sl = SparseList( 56 | { 57 | k: self.elements[i] 58 | for k, i in enumerate(indices) 59 | if i in self.elements 60 | }, self.default) 61 | sl.size = len(indices) 62 | return sl 63 | except AttributeError: 64 | i = slice(index).indices(self.size)[1] 65 | return self.elements.get(i, self.default) 66 | 67 | def __setslice__(self, start, stop, vals): 68 | ''' 69 | __setslice__ is deprecated, but kept here for backwards compatibility 70 | ''' 71 | return self.__setitem__(slice(start, stop), vals) 72 | 73 | def __delitem__(self, item): 74 | if isinstance(item, slice): 75 | keys_to_remove = range(*item.indices(self.size)) 76 | elif item < 0: 77 | keys_to_remove = (self.size + item, ) 78 | else: 79 | keys_to_remove = (item, ) 80 | 81 | if not keys_to_remove: 82 | return 83 | 84 | keys_removed = 0 85 | removing_tail = keys_to_remove[-1] == self.size - 1 86 | 87 | for current_key in sorted(self.elements.keys()): 88 | if current_key < keys_to_remove[0]: 89 | continue 90 | 91 | elif keys_removed < len(keys_to_remove) and current_key > keys_to_remove[keys_removed]: 92 | keys_removed += 1 93 | 94 | if keys_removed and not removing_tail: 95 | self.elements[current_key - keys_removed] = self.elements[current_key] 96 | 97 | del self.elements[current_key] 98 | 99 | self.size -= len(keys_to_remove) 100 | 101 | def __delslice__(self, start, stop): 102 | ''' 103 | __delslice__ is deprecated, but kept here for backwards compatibility 104 | ''' 105 | return self.__delitem__(slice(start, stop)) 106 | 107 | def __iter__(self): 108 | for index in range(self.size): 109 | yield self[index] 110 | 111 | def __contains__(self, index): 112 | return index in self.elements.values() 113 | 114 | def __repr__(self): 115 | return '[{}]'.format(', '.join([str(e) for e in self])) 116 | 117 | def __add__(self, other): 118 | result = self[:] 119 | return result.__iadd__(other) 120 | 121 | def __iadd__(self, other): 122 | for element in other: 123 | self.append(element) 124 | return self 125 | 126 | def append(self, element): 127 | ''' 128 | append element, increasing size by exactly one 129 | ''' 130 | if element != self.default: 131 | self.elements[self.size] = element 132 | self.size += 1 133 | 134 | push = append 135 | 136 | def __initialise_from_dict(self, arg): 137 | def __convert_and_size(key): 138 | try: 139 | key = int(key) 140 | except ValueError: 141 | raise ValueError('Invalid key: {}'.format(key)) 142 | self.size = max(key + 1, self.size) 143 | return key 144 | self.elements = {__convert_and_size(k): v for k, v in arg.items() if v != self.default} 145 | 146 | def __initialise_from_iterable(self, arg): 147 | for v in arg: 148 | self.append(v) 149 | 150 | def __eq__(self, other): 151 | return len(self) == len(other) and all(a == b for a, b in zip(self, other)) 152 | 153 | def __ne__(self, other): 154 | return not self.__eq__(other) 155 | 156 | def __lt__(self, other): 157 | for a, b in zip(self, other): 158 | if a < b: 159 | return True 160 | if a > b: 161 | return False 162 | return len(self) < len(other) 163 | 164 | def __ge__(self, other): 165 | return not self.__lt__(other) 166 | 167 | def __mul__(self, multiplier): 168 | result = self[:] 169 | for _ in range(multiplier - 1): 170 | result += self[:] 171 | return result 172 | 173 | def count(self, value): 174 | ''' 175 | return number of occurrences of value 176 | ''' 177 | return sum(v == value for v in self.elements.values()) + ( 178 | self.size - len(self.elements) if value == self.default else 0 179 | ) 180 | 181 | def extend(self, iterable): 182 | ''' 183 | extend sparse_list by appending elements from the iterable 184 | ''' 185 | self.__iadd__(iterable) 186 | 187 | def index(self, value): 188 | ''' 189 | return first index of value. 190 | Raises ValueError if the value is not present. 191 | ''' 192 | 193 | if value == self.default: 194 | for k, v in enumerate(self): 195 | if v == value: 196 | return k 197 | raise ValueError('{} not in SparseList'.format(value)) 198 | for k, v in self.elements.items(): 199 | if v == value: 200 | return k 201 | raise ValueError('{} not in SparseList'.format(value)) 202 | 203 | def pop(self): 204 | ''' 205 | remove and return item at end of SparseList 206 | Raises IndexError if list is empty. 207 | ''' 208 | if self.size < 1: 209 | raise IndexError('pop from empty SparseList') 210 | value = self[-1] 211 | del self[-1] 212 | return value 213 | 214 | def remove(self, value): 215 | ''' 216 | remove first occurrence of value. 217 | Raises ValueError if the value is not present. 218 | ''' 219 | if value == self.default: 220 | return 221 | for k, v in self.elements.items(): 222 | if v == value: 223 | del self.elements[k] 224 | return 225 | raise ValueError('{} not in SparseList'.format(value)) 226 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Sparse List documentation build configuration file, created by 4 | # sphinx-quickstart on Wed Sep 4 19:16:04 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | #sys.path.insert(0, os.path.abspath('.')) 20 | 21 | # -- General configuration ----------------------------------------------------- 22 | 23 | # If your documentation needs a minimal Sphinx version, state it here. 24 | #needs_sphinx = '1.0' 25 | 26 | # Add any Sphinx extension module names here, as strings. They can be extensions 27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 29 | 30 | # Add any paths that contain templates here, relative to this directory. 31 | templates_path = ['_templates'] 32 | 33 | # The suffix of source filenames. 34 | source_suffix = '.rst' 35 | 36 | # The encoding of source files. 37 | #source_encoding = 'utf-8-sig' 38 | 39 | # The master toctree document. 40 | master_doc = 'index' 41 | 42 | # General information about the project. 43 | project = u'Sparse List' 44 | copyright = u'2013, Pete Johns' 45 | 46 | # The version info for the project you're documenting, acts as replacement for 47 | # |version| and |release|, also used in various other places throughout the 48 | # built documents. 49 | # 50 | # The short X.Y version. 51 | version = '0.5' 52 | # The full version, including alpha/beta/rc tags. 53 | release = '0.5' 54 | 55 | # The language for content autogenerated by Sphinx. Refer to documentation 56 | # for a list of supported languages. 57 | #language = None 58 | 59 | # There are two options for replacing |today|: either, you set today to some 60 | # non-false value, then it is used: 61 | #today = '' 62 | # Else, today_fmt is used as the format for a strftime call. 63 | #today_fmt = '%B %d, %Y' 64 | 65 | # List of patterns, relative to source directory, that match files and 66 | # directories to ignore when looking for source files. 67 | exclude_patterns = ['_build'] 68 | 69 | # The reST default role (used for this markup: `text`) to use for all documents. 70 | #default_role = None 71 | 72 | # If true, '()' will be appended to :func: etc. cross-reference text. 73 | #add_function_parentheses = True 74 | 75 | # If true, the current module name will be prepended to all description 76 | # unit titles (such as .. function::). 77 | #add_module_names = True 78 | 79 | # If true, sectionauthor and moduleauthor directives will be shown in the 80 | # output. They are ignored by default. 81 | #show_authors = False 82 | 83 | # The name of the Pygments (syntax highlighting) style to use. 84 | pygments_style = 'sphinx' 85 | 86 | # A list of ignored prefixes for module index sorting. 87 | #modindex_common_prefix = [] 88 | 89 | # If true, keep warnings as "system message" paragraphs in the built documents. 90 | #keep_warnings = False 91 | 92 | 93 | # -- Options for HTML output --------------------------------------------------- 94 | 95 | # The theme to use for HTML and HTML Help pages. See the documentation for 96 | # a list of builtin themes. 97 | html_theme = 'default' 98 | 99 | # Theme options are theme-specific and customize the look and feel of a theme 100 | # further. For a list of options available for each theme, see the 101 | # documentation. 102 | #html_theme_options = {} 103 | 104 | # Add any paths that contain custom themes here, relative to this directory. 105 | #html_theme_path = [] 106 | 107 | # The name for this set of Sphinx documents. If None, it defaults to 108 | # " v documentation". 109 | #html_title = None 110 | 111 | # A shorter title for the navigation bar. Default is the same as html_title. 112 | #html_short_title = None 113 | 114 | # The name of an image file (relative to this directory) to place at the top 115 | # of the sidebar. 116 | #html_logo = None 117 | 118 | # The name of an image file (within the static path) to use as favicon of the 119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | #html_favicon = None 122 | 123 | # Add any paths that contain custom static files (such as style sheets) here, 124 | # relative to this directory. They are copied after the builtin static files, 125 | # so a file named "default.css" will overwrite the builtin "default.css". 126 | html_static_path = ['_static'] 127 | 128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 129 | # using the given strftime format. 130 | #html_last_updated_fmt = '%b %d, %Y' 131 | 132 | # If true, SmartyPants will be used to convert quotes and dashes to 133 | # typographically correct entities. 134 | #html_use_smartypants = True 135 | 136 | # Custom sidebar templates, maps document names to template names. 137 | #html_sidebars = {} 138 | 139 | # Additional templates that should be rendered to pages, maps page names to 140 | # template names. 141 | #html_additional_pages = {} 142 | 143 | # If false, no module index is generated. 144 | #html_domain_indices = True 145 | 146 | # If false, no index is generated. 147 | #html_use_index = True 148 | 149 | # If true, the index is split into individual pages for each letter. 150 | #html_split_index = False 151 | 152 | # If true, links to the reST sources are added to the pages. 153 | #html_show_sourcelink = True 154 | 155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 156 | #html_show_sphinx = True 157 | 158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 159 | #html_show_copyright = True 160 | 161 | # If true, an OpenSearch description file will be output, and all pages will 162 | # contain a tag referring to it. The value of this option must be the 163 | # base URL from which the finished HTML is served. 164 | #html_use_opensearch = '' 165 | 166 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 167 | #html_file_suffix = None 168 | 169 | # Output file base name for HTML help builder. 170 | htmlhelp_basename = 'SparseListdoc' 171 | 172 | 173 | # -- Options for LaTeX output -------------------------------------------------- 174 | 175 | latex_elements = { 176 | # The paper size ('letterpaper' or 'a4paper'). 177 | #'papersize': 'letterpaper', 178 | 179 | # The font size ('10pt', '11pt' or '12pt'). 180 | #'pointsize': '10pt', 181 | 182 | # Additional stuff for the LaTeX preamble. 183 | #'preamble': '', 184 | } 185 | 186 | # Grouping the document tree into LaTeX files. List of tuples 187 | # (source start file, target name, title, author, documentclass [howto/manual]). 188 | latex_documents = [ 189 | ('index', 'SparseList.tex', u'Sparse List Documentation', 190 | u'Pete Johns', 'manual'), 191 | ] 192 | 193 | # The name of an image file (relative to this directory) to place at the top of 194 | # the title page. 195 | #latex_logo = None 196 | 197 | # For "manual" documents, if this is true, then toplevel headings are parts, 198 | # not chapters. 199 | #latex_use_parts = False 200 | 201 | # If true, show page references after internal links. 202 | #latex_show_pagerefs = False 203 | 204 | # If true, show URL addresses after external links. 205 | #latex_show_urls = False 206 | 207 | # Documents to append as an appendix to all manuals. 208 | #latex_appendices = [] 209 | 210 | # If false, no module index is generated. 211 | #latex_domain_indices = True 212 | 213 | 214 | # -- Options for manual page output -------------------------------------------- 215 | 216 | # One entry per manual page. List of tuples 217 | # (source start file, name, description, authors, manual section). 218 | man_pages = [ 219 | ('index', 'sparselist', u'Sparse List Documentation', 220 | [u'Pete Johns'], 1) 221 | ] 222 | 223 | # If true, show URL addresses after external links. 224 | #man_show_urls = False 225 | 226 | 227 | # -- Options for Texinfo output ------------------------------------------------ 228 | 229 | # Grouping the document tree into Texinfo files. List of tuples 230 | # (source start file, target name, title, author, 231 | # dir menu entry, description, category) 232 | texinfo_documents = [ 233 | ('index', 'SparseList', u'Sparse List Documentation', 234 | u'Pete Johns', 'SparseList', 'One line description of project.', 235 | 'Miscellaneous'), 236 | ] 237 | 238 | # Documents to append as an appendix to all manuals. 239 | #texinfo_appendices = [] 240 | 241 | # If false, no module index is generated. 242 | #texinfo_domain_indices = True 243 | 244 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 245 | #texinfo_show_urls = 'footnote' 246 | 247 | # If true, do not generate a @detailmenu in the "Top" node's menu. 248 | #texinfo_no_detailmenu = False 249 | -------------------------------------------------------------------------------- /test_sparse_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sparse_list 4 | import pytest 5 | 6 | 7 | class TestSparseList: 8 | def test_init_zero(self): 9 | sl = sparse_list.SparseList(0) 10 | assert 0 == len(sl) 11 | assert 0 == sl.population() 12 | 13 | def test_init_non_zero(self): 14 | sl = sparse_list.SparseList(10) 15 | assert 10 == len(sl) 16 | assert 0 == sl.population() 17 | 18 | def test_init_no_default(self): 19 | sl = sparse_list.SparseList(1) 20 | assert sl.default is None 21 | assert 0 == sl.population() 22 | 23 | def test_init_default(self): 24 | sl = sparse_list.SparseList(1, 'test') 25 | assert 'test' == sl.default 26 | assert 0 == sl.population() 27 | 28 | def test_random_access_write(self): 29 | sl = sparse_list.SparseList(1) 30 | sl[0] = 'alice' 31 | assert {0: 'alice'} == sl.elements 32 | assert 1 == sl.population() 33 | 34 | def test_random_access_read_present(self): 35 | sl = sparse_list.SparseList(2) 36 | sl[0] = 'brent' 37 | assert 'brent' == sl[0] 38 | assert 1 == sl.population() 39 | 40 | def test_random_access_read_absent(self): 41 | sl = sparse_list.SparseList(2, 'absent') 42 | sl[1] = 'clint' 43 | assert 'absent' == sl[0] 44 | assert 1 == sl.population() 45 | 46 | def test_iteration_empty(self): 47 | sl = sparse_list.SparseList(3) 48 | assert [None, None, None] == list(sl) 49 | 50 | def test_iteration_populated(self): 51 | sl = sparse_list.SparseList(5) 52 | sl[1], sl[3] = 'a', 'b' 53 | assert [None, 'a', None, 'b', None] == list(sl) 54 | 55 | def test_membership_absent(self): 56 | sl = sparse_list.SparseList(5) 57 | sl[2], sl[3], = 1, 2 58 | assert False == (3 in sl) 59 | 60 | def test_membership_present(self): 61 | sl = sparse_list.SparseList(5) 62 | sl[2], sl[3], = 1, 2 63 | assert True == (2 in sl) 64 | 65 | def test_string_representations(self): 66 | sl = sparse_list.SparseList(5, 0) 67 | sl[3], sl[4] = 5, 6 68 | assert '[0, 0, 0, 5, 6]' == repr(sl) 69 | assert '[0, 0, 0, 5, 6]' == str(sl) 70 | 71 | def test_initialisation_by_dict(self): 72 | sl = sparse_list.SparseList({ 73 | 4: 6, 74 | 3: 5, 75 | }, 0) 76 | assert [0, 0, 0, 5, 6] == sl 77 | assert 2 == sl.population() 78 | 79 | def test_initialisation_by_dict_does_not_add_defaults(self): 80 | sl = sparse_list.SparseList({ 81 | 3: 0, 82 | 4: 6, 83 | }, 0) 84 | assert [0, 0, 0, 0, 6] == sl 85 | assert 1 == sl.population() 86 | 87 | def test_initialisation_by_dict_with_non_numeric_key(self): 88 | with pytest.raises(ValueError): 89 | sparse_list.SparseList({'a': 5}) 90 | 91 | def test_initialisation_by_list(self): 92 | sl = sparse_list.SparseList([0, 1, 2, 4]) 93 | assert [0, 1, 2, 4] == sl 94 | assert 4 == sl.population() 95 | 96 | def test_initialisation_by_list_does_not_add_defaults(self): 97 | sl = sparse_list.SparseList([0, 1, 2, 4], 0) 98 | assert [0, 1, 2, 4] == sl 99 | assert 3 == sl.population() 100 | 101 | def test_initialisation_by_generator(self): 102 | gen = (x for x in (1, 2, 3)) 103 | sl = sparse_list.SparseList(gen) 104 | assert [1, 2, 3] == sl 105 | assert 3 == sl.population() 106 | 107 | def test_access_with_negative_index(self): 108 | sl = sparse_list.SparseList([0, 1, 2, 4]) 109 | assert 4 == sl[-1] 110 | 111 | def test_access_with_negative_index_with_no_value(self): 112 | sl = sparse_list.SparseList(5, 0) 113 | assert 0 == sl[-1] 114 | 115 | def test_slice(self): 116 | sl = sparse_list.SparseList([0, 1, 2, 4], 10) 117 | assert [1, 2] == sl[1:3] 118 | 119 | def test_slice_is_sparse_list(self): 120 | sl = sparse_list.SparseList([0, 1, 2, 4], 10) 121 | assert isinstance(sl[1:3], sparse_list.SparseList) 122 | 123 | def test_extended_slice(self): 124 | sl = sparse_list.SparseList([0, 1, 2, 3, 4, 5, 6]) 125 | assert [1, 3, 5] == sl[1:6:2] 126 | 127 | def test_extended_slice_is_sparse_list(self): 128 | sl = sparse_list.SparseList([0, 1, 2, 3, 4, 5, 6]) 129 | assert isinstance(sl[1:6:2], sparse_list.SparseList) 130 | 131 | def test_extended_slice_with_negative_stop(self): 132 | sl = sparse_list.SparseList([0, 1, 2, 3, 4, 5, 6]) 133 | assert [1, 3, 5] == sl[1:-1:2] 134 | 135 | def test_slice_reversal_full(self): 136 | sl = sparse_list.SparseList([1, 2, 3]) 137 | assert [3, 2, 1] == sl[::-1] 138 | 139 | def test_slice_reversal_empty(self): 140 | sl = sparse_list.SparseList(4) 141 | assert [None, None, None, None] == sl[::-1] 142 | 143 | def test_default_slice(self): 144 | sl = sparse_list.SparseList(23) 145 | sl[0:2] = (1, 2) 146 | assert [None, None] == sl[2:4] 147 | 148 | def test_slice_list_size(self): 149 | initial_size = 20 150 | sl = sparse_list.SparseList(initial_size) 151 | sample_tuple = (1, 2, 3, 4) 152 | sl[2:2+len(sample_tuple)] = sample_tuple 153 | assert len(sl) == initial_size 154 | 155 | def test_reversed(self): 156 | sl = sparse_list.SparseList([1, 2, 3]) 157 | assert [3, 2, 1] == list(reversed(sl)) 158 | 159 | def test_sorted(self): 160 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 161 | assert [0, 0, 0, 1, 1] == list(sorted(sl)) 162 | 163 | def test_get_out_of_bounds(self): 164 | sl = sparse_list.SparseList(1) 165 | assert sl[1] is None 166 | 167 | def test_set_out_of_bounds(self): 168 | sl = sparse_list.SparseList(1) 169 | sl[100] = 1 170 | assert 101 == len(sl) 171 | 172 | def test_present_item_removal(self): 173 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 174 | del sl[0] 175 | assert [0, 0, 0, 1] == sl 176 | assert 1 == sl.population() 177 | 178 | def test_missing_item_removal(self): 179 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 180 | del sl[1] 181 | assert [1, 0, 0, 1] == sl 182 | assert 2 == sl.population() 183 | 184 | def test_slice_removal(self): 185 | sl = sparse_list.SparseList(range(10)) 186 | del sl[3:5] 187 | assert [0, 1, 2, 5, 6, 7, 8, 9] == sl 188 | assert 8 == sl.population() 189 | 190 | def test_slice_removal_with_default_present(self): 191 | sl = sparse_list.SparseList(range(10), 0) 192 | del sl[3:5] 193 | assert [0, 1, 2, 5, 6, 7, 8, 9] == sl 194 | assert 7 == sl.population() 195 | 196 | def test_unbounded_head_slice_removal(self): 197 | sl = sparse_list.SparseList(range(10)) 198 | del sl[:3] 199 | assert [3, 4, 5, 6, 7, 8, 9] == sl 200 | assert 7 == sl.population() 201 | 202 | def test_unbounded_head_slice_removal_with_default_present(self): 203 | sl = sparse_list.SparseList(range(10), 0) 204 | del sl[:3] 205 | assert [3, 4, 5, 6, 7, 8, 9] == sl 206 | assert 7 == sl.population() 207 | 208 | def test_unbounded_tail_slice_removal(self): 209 | sl = sparse_list.SparseList(range(10), None) 210 | del sl[5:] 211 | assert [0, 1, 2, 3, 4] == sl 212 | assert 5 == sl.population() 213 | 214 | def test_stepped_slice_removal(self): 215 | sl = sparse_list.SparseList(range(6), None) 216 | del sl[::2] 217 | assert [1, 3, 5] == sl 218 | assert 3 == sl.population() 219 | 220 | def test_empty_removal(self): 221 | sl = sparse_list.SparseList(range(5), None) 222 | del sl[3:3] 223 | assert [0, 1, 2, 3, 4] == sl 224 | assert 5 == sl.population() 225 | 226 | def test_append(self): 227 | sl = sparse_list.SparseList(1, 0) 228 | sl.append(1) 229 | assert [0, 1] == sl 230 | assert 1 == sl.population() 231 | 232 | def test_clone(self): 233 | a = sparse_list.SparseList([1, 2, 3]) 234 | b = a[:] 235 | b.append(4) 236 | assert [1, 2, 3] == a 237 | assert [1, 2, 3, 4] == b 238 | assert a.population() + 1 == b.population() 239 | 240 | def test_concatenation(self): 241 | a = sparse_list.SparseList([1, 2, 3]) 242 | b = sparse_list.SparseList([4, 5, 6]) 243 | c = a + b 244 | assert [1, 2, 3] == a 245 | assert [4, 5, 6] == b 246 | assert [1, 2, 3, 4, 5, 6] == c 247 | assert a.population() + b.population() == c.population() 248 | 249 | def test_in_place_concatenation(self): 250 | a = sparse_list.SparseList([1, 2, 3]) 251 | b = sparse_list.SparseList([4, 5, 6]) 252 | a += b 253 | assert [1, 2, 3, 4, 5, 6] == a 254 | assert [4, 5, 6] == b 255 | assert 6 == a.population() 256 | 257 | def test_equality(self): 258 | a = sparse_list.SparseList([1, 2, 3]) 259 | b = sparse_list.SparseList([1, 2, 3]) 260 | assert a == b 261 | assert not a != b 262 | assert a == b 263 | assert b == a 264 | assert not b != a 265 | assert b == a 266 | 267 | def test_inequality_same_length(self): 268 | a = sparse_list.SparseList([1, 2, 3]) 269 | b = sparse_list.SparseList([1, 0, 3]) 270 | assert a != b 271 | assert not a == b 272 | assert a != b 273 | assert b != a 274 | assert not b == a 275 | assert b != a 276 | 277 | def test_inequality_left_longer(self): 278 | a = sparse_list.SparseList([1, 2, 3, 4]) 279 | b = sparse_list.SparseList([1, 2, 3]) 280 | assert a != b 281 | assert not (a == b) 282 | assert a != b 283 | assert b != a 284 | assert not (b == a) 285 | assert b != a 286 | 287 | def test_inequality_length(self): 288 | a = sparse_list.SparseList(2) 289 | b = sparse_list.SparseList(4) 290 | assert a != b 291 | assert not (a == b) 292 | assert a != b 293 | assert b != a 294 | assert not (b == a) 295 | assert b != a 296 | 297 | def test_less_than(self): 298 | a = sparse_list.SparseList([1, 2, 3, 0]) 299 | b = sparse_list.SparseList([1, 2, 4, 5]) 300 | assert a < b 301 | assert not (a == b) 302 | assert not (a >= b) 303 | assert not (a > b) 304 | 305 | def test_greater_than(self): 306 | a = sparse_list.SparseList([1, 2, 3, 0]) 307 | b = sparse_list.SparseList([1, 2, 4, 5]) 308 | assert b > a 309 | assert not (b == a) 310 | assert not (b <= a) 311 | assert not (b < a) 312 | 313 | def test_less_than_with_a_pair_that_is_greater(self): 314 | a = sparse_list.SparseList([1, 2, 3]) 315 | b = sparse_list.SparseList([1, 0, 4]) 316 | assert not (a < b) 317 | assert not (a == b) 318 | assert b <= a 319 | assert b < a 320 | 321 | def test_less_than_prefix(self): 322 | a = sparse_list.SparseList([1, 2, 3]) 323 | b = sparse_list.SparseList([1, 2, 3, 4]) 324 | assert a < b 325 | assert not (a == b) 326 | assert not (b <= a) 327 | assert not (b < a) 328 | 329 | def test_less_than_different_lengths(self): 330 | a = sparse_list.SparseList([1, 2, 3, 4]) 331 | b = sparse_list.SparseList([2, 1, 3]) 332 | assert a < b 333 | assert not (a == b) 334 | assert not (b <= a) 335 | assert not (b < a) 336 | 337 | def test_multiply(self): 338 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 339 | sl4 = sl * 4 340 | assert [1, 0, 0, 0, 1] == sl 341 | assert [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1] == sl4 342 | assert len(sl) * 4 == len(sl4) 343 | assert sl.population() * 4 == sl4.population() 344 | 345 | def test_multiply_in_place(self): 346 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 347 | sl *= 4 348 | assert [1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1] == sl 349 | assert 8 == sl.population() 350 | 351 | def test_count_value(self): 352 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 353 | assert 2 == sl.count(1) 354 | 355 | def test_count_default_value(self): 356 | sl = sparse_list.SparseList(100, 1) 357 | sl[5] = 1 358 | assert 100 == sl.count(1) 359 | 360 | def test_extend(self): 361 | sl = sparse_list.SparseList([1, 2, 3]) 362 | sl.extend((4, 5, 6)) 363 | assert [1, 2, 3, 4, 5, 6] == sl 364 | 365 | def test_index_value(self): 366 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 367 | assert 0 == sl.index(1) 368 | 369 | def test_index_default_value(self): 370 | sl = sparse_list.SparseList({0: 1, 4: 1}, 0) 371 | assert 1 == sl.index(0) 372 | 373 | def test_index_absent_default_value(self): 374 | sl = sparse_list.SparseList([1, 2, 3], 0) 375 | with pytest.raises(ValueError): 376 | sl.index(0) 377 | 378 | def test_index_absent_value(self): 379 | sl = sparse_list.SparseList(1, 0) 380 | with pytest.raises(ValueError): 381 | sl.index(2) 382 | 383 | def test_pop_no_value(self): 384 | sl = sparse_list.SparseList(4) 385 | assert sl.pop() is None 386 | 387 | def test_pop_empty(self): 388 | sl = sparse_list.SparseList(0) 389 | with pytest.raises(IndexError): 390 | sl.pop() 391 | 392 | def test_pop_value(self): 393 | sl = sparse_list.SparseList([1, 2, 3]) 394 | popped = sl.pop() 395 | assert 3 == popped 396 | assert 2 == len(sl) 397 | assert [1, 2] == sl 398 | assert 2 == sl.population() 399 | 400 | def test_push_value(self): 401 | sl = sparse_list.SparseList([1, 2, 3]) 402 | sl.push(4) 403 | assert 4 == len(sl) 404 | assert [1, 2, 3, 4] == sl 405 | assert 4 == sl.population() 406 | 407 | def test_remove_value(self): 408 | sl = sparse_list.SparseList([1, 2, 3]) 409 | sl.remove(2) 410 | assert 3 == len(sl) 411 | assert [1, None, 3] == sl 412 | assert 2 == sl.population() 413 | 414 | def test_remove_only_first_value(self): 415 | sl = sparse_list.SparseList([2, 2, 3]) 416 | sl.remove(2) 417 | assert 3 == len(sl) 418 | assert [None, 2, 3] == sl 419 | assert 2 == sl.population() 420 | 421 | def test_remove_non_value(self): 422 | sl = sparse_list.SparseList([1, 2, 3]) 423 | with pytest.raises(ValueError): 424 | sl.remove(4) 425 | 426 | def test_remove_default_value_does_nothing(self): 427 | sl = sparse_list.SparseList(4, None) 428 | sl.remove(None) 429 | assert [None, None, None, None] == sl 430 | assert 0 == sl.population() 431 | 432 | def test_set_slice_observes_stop(self): 433 | sl = sparse_list.SparseList(4, None) 434 | sl[0:2] = [1, 2, 3] 435 | assert [1, 2, None, None] == sl 436 | assert 2 == sl.population() 437 | 438 | def test_set_slice_resizes(self): 439 | sl = sparse_list.SparseList(0, None) 440 | sl[4:] = [4, 5] 441 | assert [None, None, None, None, 4, 5] == sl 442 | assert len(sl) == 6 443 | assert 2 == sl.population() 444 | 445 | def test_set_slice_extends_past_end(self): 446 | sl = sparse_list.SparseList(5, None) 447 | sl[3:] = [6, 7, 8] 448 | assert [None, None, None, 6, 7, 8] == sl 449 | assert 3 == sl.population() 450 | 451 | def test_set_slice_with_step(self): 452 | sl = sparse_list.SparseList(6, None) 453 | sl[::2] = [1, 2, 3] 454 | assert [1, None, 2, None, 3, None] == sl 455 | assert 3 == sl.population() 456 | 457 | def test_setting_an_item_with_default_does_not_increase_population(self): 458 | sl = sparse_list.SparseList(6, None) 459 | sl[2] = None 460 | assert 6 == len(sl) 461 | assert 0 == sl.population() 462 | --------------------------------------------------------------------------------