├── .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 |
--------------------------------------------------------------------------------