├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── AUTHORS.rst ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── authors.rst ├── conf.py ├── contributing.rst ├── history.rst ├── index.rst ├── installation.rst ├── make.bat ├── modules.rst ├── prime_sieve.rst ├── readme.rst └── usage.rst ├── prime_sieve ├── __init__.py ├── array.py ├── base.py ├── list.py └── math_utils.py ├── requirements.txt ├── requirements_dev.txt ├── setup.cfg ├── setup.py ├── tests ├── __init__.py └── test_prime_sieve.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Prime Sieve version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # IDE settings 105 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.9 4 | - 3.8 5 | - 3.7 6 | - 3.6 7 | install: pip install -U tox-travis 8 | script: tox 9 | deploy: 10 | provider: pypi 11 | distributions: sdist bdist_wheel 12 | username: __token__ 13 | password: 14 | secure: 5CmibUAyYlkT2EysV+/BfqD3TwlXKLrN8as2S1Skk8Fmtvc5Dmc3xgvSHCT1hkwM4xzqaaEVNvBJicgMc0qK/iqH1hnJtPxOBC9qCDxkLXI+rzhj5ZG5/IhDEuuc1I7OnEZVncSr8UhUylJwyqANfrFz7M9Ewn6WrpczSJNPmay4v8X7C0dwu+guJKYjGW0uky9dGSrppv5EphQOQ5c7tRRz5rW78KFq0TByU3HS4G8cqGPRUrFkJtOoMxQyDeFAsqc+BVTA8UUaOwCwy1yy2SG9CsJ/MsTiiurU47cfmu3pHpyl5tAIFiFFImvYZR4EN9mtOeanEDeJQIknM+sxH4ngwpYV7pWGRzEINDXTUEdjSeDsxOCPTC7GzNZTB3Em/ajcVqd4UIJM9C3uK+qJnnaXRQiKROeU0wF9RRKxzoqnlNcT8bJcfWqT7LexTtar3ELMAj9OT7/OWp7lpOjWgzH/haUGNWG9rhp1a7kFeDUilIDmr4oCERjM43ITNlPza1ZvQw3pmwJucOKvLaDumzQMNSlSBKHZU0berZb3hhI8o0fHSTsTlaSnXQO+KWy8eWfzybchxlIooyGSz7bzlC7ZvvywEbxLv5JCsUlmiypw4JckwWf4rYOCh8cyKpXrVVovwOAio5fX/QuAcznT6XbTY6TkfiiOK70vKHLNqEA= 15 | on: 16 | tags: true 17 | repo: mCodingLLC/prime_sieve 18 | python: 3.9 19 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * James Murphy 9 | 10 | Contributors 11 | ------------ 12 | 13 | None yet. Why not be the first? 14 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/mCodingLLC/prime_sieve/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | Prime Sieve could always use more documentation, whether as part of the 42 | official Prime Sieve docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/mCodingLLC/prime_sieve/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `prime_sieve` for local development. 61 | 62 | 1. Fork the `prime_sieve` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/prime_sieve.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv prime_sieve 70 | $ cd prime_sieve/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 prime_sieve tests 83 | $ python setup.py test or pytest 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 3.6, 3.7, 3.8, and 3.9, and for PyPy. Check 106 | https://travis-ci.com/mCodingLLC/prime_sieve/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ pytest tests.test_prime_sieve 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bump2version patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2021-05-02) 6 | ------------------ 7 | 8 | * First release on PyPI. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, James Murphy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | from urllib.request import pathname2url 8 | 9 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 10 | endef 11 | export BROWSER_PYSCRIPT 12 | 13 | define PRINT_HELP_PYSCRIPT 14 | import re, sys 15 | 16 | for line in sys.stdin: 17 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 18 | if match: 19 | target, help = match.groups() 20 | print("%-20s %s" % (target, help)) 21 | endef 22 | export PRINT_HELP_PYSCRIPT 23 | 24 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 25 | 26 | help: 27 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 28 | 29 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 30 | 31 | clean-build: ## remove build artifacts 32 | rm -fr build/ 33 | rm -fr dist/ 34 | rm -fr .eggs/ 35 | find . -name '*.egg-info' -exec rm -fr {} + 36 | find . -name '*.egg' -exec rm -f {} + 37 | 38 | clean-pyc: ## remove Python file artifacts 39 | find . -name '*.pyc' -exec rm -f {} + 40 | find . -name '*.pyo' -exec rm -f {} + 41 | find . -name '*~' -exec rm -f {} + 42 | find . -name '__pycache__' -exec rm -fr {} + 43 | 44 | clean-test: ## remove test and coverage artifacts 45 | rm -fr .tox/ 46 | rm -f .coverage 47 | rm -fr htmlcov/ 48 | rm -fr .pytest_cache 49 | 50 | lint: ## check style with flake8 51 | flake8 prime_sieve tests 52 | 53 | test: ## run tests quickly with the default Python 54 | pytest 55 | 56 | test-all: ## run tests on every Python version with tox 57 | tox 58 | 59 | coverage: ## check code coverage quickly with the default Python 60 | coverage run --source prime_sieve -m pytest 61 | coverage report -m 62 | coverage html 63 | $(BROWSER) htmlcov/index.html 64 | 65 | docs: ## generate Sphinx HTML documentation, including API docs 66 | rm -f docs/prime_sieve.rst 67 | rm -f docs/modules.rst 68 | sphinx-apidoc -o docs/ prime_sieve 69 | $(MAKE) -C docs clean 70 | $(MAKE) -C docs html 71 | $(BROWSER) docs/_build/html/index.html 72 | 73 | servedocs: docs ## compile the docs watching for changes 74 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 75 | 76 | release: dist ## package and upload a release 77 | twine upload dist/* 78 | 79 | dist: clean ## builds source and wheel package 80 | python setup.py sdist 81 | python setup.py bdist_wheel 82 | ls -l dist 83 | 84 | install: clean ## install the package to the active Python's site-packages 85 | python setup.py install 86 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Prime Sieve 3 | =========== 4 | 5 | 6 | .. image:: https://img.shields.io/pypi/v/prime_sieve.svg 7 | :target: https://pypi.python.org/pypi/prime_sieve 8 | 9 | .. image:: https://travis-ci.com/mCodingLLC/prime_sieve.svg?branch=master 10 | :target: https://travis-ci.com/mCodingLLC/prime_sieve 11 | 12 | .. image:: https://readthedocs.org/projects/prime-sieve/badge/?version=latest 13 | :target: https://prime-sieve.readthedocs.io/en/latest/?badge=latest 14 | :alt: Documentation Status 15 | 16 | 17 | 18 | 19 | An understandable prime sieve implementation in numpy or pure python. 20 | The focus is providing a sieve that is easy to understand rather than the absolute fastest implementation. 21 | Though the numpy implementation is reasonably quick, 22 | being able to compute the first 100 million primes in 30 seconds on my mid-tier laptop. 23 | 24 | This library implements a version of the 25 | `segmented sieve of Eratosthenes `_. 26 | 27 | First take a look at the pure python implementation in ``_. 28 | Then see the numpy implementation in ``_. 29 | Sieve operations that are independent of the actual computation of primes, 30 | such as looking up the nth-prime number, 31 | are found in ``_. 32 | 33 | 34 | * Free software: MIT license 35 | * Documentation: https://prime-sieve.readthedocs.io. 36 | 37 | Usage 38 | ----- 39 | 40 | .. code-block:: python3 41 | 42 | # Use a numpy or pure python implementation 43 | from prime_sieve.array import PrimeArraySieve 44 | # from prime_sieve.list import PrimeListSieve 45 | 46 | sieve = PrimeArraySieve() 47 | # sieve = PrimeListSieve() 48 | 49 | print(sieve.nth_prime(0)) # 2 50 | print(sieve[4]) # 7 51 | print(sieve.index_of(7)) # 4 52 | 53 | print(sieve[:100]) # [2, 3, ..., 541] 54 | print(sieve[1:6]) # [3, 5, 7, 11, 13] 55 | 56 | print(86*97 in sieve) # False 57 | print(sieve.is_prime(2 ** 13 - 1)) # True 58 | 59 | # ranges are like python ranges, inclusive start, exclusive stop 60 | print(sieve.primes_in_range(10, 20)) # [11, 13, 17, 19] 61 | print(sieve.primes_in_range(10, 19)) # [11, 13, 17] 62 | 63 | print(sieve.count_primes_in_range(3, 7)) # 2 64 | print(sieve.count_primes_in_range(3, 8)) # 3 65 | 66 | print(sieve.next_prime_greater_than(100)) # 101 67 | print(sieve.next_prime_greater_than(101)) # 103 68 | 69 | print(sieve.prev_prime_less_than(8)) # 7 70 | print(sieve.prev_prime_less_than(7)) # 5 71 | 72 | print(sieve.count_primes_less_or_equal(10 ** 7)) # 664579 73 | 74 | for p in sieve.iter_all_primes(): # infinite loop 75 | print(p) 76 | 77 | # see sieve internals 78 | print(len(sieve)) # how many primes have currently been computed 79 | print(sieve.primes) # read-only view of already computed primes 80 | 81 | 82 | Credits 83 | ------- 84 | 85 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 86 | 87 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 88 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 89 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = prime_sieve 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # prime_sieve documentation build configuration file, created by 4 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another 16 | # directory, add these directories to sys.path here. If the directory is 17 | # relative to the documentation root, use os.path.abspath to make it 18 | # absolute, like shown here. 19 | # 20 | import os 21 | import sys 22 | sys.path.insert(0, os.path.abspath('..')) 23 | 24 | import prime_sieve 25 | 26 | # -- General configuration --------------------------------------------- 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | # 30 | # needs_sphinx = '1.0' 31 | 32 | # Add any Sphinx extension module names here, as strings. They can be 33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 34 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'Prime Sieve' 50 | copyright = "2021, James Murphy" 51 | author = "James Murphy" 52 | 53 | # The version info for the project you're documenting, acts as replacement 54 | # for |version| and |release|, also used in various other places throughout 55 | # the built documents. 56 | # 57 | # The short X.Y version. 58 | version = prime_sieve.__version__ 59 | # The full version, including alpha/beta/rc tags. 60 | release = prime_sieve.__version__ 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | html_theme = 'default' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a 89 | # theme further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output --------------------------------------- 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'prime_sievedoc' 104 | 105 | 106 | # -- Options for LaTeX output ------------------------------------------ 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, author, documentclass 128 | # [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'prime_sieve.tex', 131 | 'Prime Sieve Documentation', 132 | 'James Murphy', 'manual'), 133 | ] 134 | 135 | 136 | # -- Options for manual page output ------------------------------------ 137 | 138 | # One entry per manual page. List of tuples 139 | # (source start file, name, description, authors, manual section). 140 | man_pages = [ 141 | (master_doc, 'prime_sieve', 142 | 'Prime Sieve Documentation', 143 | [author], 1) 144 | ] 145 | 146 | 147 | # -- Options for Texinfo output ---------------------------------------- 148 | 149 | # Grouping the document tree into Texinfo files. List of tuples 150 | # (source start file, target name, title, author, 151 | # dir menu entry, description, category) 152 | texinfo_documents = [ 153 | (master_doc, 'prime_sieve', 154 | 'Prime Sieve Documentation', 155 | author, 156 | 'prime_sieve', 157 | 'An understandable prime sieve implementation in numpy or pure python.', 158 | 'Miscellaneous'), 159 | ] 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Prime Sieve's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | usage 11 | modules 12 | contributing 13 | authors 14 | history 15 | 16 | Indices and tables 17 | ================== 18 | * :ref:`genindex` 19 | * :ref:`modindex` 20 | * :ref:`search` 21 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install Prime Sieve, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install prime_sieve 16 | 17 | This is the preferred method to install Prime Sieve, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for Prime Sieve can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/mCodingLLC/prime_sieve 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OJL https://github.com/mCodingLLC/prime_sieve/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/mCodingLLC/prime_sieve 51 | .. _tarball: https://github.com/mCodingLLC/prime_sieve/tarball/master 52 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=prime_sieve 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | prime_sieve 2 | =========== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | prime_sieve 8 | -------------------------------------------------------------------------------- /docs/prime_sieve.rst: -------------------------------------------------------------------------------- 1 | prime\_sieve package 2 | ==================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | prime\_sieve.array module 8 | ------------------------- 9 | 10 | .. automodule:: prime_sieve.array 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | prime\_sieve.base module 16 | ------------------------ 17 | 18 | .. automodule:: prime_sieve.base 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | prime\_sieve.list module 24 | ------------------------ 25 | 26 | .. automodule:: prime_sieve.list 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | prime\_sieve.math\_utils module 32 | ------------------------------- 33 | 34 | .. automodule:: prime_sieve.math_utils 35 | :members: 36 | :undoc-members: 37 | :show-inheritance: 38 | 39 | 40 | Module contents 41 | --------------- 42 | 43 | .. automodule:: prime_sieve 44 | :members: 45 | :undoc-members: 46 | :show-inheritance: 47 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | ===== 2 | Usage 3 | ===== 4 | 5 | 6 | .. code-block:: python3 7 | 8 | # Use a numpy or pure python implementation 9 | from prime_sieve.array import PrimeArraySieve 10 | # from prime_sieve.list import PrimeListSieve 11 | 12 | sieve = PrimeArraySieve() 13 | # sieve = PrimeListSieve() 14 | 15 | print(sieve.nth_prime(0)) # 2 16 | print(sieve[4]) # 7 17 | print(sieve.index_of(7)) # 4 18 | 19 | print(sieve[:100]) # [2, 3, ..., 541] 20 | print(sieve[1:6]) # [3, 5, 7, 11, 13] 21 | 22 | print(86*97 in sieve) # False 23 | print(sieve.is_prime(2 ** 13 - 1)) # True 24 | 25 | # ranges are like python ranges, inclusive start, exclusive stop 26 | print(sieve.primes_in_range(10, 20)) # [11, 13, 17, 19] 27 | print(sieve.primes_in_range(10, 19)) # [11, 13, 17] 28 | 29 | print(sieve.count_primes_in_range(3, 7)) # 2 30 | print(sieve.count_primes_in_range(3, 8)) # 3 31 | 32 | print(sieve.next_prime_greater_than(100)) # 101 33 | print(sieve.next_prime_greater_than(101)) # 103 34 | 35 | print(sieve.prev_prime_less_than(8)) # 7 36 | print(sieve.prev_prime_less_than(7)) # 5 37 | 38 | print(sieve.count_primes_less_or_equal(10 ** 7)) # 664579 39 | 40 | for p in sieve.iter_all_primes(): # infinite loop 41 | print(p) 42 | 43 | # see sieve internals 44 | print(len(sieve)) # how many primes have currently been computed 45 | print(sieve.primes) # read-only view of already computed primes 46 | -------------------------------------------------------------------------------- /prime_sieve/__init__.py: -------------------------------------------------------------------------------- 1 | """Top-level package for Prime Sieve.""" 2 | 3 | __author__ = """James Murphy""" 4 | __email__ = 'james@mcoding.io' 5 | __version__ = '0.1.11' 6 | -------------------------------------------------------------------------------- /prime_sieve/array.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from prime_sieve.base import SegmentedPrimeSieve 4 | from prime_sieve.math_utils import smallest_multiple_of_n_geq_m 5 | 6 | 7 | class PrimeArraySieve(SegmentedPrimeSieve): 8 | """ 9 | A numpy array implementation of a segmented prime sieve. 10 | It is relatively fast, but subject to possible overflow if you are working with very many primes. 11 | """ 12 | 13 | def __init__(self, dtype=np.uint64): 14 | self._primes: np.ndarray = np.array([2, 3, 5, 7], dtype=dtype) 15 | self.end_segment: int = 1 16 | self.dtype = dtype 17 | self.dtype_max = np.iinfo(dtype).max 18 | self.extend_at_most_n_segments_target: int = 100 19 | 20 | @property 21 | def primes(self): 22 | return self._primes 23 | 24 | def _extend_at_most_n_segments(self, n: int) -> None: 25 | k = self.end_segment 26 | n = min(n, len(self._primes) - 1 - k) 27 | p, q = int(self._primes[k]), int(self._primes[k + n]) 28 | segment_min, segment_max = p * p, q * q - 1 29 | if segment_max > self.dtype_max: 30 | raise RuntimeError("overflow, use a larger dtype or pure python implementation") 31 | 32 | segment_len = segment_max - segment_min + 1 33 | is_prime = np.full(shape=segment_len, fill_value=True, dtype=bool) 34 | 35 | for pk in self._primes[:k + n]: 36 | pk = int(pk) 37 | start = smallest_multiple_of_n_geq_m(pk, segment_min) 38 | is_prime[start - segment_min::pk] = False 39 | 40 | segment = np.arange(p * p, q * q, dtype=self.dtype) 41 | new_primes = segment[is_prime] 42 | try: 43 | old_len = len(self._primes) 44 | self._primes.resize(old_len + len(new_primes)) 45 | self._primes[old_len:] = new_primes 46 | except ValueError: 47 | self._primes = np.concatenate((self._primes, new_primes)) 48 | 49 | self.end_segment += n 50 | 51 | def _extend(self) -> None: 52 | self._extend_at_most_n_segments(self.extend_at_most_n_segments_target) 53 | -------------------------------------------------------------------------------- /prime_sieve/base.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from bisect import bisect_right, bisect_left 3 | from itertools import count 4 | 5 | 6 | class SegmentedPrimeSieve(ABC): 7 | """ 8 | An abstract segmented prime sieve. It can compute primes in chunks (segments) and return the so-far-computed primes 9 | at any time, or do prime finding or counting related operations. 10 | The values of the primes are stored for the duration of the object, so multiple computations potentially 11 | requiring further primes to be computed will be accelerated if the object is kept alive. 12 | The stored primes are guaranteed to be all the primes in the range [2, p_k^2) for some k, referred to as the end segment index. 13 | """ 14 | 15 | @property 16 | @abstractmethod 17 | def primes(self): 18 | """ 19 | A read-only view of the currently computed primes. Do not modify the return value, make a copy first if needed. 20 | 21 | :return: A sequence-like object of primes 22 | """ 23 | pass 24 | 25 | @abstractmethod 26 | def _extend(self) -> None: 27 | """ 28 | Extends the currently computed primes with the primes in the range [p_k^2, p_{k+n}^2) for some n, 29 | where k is the end segment index. 30 | Note: the range [p_k^2, p_{k+n}^2) is sieved by [p_0, ..., p_{k+n-1}] 31 | because if a composite number n has all prime factors >= p_{k+n}, then n >= p_{k+n}^2. 32 | Precondition: primes contains all primes in the range [2, p_k^2) and no more. 33 | Postcondition: primes contains all primes in the range [2, p_{k+n}^2) for some n and no more. 34 | """ 35 | pass 36 | 37 | def __contains__(self, item) -> bool: 38 | """ 39 | Returns whether item is prime, computing new primes as necessary in order to check. 40 | Note: a sieve is not meant to be a primality checker, there are much faster ways to check primality than using a sieve. 41 | 42 | :param item: An int to check whether it is prime. 43 | :return: Whether item is prime. 44 | """ 45 | return self.is_prime(item) 46 | 47 | def __len__(self) -> int: 48 | """ 49 | The number of primes that have been computed and are stored so far. 50 | 51 | :return: The length of the internal storage of primes. 52 | """ 53 | return len(self.primes) 54 | 55 | def __getitem__(self, item): 56 | """ 57 | Gets a prime by index starting with p_0 = 2, p_1 = 3, etc. 58 | Also accepts slices of indices. 59 | Primes will be computed as necessary. 60 | 61 | :param item: Either an int or slice of indices of primes to get. 62 | :return: Either the prime at that index or the slice of precomputed primes with the sliced indices. 63 | """ 64 | if isinstance(item, int): 65 | return self.nth_prime(item) 66 | elif isinstance(item, slice): 67 | self.ensure_len_greater_or_equal(item.stop) 68 | return self.primes[item] 69 | 70 | def iter_all_primes(self): 71 | """ 72 | Yield all prime numbers starting at 2. The primes are computed as needed. 73 | 74 | :return A generator yielding all primes: 75 | """ 76 | for idx in count(): 77 | yield self.nth_prime(idx) 78 | 79 | def is_prime(self, n: int): 80 | """ 81 | Returns whether n is prime, computing new primes as necessary in order to check. 82 | Note: a sieve is not meant to be a primality checker, there are much faster ways to check primality than using a sieve. 83 | 84 | :param n: An int to check whether it is prime. 85 | :return: Whether n is prime. 86 | """ 87 | return self.next_prime_greater_than(n - 1) == n 88 | 89 | def nth_prime(self, n: int): 90 | """ 91 | Returns the nth prime number, starting with p_0 = 2, p_1 = 3, etc. 92 | Primes are computed as necessary. 93 | 94 | :param n: The zero-based index of the prime to compute. 95 | :return: The nth prime number. 96 | """ 97 | self.ensure_len_greater_or_equal(n + 1) 98 | return self.primes[n] 99 | 100 | def primes_in_range(self, n: int, m: int): 101 | """ 102 | Returns a read-only view of primes in range(n,m), i.e. primes p with n <= p < m. 103 | Primes are computed as necessary. 104 | Do not modify the contents of the returned range, make a copy if necessary. 105 | 106 | :param n: The lower bound (inclusive) of primes to compute. 107 | :param m: The upper bound (exclusive) of primes to compute. 108 | :return: A read-only sequence of prime p with n <= p < m. 109 | """ 110 | start = self.index_of_next_prime_greater_than(n - 1) 111 | end = self.index_of_prev_prime_less_than(m) + 1 112 | return self.primes[start:end] 113 | 114 | def index_of_next_prime_greater_than(self, n: int) -> int: 115 | """ 116 | Return the index i of the smallest prime p_i with p_i > n. 117 | Primes are computed as necessary. 118 | 119 | :param n: An integer. 120 | :return: the index i for which p_i is the smallest prime with p_i > n. 121 | """ 122 | self.ensure_max_greater_or_equal(n + 1) 123 | idx = bisect_right(self.primes, n) 124 | return idx 125 | 126 | def index_of(self, p: int) -> int: 127 | """ 128 | Return the index i of a prime p such that p = p_i. 129 | 130 | :return: The index i such that p = p_i 131 | """ 132 | idx = self.index_of_next_prime_greater_than(p - 1) 133 | if p != self.primes[idx]: 134 | raise ValueError("not a prime") 135 | return idx 136 | 137 | def next_prime_greater_than(self, n: int): 138 | """ 139 | Return the smallest prime strictly greater than a given number. 140 | Primes are computed as necessary. 141 | 142 | :param n: An integer. 143 | :return: The smallest prime p with p > n. 144 | """ 145 | idx = self.index_of_next_prime_greater_than(n) 146 | return self.primes[idx] 147 | 148 | def index_of_prev_prime_less_than(self, n: int) -> int: 149 | """ 150 | Return the index i of the largest prime p_i with p_i < n. 151 | Primes are computed as necessary. 152 | 153 | :param n: An integer. 154 | :return: the index i for which p_i is the largest prime with p_i < n. 155 | """ 156 | if n <= 2: 157 | raise ValueError 158 | self.ensure_max_greater_or_equal(n - 1) 159 | idx = bisect_left(self.primes, n) 160 | return idx - 1 161 | 162 | def prev_prime_less_than(self, n: int): 163 | """ 164 | Return the largest prime strictly less than a given number. 165 | Primes are computed as necessary. 166 | 167 | :param n: An integer. 168 | :return: The largest prime p with p < n. 169 | """ 170 | idx = self.index_of_prev_prime_less_than(n) 171 | return self.primes[idx] 172 | 173 | def count_primes_less_or_equal(self, n: int) -> int: 174 | """ 175 | Count the number of primes less than or equal to a given bound. 176 | Primes are computed as necessary. 177 | 178 | :param n: An integer. 179 | :return: The number of primes less than or equal to n., commonly referred to as pi(n). 180 | """ 181 | self.ensure_max_greater_or_equal(n) 182 | return bisect_right(self.primes, n) 183 | 184 | def count_primes_in_range(self, n: int, m: int) -> int: 185 | """ 186 | Count the number of primes in range(n, m), i.e. the number of primes p with n <= p < m. 187 | Primes are computed as necessary. 188 | 189 | :param n: The lower bound. 190 | :param m: The upper bound. 191 | :return: The number of primes p with n <= p < m. 192 | """ 193 | start = self.index_of_next_prime_greater_than(n - 1) 194 | end = self.index_of_prev_prime_less_than(m) 195 | return max(0, end - start + 1) 196 | 197 | def ensure_len_greater_or_equal(self, n: int) -> None: 198 | """ 199 | Precompute primes until at least n primes have been computed. 200 | Afterwards, it is guaranteed than the length of self is at least n. 201 | Most likely this will cause more than n primes to be computed. 202 | 203 | :param n: The number of primes to ensure are computed. 204 | :return: None 205 | """ 206 | while len(self.primes) < n: 207 | self._extend() 208 | 209 | def ensure_max_greater_or_equal(self, n: int) -> None: 210 | """ 211 | Precompute primes until the largest computed prime is at least n. 212 | Afterwards, it is guaranteed that that self.primes[-1] >= n. 213 | Most likely, afterwards self.primes[-1] > n even if n is prime. 214 | 215 | :param n: An integer. 216 | :return: None 217 | """ 218 | while self.primes[-1] < n: 219 | self._extend() 220 | 221 | def find_primes_until(self, stop_cb, progress_cb=None) -> None: 222 | """ 223 | Compute more and more primes until a told to stop by the stop callback. 224 | 225 | :param stop_cb: A callable accepting this object as its only argument whose return value's truthiness indicates 226 | whether to stop computing. 227 | :param progress_cb: A callable accepting this object as its only argument that will be called for each segment 228 | of primes computed. 229 | :return: None 230 | """ 231 | while not stop_cb(self): 232 | self._extend() 233 | if progress_cb is not None: 234 | progress_cb(self) 235 | -------------------------------------------------------------------------------- /prime_sieve/list.py: -------------------------------------------------------------------------------- 1 | from itertools import repeat 2 | 3 | from prime_sieve.base import SegmentedPrimeSieve 4 | from prime_sieve.math_utils import smallest_multiple_of_n_geq_m 5 | 6 | 7 | class PrimeListSieve(SegmentedPrimeSieve): 8 | """ 9 | A pure Python implementation of a segmented prime sieve. 10 | It is much slower than the numpy implementation, but has no dependencies and no possibility of overflow. 11 | """ 12 | 13 | def __init__(self): 14 | self._primes: list[int] = [2, 3, 5, 7] 15 | self.end_segment: int = 1 16 | self.extend_at_most_n_segments_target: int = 10 17 | 18 | @property 19 | def primes(self): 20 | return self._primes 21 | 22 | def _extend_at_most_n_segments(self, n: int) -> None: 23 | k = self.end_segment 24 | n = min(n, len(self._primes) - 1 - k) 25 | p, q = self._primes[k], self._primes[k + n] 26 | segment = range(p * p, q * q) 27 | segment_min = min(segment) 28 | segment_len = len(segment) 29 | is_prime = [True] * segment_len 30 | 31 | for i in range(k + n): 32 | pk = self._primes[i] 33 | start = smallest_multiple_of_n_geq_m(pk, segment_min) 34 | is_prime[start - segment_min::pk] = repeat(False, len(range(start - segment_min, segment_len, pk))) 35 | 36 | self._primes.extend([x for x, it_is_prime in zip(segment, is_prime) if it_is_prime]) 37 | self.end_segment += n 38 | 39 | def _extend(self) -> None: 40 | self._extend_at_most_n_segments(self.extend_at_most_n_segments_target) 41 | -------------------------------------------------------------------------------- /prime_sieve/math_utils.py: -------------------------------------------------------------------------------- 1 | def smallest_multiple_of_n_geq_m(n: int, m: int) -> int: 2 | """ 3 | Returns the smallest multiple of n greater than or equal to m. 4 | 5 | :param n: A strictly positive integer. 6 | :param m: A non-negative integer. 7 | :return: The smallest multiple of n that is greater or equal to m. 8 | """ 9 | return m + -m % n 10 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | # pip==19.2.3 2 | bump2version==0.5.11 3 | wheel==0.33.6 4 | watchdog==0.9.0 5 | flake8==3.7.8 6 | tox==3.14.0 7 | coverage==4.5.4 8 | Sphinx==1.8.5 9 | twine==1.14.0 10 | 11 | pytest==4.6.5 12 | pytest-runner==5.1 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.1.11 3 | commit = True 4 | tag = True 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:prime_sieve/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | max-line-length = 160 19 | exclude = docs 20 | 21 | [aliases] 22 | test = pytest 23 | 24 | [tool:pytest] 25 | collect_ignore = ['setup.py'] 26 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """The setup script.""" 4 | 5 | from setuptools import setup, find_packages 6 | 7 | with open('README.rst') as readme_file: 8 | readme = readme_file.read() 9 | 10 | with open('HISTORY.rst') as history_file: 11 | history = history_file.read() 12 | 13 | requirements = ['numpy', ] 14 | 15 | setup_requirements = ['pytest-runner', ] 16 | 17 | test_requirements = ['pytest>=3', ] 18 | 19 | setup( 20 | author="James Murphy", 21 | author_email='james@mcoding.io', 22 | python_requires='>=3.6', 23 | classifiers=[ 24 | 'Development Status :: 2 - Pre-Alpha', 25 | 'Intended Audience :: Developers', 26 | 'License :: OSI Approved :: MIT License', 27 | 'Natural Language :: English', 28 | 'Programming Language :: Python :: 3', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Programming Language :: Python :: 3.7', 31 | 'Programming Language :: Python :: 3.8', 32 | 'Programming Language :: Python :: 3.9', 33 | ], 34 | description="An understandable prime sieve implementation in numpy or pure python.", 35 | install_requires=requirements, 36 | license="MIT license", 37 | long_description=readme + '\n\n' + history, 38 | include_package_data=True, 39 | keywords='prime_sieve', 40 | name='prime_sieve', 41 | packages=find_packages(include=['prime_sieve', 'prime_sieve.*']), 42 | setup_requires=setup_requirements, 43 | test_suite='tests', 44 | tests_require=test_requirements, 45 | url='https://github.com/mCodingLLC/prime_sieve', 46 | version='0.1.11', 47 | zip_safe=False, 48 | ) 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Unit test package for prime_sieve.""" 2 | -------------------------------------------------------------------------------- /tests/test_prime_sieve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Tests for `prime_sieve` package.""" 4 | 5 | import numpy as np 6 | import pytest 7 | from prime_sieve.array import PrimeArraySieve 8 | from prime_sieve.base import SegmentedPrimeSieve 9 | from prime_sieve.list import PrimeListSieve 10 | from prime_sieve.math_utils import smallest_multiple_of_n_geq_m 11 | 12 | first_100_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 13 | 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 14 | 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 15 | 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 16 | 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 17 | 487, 491, 499, 503, 509, 521, 523, 541] 18 | 19 | 20 | @pytest.fixture(params=[PrimeArraySieve, PrimeListSieve]) 21 | def sieve(request) -> SegmentedPrimeSieve: 22 | return request.param() 23 | 24 | 25 | @pytest.mark.parametrize("test_input,expected", 26 | [((2, 3), 4), 27 | ((3, 2), 3), 28 | ((2, 4), 4), 29 | ((3, 3), 3), 30 | ((3, 6), 6), 31 | ((3, 7), 9)]) 32 | def test_smallest_multiple_of_n_geq_m(test_input, expected): 33 | assert smallest_multiple_of_n_geq_m(*test_input) == expected 34 | 35 | 36 | @pytest.mark.parametrize("test_input,expected", enumerate(first_100_primes)) 37 | def test_nth_prime(test_input, expected, sieve): 38 | """ 39 | See for expected answers: 40 | https://en.wikipedia.org/wiki/List_of_prime_numbers 41 | """ 42 | assert sieve.nth_prime(test_input) == expected 43 | 44 | 45 | @pytest.mark.parametrize("test_input,expected", enumerate(first_100_primes)) 46 | def test_getitem_nth_prime(test_input, expected, sieve): 47 | assert sieve[test_input] == expected 48 | 49 | 50 | @pytest.mark.parametrize("test_input", [slice(0, 10), slice(4, 19), slice(4, 4), slice(6, 5), slice(5, 20)]) 51 | def test_getitem_slice(test_input, sieve): 52 | assert list(sieve[test_input]) == first_100_primes[test_input] 53 | 54 | 55 | @pytest.mark.parametrize("test_input,expected", 56 | [(0, False), 57 | (1, False), 58 | (2, True), 59 | (3, True), 60 | (4, False), 61 | (5, True), 62 | (6, False), 63 | (97, True), 64 | (100, False), 65 | (100 * 200, False), 66 | (86 * 97, False), 67 | (2 ** 11 - 1, False), 68 | (2 ** 13 - 1, True)]) 69 | def test_is_prime(test_input, expected, sieve): 70 | assert sieve.is_prime(test_input) == expected 71 | 72 | 73 | @pytest.mark.parametrize("test_input,expected", 74 | [(0, False), 75 | (1, False), 76 | (2, True), 77 | (3, True), 78 | (4, False), 79 | (5, True), 80 | (6, False), 81 | (97, True), 82 | (100, False), 83 | (100 * 200, False), 84 | (86 * 97, False), 85 | (2 ** 11 - 1, False), 86 | (2 ** 13 - 1, True)]) 87 | def test_is_prime__contains__alias(test_input, expected, sieve): 88 | assert (test_input in sieve) == expected 89 | 90 | 91 | @pytest.mark.parametrize("test_input,expected", 92 | [((0, 5), [2, 3]), 93 | ((2, 5), [2, 3]), 94 | ((2, 6), [2, 3, 5]), 95 | ((10, 50), [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]), 96 | ((2, 100), first_100_primes[:25]) 97 | ]) 98 | def test_primes_in_range(test_input, expected, sieve): 99 | assert list(sieve.primes_in_range(*test_input)) == expected 100 | 101 | 102 | @pytest.mark.parametrize("test_input,expected", 103 | [(p, i + 1) for i, p in enumerate(first_100_primes)] + 104 | [(12, 5), 105 | (13, 6), 106 | (14, 6), 107 | (15, 6), 108 | (16, 6), 109 | (17, 7)]) 110 | def test_index_of_next_prime_greater_than(test_input, expected, sieve): 111 | assert sieve.index_of_next_prime_greater_than(test_input) == expected 112 | 113 | 114 | @pytest.mark.parametrize("test_input,expected", 115 | [(0, 2), 116 | (1, 2), 117 | (2, 3), 118 | (3, 5), 119 | (4, 5), 120 | (100, 101), 121 | (104, 107), 122 | (107, 109), 123 | (7907, 7919)]) 124 | def test_next_prime_greater_than(test_input, expected, sieve): 125 | assert sieve.next_prime_greater_than(test_input) == expected 126 | 127 | 128 | @pytest.mark.parametrize("test_input,expected", 129 | [(12, 4), 130 | (13, 4), 131 | (14, 5), 132 | (15, 5), 133 | (16, 5), 134 | (17, 5), 135 | (18, 6)]) 136 | def test_index_of_prev_prime_less_than(test_input, expected, sieve): 137 | assert sieve.index_of_prev_prime_less_than(test_input) == expected 138 | 139 | 140 | def test_index_of_prev_prime_less_than_error(sieve): 141 | with pytest.raises(ValueError): 142 | sieve.index_of_prev_prime_less_than(2) 143 | 144 | 145 | @pytest.mark.parametrize("test_input,expected", 146 | [(3, 2), 147 | (4, 3), 148 | (5, 3), 149 | (6, 5), 150 | (7, 5), 151 | (8, 7), 152 | (101, 97), 153 | (104, 103), 154 | (109, 107), 155 | (7907, 7901)]) 156 | def test_prev_prime_less_than(test_input, expected, sieve): 157 | assert sieve.prev_prime_less_than(test_input) == expected 158 | 159 | 160 | @pytest.mark.parametrize("test_input,expected", 161 | [(1, 0), 162 | (2, 1), 163 | (3, 2), 164 | (4, 2), 165 | (5, 3), 166 | (6, 3), 167 | (7, 4), 168 | (8, 4), 169 | (9, 4), 170 | (10, 4), 171 | (10 ** 2, 25), 172 | (10 ** 3, 168), 173 | (10 ** 4, 1229), 174 | (10 ** 5, 9592), 175 | (10 ** 6, 78498), 176 | (10 ** 7, 664579)]) 177 | def test_count_primes_less_or_equal(test_input, expected, sieve): 178 | """ 179 | See for expected answers: 180 | https://en.wikipedia.org/wiki/Prime-counting_function 181 | """ 182 | assert sieve.count_primes_less_or_equal(test_input) == expected 183 | 184 | 185 | @pytest.mark.parametrize("test_input,expected", 186 | [((4, 3), 0), 187 | ((3, 3), 0), 188 | ((2, 3), 1), 189 | ((2, 4), 2), 190 | ((0, 10), 4), 191 | ((2, 10), 4), 192 | ((3, 10), 3), 193 | ((3, 9), 3), 194 | ((3, 8), 3), 195 | ((3, 7), 2), 196 | ((1, 100), 25), ]) 197 | def test_count_primes_in_range(test_input, expected, sieve): 198 | assert sieve.count_primes_in_range(*test_input) == expected 199 | 200 | 201 | @pytest.mark.parametrize("test_input", [1, 10, 100, 100]) 202 | def test_ensure_len_greater_or_equal(test_input, sieve): 203 | sieve.ensure_len_greater_or_equal(test_input) 204 | assert len(sieve) >= test_input 205 | 206 | 207 | @pytest.mark.parametrize("test_input", [1, 10, 100, 1000]) 208 | def test_ensure_max_greater_or_equal(test_input, sieve): 209 | sieve.ensure_max_greater_or_equal(test_input) 210 | assert sieve[-1] >= test_input 211 | 212 | 213 | @pytest.mark.parametrize("dtype", [np.int8, np.uint8, np.int16, np.uint16]) 214 | def test_numpy_overflow_error(dtype): 215 | sieve = PrimeArraySieve(dtype) 216 | max = np.iinfo(dtype).max 217 | with pytest.raises(RuntimeError): 218 | sieve.ensure_max_greater_or_equal(max) 219 | 220 | 221 | def test_iter_all_primes(sieve): 222 | primes_found = [] 223 | for p in sieve.iter_all_primes(): 224 | primes_found.append(p) 225 | if p % 60 == 1: 226 | break 227 | assert primes_found == first_100_primes[:18] 228 | 229 | 230 | def test_find_primes_until(sieve): 231 | def stop_cb(s): 232 | return len(s) >= 100 233 | 234 | call_count = 0 235 | 236 | def progress_cb(s): 237 | nonlocal call_count 238 | call_count += 1 239 | 240 | sieve.find_primes_until(stop_cb, progress_cb) 241 | assert len(sieve) >= 100 242 | assert call_count > 0 243 | 244 | 245 | @pytest.mark.parametrize("expected,test_input", enumerate(first_100_primes)) 246 | def test_index_of_prime(test_input, expected, sieve): 247 | assert sieve.index_of(test_input) == expected 248 | 249 | 250 | @pytest.mark.parametrize("test_input", [0, 4, 6, 9, 25, 2 ** 10 + 2]) 251 | def test_index_of_not_prime_raises(test_input, sieve): 252 | with pytest.raises(ValueError): 253 | sieve.index_of(test_input) 254 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, py37, py38, py39, flake8 3 | 4 | [travis] 5 | python = 6 | 3.9: py39 7 | 3.8: py38 8 | 3.7: py37 9 | 3.6: py36 10 | 11 | [flake8] 12 | max-line-length = 160 13 | 14 | [testenv:flake8] 15 | basepython = python 16 | deps = flake8 17 | commands = flake8 prime_sieve tests 18 | 19 | [testenv] 20 | setenv = 21 | PYTHONPATH = {toxinidir} 22 | deps = 23 | -r{toxinidir}/requirements_dev.txt 24 | ; If you want to make tox run the tests with the same versions, create a 25 | ; requirements.txt with the pinned versions and uncomment the following line: 26 | ; -r{toxinidir}/requirements.txt 27 | commands = 28 | pip install -U pip 29 | pytest --basetemp={envtmpdir} 30 | 31 | --------------------------------------------------------------------------------