├── .coveragerc ├── .gitignore ├── .nojekyll ├── .pytest_cache └── v │ └── cache │ └── lastfailed ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── README.rst ├── RELEASENOTES.rst ├── docs └── html │ ├── .buildinfo │ ├── .nojekyll │ ├── _images │ └── nutsflow_logo.gif │ ├── _modules │ ├── index.html │ └── nutsflow │ │ ├── base.html │ │ ├── common.html │ │ ├── examples │ │ └── examples.html │ │ ├── factory.html │ │ ├── function.html │ │ ├── iterfunction.html │ │ ├── processor.html │ │ ├── sink.html │ │ └── source.html │ ├── _sources │ ├── contributions.rst.txt │ ├── index.rst.txt │ ├── installation.rst.txt │ ├── introduction.rst.txt │ ├── links.rst.txt │ ├── nutsflow.examples.rst.txt │ ├── nutsflow.rst.txt │ ├── overview.rst.txt │ └── tutorial │ │ ├── custom_nuts.rst.txt │ │ ├── debugging.rst.txt │ │ ├── divide_conquer.rst.txt │ │ ├── error_messages.rst.txt │ │ ├── exceptions.rst.txt │ │ ├── filtering.rst.txt │ │ ├── introduction.rst.txt │ │ ├── nuts_basics.rst.txt │ │ ├── performance.rst.txt │ │ ├── practice_problems.rst.txt │ │ ├── prerequisites.rst.txt │ │ ├── rearranging.rst.txt │ │ ├── recipes.rst.txt │ │ ├── sinks.rst.txt │ │ ├── sources.rst.txt │ │ ├── transforming.rst.txt │ │ └── underscore.rst.txt │ ├── _static │ ├── basic.css │ ├── css │ │ ├── badge_only.css │ │ ├── fonts │ │ │ ├── Roboto-Slab-Bold.woff │ │ │ ├── Roboto-Slab-Bold.woff2 │ │ │ ├── Roboto-Slab-Regular.woff │ │ │ ├── Roboto-Slab-Regular.woff2 │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ ├── fontawesome-webfont.woff2 │ │ │ ├── lato-bold-italic.woff │ │ │ ├── lato-bold-italic.woff2 │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-normal-italic.woff │ │ │ ├── lato-normal-italic.woff2 │ │ │ ├── lato-normal.woff │ │ │ └── lato-normal.woff2 │ │ └── theme.css │ ├── doctools.js │ ├── documentation_options.js │ ├── file.png │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── Lato │ │ │ ├── lato-bold.eot │ │ │ ├── lato-bold.ttf │ │ │ ├── lato-bold.woff │ │ │ ├── lato-bold.woff2 │ │ │ ├── lato-bolditalic.eot │ │ │ ├── lato-bolditalic.ttf │ │ │ ├── lato-bolditalic.woff │ │ │ ├── lato-bolditalic.woff2 │ │ │ ├── lato-italic.eot │ │ │ ├── lato-italic.ttf │ │ │ ├── lato-italic.woff │ │ │ ├── lato-italic.woff2 │ │ │ ├── lato-regular.eot │ │ │ ├── lato-regular.ttf │ │ │ ├── lato-regular.woff │ │ │ └── lato-regular.woff2 │ │ ├── Roboto-Slab-Bold.woff │ │ ├── Roboto-Slab-Bold.woff2 │ │ ├── Roboto-Slab-Light.woff │ │ ├── Roboto-Slab-Light.woff2 │ │ ├── Roboto-Slab-Regular.woff │ │ ├── Roboto-Slab-Regular.woff2 │ │ ├── Roboto-Slab-Thin.woff │ │ ├── Roboto-Slab-Thin.woff2 │ │ ├── RobotoSlab │ │ │ ├── roboto-slab-v7-bold.eot │ │ │ ├── roboto-slab-v7-bold.ttf │ │ │ ├── roboto-slab-v7-bold.woff │ │ │ ├── roboto-slab-v7-bold.woff2 │ │ │ ├── roboto-slab-v7-regular.eot │ │ │ ├── roboto-slab-v7-regular.ttf │ │ │ ├── roboto-slab-v7-regular.woff │ │ │ └── roboto-slab-v7-regular.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── lato-bold-italic.woff │ │ ├── lato-bold-italic.woff2 │ │ ├── lato-bold.woff │ │ ├── lato-bold.woff2 │ │ ├── lato-normal-italic.woff │ │ ├── lato-normal-italic.woff2 │ │ ├── lato-normal.woff │ │ └── lato-normal.woff2 │ ├── jquery-3.5.1.js │ ├── jquery.js │ ├── js │ │ ├── badge_only.js │ │ ├── html5shiv-printshiv.min.js │ │ ├── html5shiv.min.js │ │ ├── modernizr.min.js │ │ └── theme.js │ ├── language_data.js │ ├── minus.png │ ├── plus.png │ ├── pygments.css │ ├── searchtools.js │ ├── underscore-1.3.1.js │ └── underscore.js │ ├── contributions.html │ ├── genindex.html │ ├── index.html │ ├── installation.html │ ├── introduction.html │ ├── links.html │ ├── nutsflow.examples.html │ ├── nutsflow.html │ ├── objects.inv │ ├── overview.html │ ├── py-modindex.html │ ├── search.html │ ├── searchindex.js │ └── tutorial │ ├── custom_nuts.html │ ├── debugging.html │ ├── divide_conquer.html │ ├── error_messages.html │ ├── exceptions.html │ ├── filtering.html │ ├── introduction.html │ ├── nuts_basics.html │ ├── performance.html │ ├── practice_problems.html │ ├── prerequisites.html │ ├── rearranging.html │ ├── recipes.html │ ├── sinks.html │ ├── sources.html │ ├── transforming.html │ └── underscore.html ├── nutsflow ├── __init__.py ├── base.py ├── common.py ├── config.py ├── examples │ ├── __init__.py │ └── examples.py ├── factory.py ├── function.py ├── iterfunction.py ├── processor.py ├── sink.py ├── source.py └── underscore.py ├── origin) ├── pics └── nutsflow_logo.gif ├── push_docs ├── pylintrc ├── pytest.ini ├── setup.cfg ├── setup.py ├── sphinx ├── .nojekyll ├── Makefile └── source │ ├── conf.py │ ├── contributions.rst │ ├── index.rst │ ├── installation.rst │ ├── introduction.rst │ ├── links.rst │ ├── modules.rst │ ├── nutsflow.examples.rst │ ├── nutsflow.rst │ ├── overview.rst │ ├── pics │ ├── nuts.svg │ └── nutsflow_logo.gif │ └── tutorial │ ├── custom_nuts.rst │ ├── debugging.rst │ ├── divide_conquer.rst │ ├── error_messages.rst │ ├── exceptions.rst │ ├── filtering.rst │ ├── introduction.rst │ ├── nuts_basics.rst │ ├── performance.rst │ ├── practice_problems.rst │ ├── prerequisites.rst │ ├── rearranging.rst │ ├── recipes.rst │ ├── sinks.rst │ ├── sources.rst │ ├── transforming.rst │ └── underscore.rst └── tests ├── __init__.py ├── data ├── arab2num.csv ├── config.yaml ├── configuration.json ├── configuration.yaml ├── data.csv ├── data.tsv └── numbers.txt └── nutsflow ├── __init__.py ├── examples ├── __init__.py └── test_examples.py ├── test_base.py ├── test_common.py ├── test_config.py ├── test_factory.py ├── test_function.py ├── test_iterfunction.py ├── test_processor.py ├── test_sink.py ├── test_source.py └── test_underscore.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = tests/*,*/__init__.py,*/test_*,sphinx/source/conf.py,setup.py -------------------------------------------------------------------------------- /.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 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | *.whl 26 | *.tar.gz 27 | 28 | # Sphinx 29 | docs/doctrees/ 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Sphinx documentation 61 | docs/_build/ 62 | 63 | # PyBuilder 64 | target/ 65 | 66 | # IPython Notebook 67 | .ipynb_checkpoints 68 | 69 | # pyenv 70 | .python-version 71 | 72 | # virtualenv 73 | venv/ 74 | ENV/ 75 | 76 | # Spyder project settings 77 | .spyderproject 78 | 79 | # Pycharm 80 | .idea/ 81 | 82 | # Pickle files 83 | *.pkl 84 | *.npz 85 | *.npy 86 | 87 | # pytest 88 | .pytest_cache/ 89 | nodeids -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/.nojekyll -------------------------------------------------------------------------------- /.pytest_cache/v/cache/lastfailed: -------------------------------------------------------------------------------- 1 | { 2 | "nutsflow/.ipynb_checkpoints/processor-checkpoint.py": true, 3 | "nutsflow/.ipynb_checkpoints/source-checkpoint.py": true, 4 | "nutsflow/processor.py::nutsflow.processor.Append.__init__": true, 5 | "nutsflow/processor.py::nutsflow.processor.Chunk.__init__": true, 6 | "nutsflow/processor.py::nutsflow.processor.ChunkBy.__init__": true, 7 | "nutsflow/processor.py::nutsflow.processor.Clone.__init__": true, 8 | "nutsflow/processor.py::nutsflow.processor.Concat.__init__": true, 9 | "nutsflow/processor.py::nutsflow.processor.Drop.__init__": true, 10 | "nutsflow/processor.py::nutsflow.processor.DropWhile.__init__": true, 11 | "nutsflow/processor.py::nutsflow.processor.FilterCol.__init__": true, 12 | "nutsflow/processor.py::nutsflow.processor.Flatten.__init__": true, 13 | "nutsflow/processor.py::nutsflow.processor.FlattenCol.__init__": true, 14 | "nutsflow/processor.py::nutsflow.processor.GroupBy.__init__": true, 15 | "nutsflow/processor.py::nutsflow.processor.GroupBySorted.__init__": true, 16 | "nutsflow/processor.py::nutsflow.processor.If.__init__": true, 17 | "nutsflow/processor.py::nutsflow.processor.Insert.__init__": true, 18 | "nutsflow/processor.py::nutsflow.processor.Interleave.__init__": true, 19 | "nutsflow/processor.py::nutsflow.processor.MapCol.__init__": true, 20 | "nutsflow/processor.py::nutsflow.processor.MapMulti.__init__": true, 21 | "nutsflow/processor.py::nutsflow.processor.Pick.__init__": true, 22 | "nutsflow/processor.py::nutsflow.processor.Prefetch.__init__": true, 23 | "nutsflow/processor.py::nutsflow.processor.Shuffle.__init__": true, 24 | "nutsflow/processor.py::nutsflow.processor.Slice.__init__": true, 25 | "nutsflow/processor.py::nutsflow.processor.Take.__init__": true, 26 | "nutsflow/processor.py::nutsflow.processor.Try.__init__": true, 27 | "nutsflow/processor.py::nutsflow.processor.Window.__init__": true, 28 | "nutsflow/processor.py::nutsflow.processor.Zip.__init__": true, 29 | "nutsflow/processor.py::nutsflow.processor.ZipWith.__init__": true 30 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: python 3 | python: 4 | - "2.7" 5 | - "3.5" 6 | - "3.6" 7 | - "3.7" 8 | - "3.8" 9 | - "3.9" 10 | before_install: 11 | - pip install pytest coveralls pytest-cov==2.5.0 12 | - pip install six 13 | - pip install pyyaml # should not be needed 14 | install: 15 | - pip install nutsflow 16 | script: 17 | - py.test 18 | - coverage run --source=nutsflow setup.py test 19 | - coverage report --fail-under=100 20 | after_success: 21 | - coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright IBM Research Australia 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include tests/data * 2 | include README.rst 3 | include pics/nutsflow_logo.gif 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | .. image:: pics/nutsflow_logo.gif 5 | :align: center 6 | 7 | - `Introduction `_ 8 | - `Installation `_ 9 | - `Tutorial `_ 10 | - `Documentation `_ 11 | - `Github `_ 12 | 13 | **nuts-flow** is largely a thin wrapper around Python's *itertools* that allows 14 | the chaining of iterators using the ``>>`` operator. This leads to more 15 | readable code that highlights the flow of data. The following example shows 16 | two implementations of a simple data processing pipeline; the first based on 17 | *itertools* and the second using **nuts-flow**: 18 | 19 | >>> from itertools import islice 20 | >>> list(islice(filter(lambda x: x > 5, xrange(10)), 3)) 21 | [6, 7, 8] 22 | 23 | 24 | >>> from nutsflow import Range, Filter, Take, Collect, _ 25 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 26 | [6, 7, 8] 27 | 28 | Both examples extract the first three numbers within range [0, 9] 29 | that are greater than five. However, the **nuts-flow** pipeline 30 | is easier to understand than the nested *itertools* code. 31 | 32 | **nuts-flow** is the base for `nuts-ml `_, 33 | which is described `here `_ . 34 | 35 | 36 | .. image:: https://badge.fury.io/py/nutsflow.svg 37 | :target: https://badge.fury.io/py/nutsflow 38 | 39 | .. image:: https://img.shields.io/pypi/pyversions/nutsflow.svg 40 | :target: https://pypi.python.org/pypi/nutsflow/ 41 | 42 | .. image:: https://travis-ci.org/maet3608/nuts-flow.svg?branch=master 43 | :target: https://travis-ci.org/maet3608/nuts-flow 44 | 45 | .. image:: https://coveralls.io/repos/github/maet3608/nuts-flow/badge.svg?branch=master 46 | :target: https://coveralls.io/github/maet3608/nuts-flow?branch=master 47 | 48 | .. image:: https://img.shields.io/github/issues/maet3608/nuts-flow.svg 49 | :target: https://github.com/maet3608/nuts-flow/issues 50 | 51 | .. image:: https://img.shields.io/badge/license-Apache%202-blue.svg 52 | :target: https://github.com/maet3608/nuts-ml/blob/master/LICENSE 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /RELEASENOTES.rst: -------------------------------------------------------------------------------- 1 | Release notes 2 | ============= 3 | 4 | 1.2.4 5 | ----- 6 | 7 | 8 | 1.2.3 9 | ----- 10 | - support for Python 3.4 removed 11 | 12 | 1.2.2 13 | ----- 14 | - Config moved from nuts-ml to nuts-flow 15 | 16 | 1.2.1 17 | ----- 18 | - ReadNamedCSV added 19 | 20 | 1.2.0 21 | ----- 22 | - PrintType and PrintColType moved from nuts-ml to nuts-flow 23 | 24 | 1.1.2 25 | ----- 26 | - print_type added 27 | 28 | 1.1.1 29 | ----- 30 | - shapestr and dependent functions moved from nuts-ml to nuts-flow 31 | - wrapping for nut functions fixed 32 | 33 | 1.1.0 34 | ----- 35 | - support for Python 3.8 36 | - pick parameter for Cache added 37 | - Timer() added 38 | 39 | 1.0.38 40 | ------ 41 | - common.itemize added 42 | 43 | 1.0.36 44 | ------ 45 | - encoding added to WriteCSV 46 | 47 | 1.0.35 48 | ------ 49 | - long description added to setup.py 50 | 51 | 1.0.34 52 | ------ 53 | - Minor refactorings 54 | 55 | 1.0.33 56 | ------ 57 | - deprecated time.clock() replaced 58 | 59 | 1.0.32 60 | ------ 61 | - Minor refactorings 62 | 63 | 1.0.31 64 | ------ 65 | - Title parameter added to PrintProgress 66 | - Repeat now repeatably calls functions 67 | - FilterCol added 68 | - CSVReader now supports format functions per column 69 | - Start of release notes -------------------------------------------------------------------------------- /docs/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 84531d439e2b2bb9a54372640d73daa4 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/html/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/.nojekyll -------------------------------------------------------------------------------- /docs/html/_images/nutsflow_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_images/nutsflow_logo.gif -------------------------------------------------------------------------------- /docs/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Overview: module code — nutsflow 1.2.4 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 103 | 104 |
105 | 106 | 107 | 113 | 114 | 115 |
116 | 117 |
118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 |
136 | 137 |
    138 | 139 |
  • »
  • 140 | 141 |
  • Overview: module code
  • 142 | 143 | 144 |
  • 145 | 146 |
  • 147 | 148 |
149 | 150 | 151 |
152 |
153 |
154 |
155 | 156 |

All modules for which code is available

157 | 170 | 171 |
172 | 173 |
174 |
175 | 176 | 177 |
178 | 179 |
180 |

181 | 182 | © Copyright 2017, IBM Research Australia 183 | 184 | Last updated on Mar 04, 2021. 185 | 186 | 187 |

188 |
189 | 190 |
191 | 192 |
193 |
194 | 195 |
196 | 197 |
198 | 199 | 200 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /docs/html/_sources/contributions.rst.txt: -------------------------------------------------------------------------------- 1 | Contributions 2 | ============= 3 | 4 | Contributions to **nuts-flow** are welcome. Please document the code following 5 | the examples, provide unit tests and ensure that ``pytest`` runs without 6 | errors. 7 | 8 | 9 | Environment 10 | ----------- 11 | 12 | You will need git, Python, pytest, Sphinx installed. 13 | 14 | 15 | Unit tests 16 | ---------- 17 | 18 | .. code :: 19 | 20 | $ cd nutsflow 21 | $ pytest 22 | ============================= test session starts ============================= 23 | platform win32 -- Python 2.7.13, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 24 | rootdir: C:\Maet\Projects\Python\nuts-flow, inifile: pytest.ini 25 | plugins: cov-2.3.1 26 | collected 138 items 27 | 28 | README.rst . 29 | nutsflow\base.py .. 30 | nutsflow\common.py .... 31 | nutsflow\function.py ......... 32 | nutsflow\iterfunction.py ........... 33 | nutsflow\processor.py ................... 34 | nutsflow\sink.py ............ 35 | nutsflow\source.py ...... 36 | nutsflow\underscore.py . 37 | sphinx\source\installation.rst . 38 | sphinx\source\introduction.rst . 39 | sphinx\source\tutorial\custom_nuts.rst . 40 | sphinx\source\tutorial\debugging.rst . 41 | sphinx\source\tutorial\divide_conquer.rst . 42 | sphinx\source\tutorial\error_messages.rst . 43 | sphinx\source\tutorial\filtering.rst . 44 | sphinx\source\tutorial\nuts_basics.rst . 45 | sphinx\source\tutorial\performance.rst . 46 | sphinx\source\tutorial\practice_problems.rst . 47 | sphinx\source\tutorial\prerequisites.rst . 48 | sphinx\source\tutorial\rearranging.rst . 49 | sphinx\source\tutorial\recipes.rst . 50 | sphinx\source\tutorial\sinks.rst . 51 | sphinx\source\tutorial\sources.rst . 52 | sphinx\source\tutorial\transforming.rst . 53 | sphinx\source\tutorial\underscore.rst . 54 | tests\nutsflow\test_base.py .... 55 | tests\nutsflow\test_common.py ...... 56 | tests\nutsflow\test_factory.py ......... 57 | tests\nutsflow\test_function.py ......... 58 | tests\nutsflow\test_iterfunction.py ........... 59 | tests\nutsflow\test_processor.py ................................... 60 | tests\nutsflow\test_sink.py ................ 61 | tests\nutsflow\test_source.py ....... 62 | tests\nutsflow\test_underscore.py .. 63 | tests\nutsflow\examples\test_examples.py . 64 | 65 | ========================= 182 passed in 19.58 seconds ========================= 66 | 67 | 68 | 69 | We are aiming at a code coverage of 100%. Run ``pytest --cov`` for verification. 70 | 71 | .. code :: 72 | 73 | $ cd nutsflow 74 | $ pytest --cov 75 | 76 | ---------- coverage: platform win32, python 2.7.13-final-0 ----------- 77 | Name Stmts Miss Cover 78 | --------------------------------------------------- 79 | nutsflow\base.py 22 0 100% 80 | nutsflow\common.py 27 0 100% 81 | nutsflow\examples\examples.py 57 0 100% 82 | nutsflow\factory.py 39 0 100% 83 | nutsflow\function.py 69 0 100% 84 | nutsflow\iterfunction.py 65 0 100% 85 | nutsflow\processor.py 160 0 100% 86 | nutsflow\sink.py 85 0 100% 87 | nutsflow\source.py 40 0 100% 88 | nutsflow\underscore.py 26 0 100% 89 | --------------------------------------------------- 90 | TOTAL 590 0 100% 91 | 92 | 93 | 94 | Documentation 95 | ------------- 96 | 97 | Update Sphinx/HTML documentation as follows 98 | 99 | .. code:: 100 | 101 | cd sphinx 102 | make clean 103 | make html 104 | make test 105 | 106 | cd .. 107 | ./push_docs 108 | 109 | 110 | Style guide 111 | ----------- 112 | 113 | Code should be formatted following the `PEP-8 `_ 114 | style guide. 115 | 116 | Names of *nuts* shoulds be in CamelCase (just like class names) and describe an action, 117 | e.g. ``ReadCSV`` but not ``CSVReader``. 118 | 119 | Prefer *immutable* data types, e.g. tuples over lists, for outputs of nuts and 120 | avoid nuts with *side-effects. Nuts should not *mutate* their input data but create 121 | copies. 122 | 123 | If a nut has no input it should be a *source*, for instance like ``Range``. 124 | If it doesn't output a generator or iterator it should be a *sink*, 125 | see ``Collect`` for example. 126 | If a nut outputs the same number of elements it reads, it probably 127 | is a *function* (e.g. ``Square``) otherwise a *processor*. -------------------------------------------------------------------------------- /docs/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. Nutsflow documentation master file 2 | 3 | Welcome to nuts-flow 4 | ==================== 5 | 6 | .. image:: pics/nutsflow_logo.gif 7 | :align: center 8 | 9 | **nuts-flow** is a flow-programming framework based on the chaining of 10 | iterators using the ``>>`` operator. Here a small example 11 | 12 | >>> from nutsflow import Range, Filter, Take, Collect, _ 13 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 14 | [6, 7, 8] 15 | 16 | For a quick start have a look at the :ref:`Introduction` and for 17 | more detailed information see the :ref:`Tutorial` . Skim over the 18 | short description of all nuts in the :ref:`Overview` for an 19 | overall impression of the available functionality. 20 | 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | 25 | introduction 26 | installation 27 | overview 28 | tutorial/introduction 29 | contributions 30 | links 31 | nutsflow 32 | 33 | 34 | Indices 35 | ======= 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | 41 | -------------------------------------------------------------------------------- /docs/html/_sources/installation.rst.txt: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | Standard 6 | -------- 7 | 8 | Installation via ``pip`` from `PyPi `_ 9 | 10 | .. code:: 11 | 12 | pip install nutsflow 13 | 14 | 15 | Verification 16 | ------------ 17 | 18 | Quick check that it works 19 | 20 | .. doctest:: 21 | 22 | python 23 | >>> from nutsflow import * 24 | >>> Range(5) >> Square() >> Collect() 25 | [0, 1, 4, 9, 16] 26 | 27 | 28 | You can also run the entire unit test suite using ``pytest``: 29 | 30 | .. code:: 31 | 32 | cd my_python_path/site-packages/nutflow 33 | pytest 34 | 35 | 36 | .. note:: 37 | 38 | If you don't know where your ``site-packages`` are, run the following code 39 | 40 | .. code:: 41 | 42 | python -c "import site; print(site.getsitepackages())" 43 | ['C:\\Maet\\Software\\Anaconda', 'C:\\Maet\\Software\\Anaconda\\lib\\site-packages'] 44 | 45 | 46 | Bleeding-edge 47 | ------------- 48 | 49 | If you want the bleeding-edge version, install via 50 | ``git clone`` from `GitHub `_ 51 | 52 | .. code:: 53 | 54 | git clone https://github.com/maet3608/nuts-flow.git 55 | cd nuts-flow 56 | python setup.py install 57 | pytest 58 | 59 | 60 | Upgrade 61 | ------- 62 | 63 | For upgrading an existing installation 64 | 65 | .. code:: 66 | 67 | pip install nutsflow --upgrade 68 | 69 | or if installed via ``git clone`` and ``setup.py`` 70 | 71 | .. code:: 72 | 73 | cd nuts-flow 74 | python setup.py install --force 75 | 76 | 77 | Virtual environment 78 | ------------------- 79 | 80 | Create virtual environment: 81 | 82 | .. code:: 83 | 84 | pip install virtualenv 85 | cd my_projects 86 | virtualenv vnuts 87 | 88 | 89 | Activate/deactivate environment: 90 | 91 | **Linux, Mac** 92 | 93 | .. code:: 94 | 95 | $ source vnuts/bin/activate 96 | $ deactivate 97 | 98 | 99 | **Windows** 100 | 101 | .. code:: 102 | 103 | > vnuts\Scripts\activate.bat 104 | > vnuts\Scripts\deactivate.bat 105 | 106 | 107 | Install **nuts-flow** in virtual environment (here for Linux, Mac) 108 | 109 | .. code:: 110 | 111 | source vnuts/bin/activate 112 | pip install nutsflow 113 | 114 | 115 | -------------------------------------------------------------------------------- /docs/html/_sources/introduction.rst.txt: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | **nuts-flow** is a data processing pipeline that is largely based on Python's 5 | `itertools `_. 6 | 7 | **nuts** are thin wrappers around itertool functions and 8 | provide a ``>>`` operator to chain iterators in pipelines 9 | to construct data flows. The result is an easier to read flow of data. 10 | 11 | The following two examples show the same data flow. The first is 12 | using Python's itertools and the second is using **nuts-flow**: 13 | 14 | >>> from itertools import islice 15 | >>> list(islice(filter(lambda x: x > 5, range(10)), 3)) 16 | [6, 7, 8] 17 | 18 | >>> from nutsflow import Range, Filter, Take, Collect, _ 19 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 20 | [6, 7, 8] 21 | 22 | Both data flows extract the first three integers 23 | in the interval [0, 8[ that are greater than five. However, 24 | the linear arrangment of processing steps with **nuts-flow** is 25 | easier to read than the nested calls of itertool functions. 26 | 27 | **nuts-flow** is the base library for 28 | `nuts-ml `_, a 29 | data pre-processing pipeline for deep learning. -------------------------------------------------------------------------------- /docs/html/_sources/links.rst.txt: -------------------------------------------------------------------------------- 1 | Links 2 | ===== 3 | 4 | Links to other interesting projects regarding data flows or functional programming 5 | under Python. 6 | 7 | 8 | - `toolz `_ 9 | - `PyFunctional `_ 10 | - `fn `_ 11 | -------------------------------------------------------------------------------- /docs/html/_sources/nutsflow.examples.rst.txt: -------------------------------------------------------------------------------- 1 | nutsflow.examples package 2 | ========================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | nutsflow.examples.examples module 8 | --------------------------------- 9 | 10 | .. automodule:: nutsflow.examples.examples 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: nutsflow.examples 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /docs/html/_sources/nutsflow.rst.txt: -------------------------------------------------------------------------------- 1 | nutsflow package 2 | ================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | nutsflow.examples 10 | 11 | Submodules 12 | ---------- 13 | 14 | nutsflow.base module 15 | -------------------- 16 | 17 | .. automodule:: nutsflow.base 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | nutsflow.common module 23 | ---------------------- 24 | 25 | .. automodule:: nutsflow.common 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | nutsflow.factory module 31 | ----------------------- 32 | 33 | .. automodule:: nutsflow.factory 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | nutsflow.function module 39 | ------------------------ 40 | 41 | .. automodule:: nutsflow.function 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | nutsflow.iterfunction module 47 | ---------------------------- 48 | 49 | .. automodule:: nutsflow.iterfunction 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | nutsflow.processor module 55 | ------------------------- 56 | 57 | .. automodule:: nutsflow.processor 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | nutsflow.sink module 63 | -------------------- 64 | 65 | .. automodule:: nutsflow.sink 66 | :members: 67 | :undoc-members: 68 | :show-inheritance: 69 | 70 | nutsflow.source module 71 | ---------------------- 72 | 73 | .. automodule:: nutsflow.source 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | nutsflow.underscore module 79 | -------------------------- 80 | 81 | .. automodule:: nutsflow.underscore 82 | :members: 83 | :undoc-members: 84 | :show-inheritance: 85 | 86 | 87 | Module contents 88 | --------------- 89 | 90 | .. automodule:: nutsflow 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/divide_conquer.rst.txt: -------------------------------------------------------------------------------- 1 | .. _divide_conquer: 2 | 3 | Divide and conquer 4 | ================== 5 | 6 | It is frequently necessary to either split a data flow into multiple flows 7 | or combine data flows. The following nuts are specifically designed for this 8 | purpose. In this context the :ref:`Partition` and the :ref:`MapMulti` nuts 9 | might be of interest as well. 10 | 11 | 12 | Zip 13 | ^^^ 14 | 15 | ``Zip(*iterables)`` combines two or more iterables like a *zipper* taking at 16 | every step an element from each iterable and outputting a tuple of the 17 | grouped elements. Here an example 18 | 19 | >>> from nutsflow import * 20 | 21 | >>> numbers = [0, 1, 2] 22 | >>> letters = ['a', 'b', 'c'] 23 | >>> numbers >> Zip(letters) >> Collect() 24 | [(0, 'a'), (1, 'b'), (2, 'c')] 25 | 26 | ``Zip`` finishes when the shortest iterable is exhausted. See 27 | 28 | >>> Range(100) >> Zip('abc') >> Collect() 29 | [(0, 'a'), (1, 'b'), (2, 'c')] 30 | 31 | Note that ``Zip`` can zip more than two iterables: 32 | 33 | >>> '12' >> Zip('ab', '+-') >> Collect() 34 | [('1', 'a', '+'), ('2', 'b', '-')] 35 | 36 | If the output of ``Zip`` is required to be flat ``Flatten`` can be called 37 | 38 | >>> [0, 1, 2] >> Zip('abc') >> Flatten() >> Collect() 39 | [0, 'a', 1, 'b', 2, 'c'] 40 | 41 | but using :ref:`Interleave` is simpler in this case. 42 | 43 | Instead of nuts-flow's ``Zip``, Python's ``zip`` could be used alternatively: 44 | 45 | >>> zip(numbers, letters) >> Print() >> Consume() 46 | (0, 'a') 47 | (1, 'b') 48 | (2, 'c') 49 | 50 | 51 | Unzip 52 | ^^^^^ 53 | 54 | ``Unzip(container=None)`` reverses a :ref:`Zip` operation: 55 | 56 | >>> numbers, letters = [0, 1, 2] >> Zip('abc') >> Unzip() 57 | >>> list(numbers) 58 | [0, 1, 2] 59 | >>> list(letters) 60 | ['a', 'b', 'c'] 61 | 62 | Per default ``Unzip`` returns iterators but often the results are required 63 | as lists or other collections (see above). ``Unzip`` allows to provide a 64 | container to collect the results: 65 | 66 | >>> zip([0, 1, 2], 'abc') >> Unzip(list) >> Collect() 67 | [[0, 1, 2], ['a', 'b', 'c']] 68 | 69 | This equivalent to ``Unzip() >> Map(list) >> Collect()`` but shorter. 70 | 71 | 72 | 73 | Interleave 74 | ^^^^^^^^^^ 75 | 76 | ``Interleave`` works like :ref:`Zip` but does not group zipped results in 77 | tuples. Instead an iterator over a flattened sequence of interleaved elements 78 | is returned: 79 | 80 | >>> numbers = [0, 1, 2] 81 | >>> letters = ['a', 'b', 'c'] 82 | >>> numbers >> Interleave(letters) >> Collect() 83 | [0, 'a', 1, 'b', 2, 'c'] 84 | 85 | Also in contrast to ``Zip``, ``Interleave`` does not stop when the shortest 86 | input iterable is depleted. Elements are returned until all inputs are 87 | depleted: 88 | 89 | >>> Range(10) >> Interleave('abc') >> Collect() 90 | [0, 'a', 1, 'b', 2, 'c', 3, 4, 5, 6, 7, 8, 9] 91 | 92 | 93 | Concat 94 | ^^^^^^ 95 | 96 | Apart from zipping or interleaving iterators, they can also be concatenated 97 | using ``Concat``: 98 | 99 | >>> Range(5) >> Concat('abc') >> Collect() 100 | [0, 1, 2, 3, 4, 'a', 'b', 'c'] 101 | 102 | >>> '12' >> Concat('abcd', [3, 4, 5]) >> Collect() 103 | ['1', '2', 'a', 'b', 'c', 'd', 3, 4, 5] 104 | 105 | Note that ``Concat`` is memory efficient and does not materialize any of the 106 | input iterables or the concatenated result in memory; e.g. in contrast to the 107 | following code: 108 | 109 | >>> list(Range(5)) + list('abc') 110 | [0, 1, 2, 3, 4, 'a', 'b', 'c'] 111 | 112 | 113 | Tee 114 | ^^^ 115 | 116 | ``Tee([n=2])`` creates multiple independent iterators from a single iterable. 117 | 118 | >>> numbers1, numbers2 = Range(5) >> Tee(2) 119 | >>> numbers1 >> Collect() 120 | [0, 1, 2, 3, 4] 121 | 122 | >>> numbers2 >> Collect() 123 | [0, 1, 2, 3, 4] 124 | 125 | ``Tee`` is only useful if the returned iterators are advanced largely 126 | synchronously. Otherwise the memory consumption is identical to simply 127 | materializing the input iterable and referencing it, e.g. 128 | 129 | >>> numbers1 = Range(5) >> Collect() 130 | >>> numbers2 = numbers1 131 | 132 | A simple example where ``Tee`` is useful would be to add each number in the 133 | input iterable to its predecessor: 134 | 135 | >>> add = lambda a, b: a + b 136 | >>> numbers1, numbers2 = Range(5) >> Tee(2) 137 | >>> numbers1 >> Drop(1) >> Map(add, numbers2) >> Collect() 138 | [1, 3, 5, 7] 139 | 140 | Iterators, in contrast to streams, do not allow to go back and ``Tee`` provides 141 | a way to overcome this limitation. 142 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/error_messages.rst.txt: -------------------------------------------------------------------------------- 1 | Common error messages 2 | ===================== 3 | 4 | ``'Wrapper' object is not callable`` 5 | ------------------------------------------------------------------ 6 | 7 | Additional brackets when calling nut: 8 | 9 | >>> from nutsflow import * 10 | >>> greater0 = Filter(lambda x : x > 0) 11 | >>> [2, -1, 3] >> greater0() >> Collect() # doctest: +SKIP 12 | ... 13 | TypeError: 'Wrapper' object is not callable 14 | 15 | Should be: 16 | 17 | >>> greater0 = Filter(lambda x : x > 0) 18 | >>> [2, -1, 3] >> greater0 >> Collect() 19 | [2, 3] 20 | 21 | 22 | 23 | ``unsupported operand type(s) for >>`` 24 | ------------------------------------------------------------------ 25 | 26 | Missing brackets when calling nut: 27 | 28 | >>> Greater0 = nut_filter(lambda x: x > 0) 29 | >>> [2, -1, 3] >> Greater0 >> Collect() # doctest: +SKIP 30 | ... 31 | TypeError: unsupported operand type(s) for >>: 'list' and 'type' 32 | 33 | Should be: 34 | 35 | >>> Greater0 = nut_filter(lambda x: x > 0) 36 | >>> [2, -1, 3] >> Greater0() >> Collect() 37 | [2, 3] 38 | 39 | 40 | 41 | 42 | ``name '_' is not defined`` 43 | ------------------------------------------------------------------ 44 | 45 | Typically encountered when using ``_`` without importing it. 46 | Example: 47 | 48 | >>> from nutsflow import * 49 | >>> [2, -1, 3] >> Filter(_ > 0) >> Collect() # doctest: +SKIP 50 | ... 51 | NameError: name '_' is not defined 52 | 53 | Since ``_`` is a common name for place-holder variables the 54 | explicit import of ``_`` is required: 55 | 56 | >>> from nutsflow import * 57 | >>> from nutsflow import _ 58 | >>> [2, -1, 3] >> Filter(_ > 0) >> Collect() 59 | [2, 3] 60 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/exceptions.rst.txt: -------------------------------------------------------------------------------- 1 | .. _exceptions: 2 | 3 | Handling exceptions 4 | ==================== 5 | 6 | Data processing pipelines are typically composed of multiple nuts 7 | and process a stream of data. If one of the nuts within the pipeline 8 | fails the data stream breaks and processing stops. 9 | 10 | Sometimes a more graceful handling of errors is needed and **nuts-flow** 11 | provides the ``Try`` nut for this purpose. 12 | 13 | 14 | Try 15 | --- 16 | 17 | ``Try(func, default)`` can wrap any nut function 18 | (but not other types of nuts such as processors) and handle exceptions 19 | raised by the wrapped nut. 20 | In the following example a nut ``Div`` is defined, which computes 10/x 21 | and is applied to a sequence of numbers: 22 | 23 | >>> from nutsflow import * 24 | >>> Div = nut_function(lambda x : 10/x) 25 | >>> [1, 5, 10] >> Div() >> Collect() 26 | [10, 2, 1] 27 | 28 | As it is this pipeline will break and not collect any results 29 | if any of the input elements is zero: 30 | 31 | >>> [1, 0, 10] >> Div() >> Collect() 32 | Traceback (most recent call last): 33 | ... 34 | ZeroDivisionError: integer division or modulo by zero 35 | 36 | Wrapping the ``Div`` function within a ``Try`` allows the pipeline 37 | to ignore the input element that causes ``Div`` to fail. The problematic 38 | element and the error message are printed to standard out 39 | but the pipeline does not break and collects all other elements: 40 | 41 | >>> [1, 0, 10] >> Try(Div(), 'STDOUT') >> Collect() 42 | ERROR: 0 : integer division or modulo by zero 43 | [10, 1] 44 | 45 | ``Try`` allows defining a default value to be returned if the wrapped 46 | function fails. In this case no error is printed the offending input element 47 | is replaced by the provided default value: 48 | 49 | >>> [1, 0, 10] >> Try(Div(), -1) >> Collect() 50 | [10, -1, 1] 51 | 52 | This kind of exception handling can be performed for nut functions 53 | or plain Python functions (user-defined or built-in). Here an example 54 | where Python's logarithm function is wrapped and zero values are 55 | ignored: 56 | 57 | >>> from math import log 58 | >>> [1, 0, 10] >> Try(log, default='STDOUT') >> Collect() 59 | ERROR: 0 : math domain error 60 | [0.0, 2.302585092994046] 61 | 62 | The ``default`` parameter can also be a function that takes the offending 63 | input element ``x`` and the exception ``e`` as parameters. This allows 64 | to replace offending inputs depending on the input value or the types of 65 | exception raised. 66 | In the following example negative input elements are replaced by 67 | their absolute value and zero is replaced by ``None``. 68 | 69 | >>> if_invalid = lambda x, e: -x if x < 0 else None 70 | >>> [1, 0, -1, 10] >> Try(log, if_invalid) >> Collect() 71 | [0.0, None, 1, 2.302585092994046] 72 | 73 | As a last example, invalid inputs are replaced by the exception they 74 | cause: 75 | 76 | >>> if_invalid = lambda x, e: e 77 | >>> [1, -1, 10] >> Try(log, if_invalid) >> Collect() 78 | [0.0, ValueError('math domain error',), 2.302585092994046] 79 | 80 | The default value for ``Try(x,default)`` is ``default='STDERR'``, 81 | which ignores all elements that raise exceptions and prints error 82 | message to stderr. For ``default='IGNORE'``, offending inputs are 83 | ignored and no error messages are printed. 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/filtering.rst.txt: -------------------------------------------------------------------------------- 1 | .. _filtering: 2 | 3 | Filtering 4 | ========= 5 | 6 | Apart from :ref:`reading ` and :ref:`writing `, 7 | :ref:`filtering ` and :ref:`transforming ` 8 | are the most common operations within data flows. This 9 | sections presents various nuts used to filter, partition 10 | or group data. 11 | 12 | 13 | Filter 14 | ------ 15 | 16 | A common task is to remove elements from a data flow. **nuts-flow** 17 | provides ``Filter`` and ``FilterFalse`` for this purpose. In the 18 | following example all number greater than five are extracted: 19 | 20 | >>> from nutsflow import * 21 | 22 | >>> Range(10) >> Filter(lambda x: x > 5) >> Collect() 23 | [6, 7, 8, 9] 24 | 25 | ``FilterFalse`` is simply the negation of ``Filter`` and extracts 26 | number smaller or equal to five: 27 | 28 | >>> Range(10) >> FilterFalse(lambda x: x > 5) >> Collect() 29 | [0, 1, 2, 3, 4, 5] 30 | 31 | ``Filter`` and ``FilterFalse`` take a predicate (Lambda) function that 32 | must return a boolean value. If the predicate function is very simple 33 | it can be written shorter using :ref:`underscore syntax `: 34 | 35 | >>> from nutsflow import _ 36 | 37 | >>> Range(10) >> Filter(_ > 5) >> Collect() 38 | [6, 7, 8, 9] 39 | 40 | >>> Range(10) >> FilterFalse(_ > 5) >> Collect() 41 | [0, 1, 2, 3, 4, 5] 42 | 43 | 44 | Partition 45 | --------- 46 | 47 | If both 'sides' of a filter, the elements accepted **and** the elements 48 | rejected, are wanted the ``Partition`` nut can be used: 49 | 50 | >>> greater, smaller = Range(10) >> Partition(_ > 5) 51 | >>> greater >> Collect() 52 | [6, 7, 8, 9] 53 | >>> smaller >> Collect() 54 | [0, 1, 2, 3, 4, 5] 55 | 56 | >>> odd, even = Range(10) >> Partition(_ % 2) 57 | >>> odd >> Collect() 58 | [1, 3, 5, 7, 9] 59 | >>> even >> Collect() 60 | [0, 2, 4, 6, 8] 61 | 62 | Note that ``Partition`` returns a tuple containing two iterators. 63 | 64 | 65 | GroupBy 66 | ------- 67 | 68 | Similar, but more powerful than ``Partition`` is ``GroupBy``, which allows 69 | to group the elements of the flow according to a key function: 70 | 71 | >>> Range(10) >> GroupBy(_ > 5) >> Collect() 72 | [(False, [0, 1, 2, 3, 4, 5]), (True, [6, 7, 8, 9])] 73 | 74 | ``GroupBy`` returns an iterator over the groups, where each group is 75 | a tuple with the result of the key function first and the elements of 76 | the group second. If the result of the key function is not required 77 | the *nokey* flag can be set to ``True``: 78 | 79 | >>> Range(10) >> GroupBy(_ > 5, nokey=True) >> Collect() 80 | [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9]] 81 | 82 | In contrast to ``Partition``, ``GroupBy`` is not limited to a boolean 83 | key function. For instance, to group by the remainder of the division 84 | by 3 simply call 85 | 86 | >>> Range(10) >> GroupBy(_ % 3) >> Collect() 87 | [(0, [0, 3, 6, 9]), (1, [1, 4, 7]), (2, [2, 5, 8])] 88 | 89 | ``GroupBy`` loads all data in memory and should be avoided for large data sets. 90 | If the data is sorted ``GroupBySorted`` can be used instead. 91 | 92 | 93 | TakeWhile and DropWhile 94 | ----------------------- 95 | 96 | Occasionally, it is necessary to run a data flow until a certain 97 | condition is met. ``TakeWhile(func)`` takes elements from the 98 | iterable as long as the predicate function is true. 99 | In the following example all number are collected until 100 | the **first** negative number is encountered: 101 | 102 | >>> [2, 1, -1, 3, 4, -1] >> TakeWhile(_ > 0) >> Collect() 103 | [2, 1] 104 | 105 | Similarily, ``DropWhile(func)`` skips all elements while the predicate function 106 | is true and returns the remainder of the iterable: 107 | 108 | >>> [2, 1, -1, 3, 4, -1] >> DropWhile(_ > 0) >> Collect() 109 | [-1, 3, 4, -1] 110 | 111 | 112 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/introduction.rst.txt: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | This tutorial introduces the basic concepts of **nuts-flow** and 5 | provides examples of its usage. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | prerequisites 11 | nuts_basics 12 | debugging 13 | filtering 14 | transforming 15 | rearranging 16 | divide_conquer 17 | sources 18 | sinks 19 | exceptions 20 | custom_nuts 21 | underscore 22 | performance 23 | recipes 24 | practice_problems 25 | error_messages 26 | 27 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/nuts_basics.rst.txt: -------------------------------------------------------------------------------- 1 | Nuts basics 2 | =========== 3 | 4 | Flows 5 | ----- 6 | 7 | **nuts-flow** data pipelines are composed of **nuts** that 8 | are chained together with the ``>>`` operator. For instance, in the 9 | following data flow, ``Range`` generates number from 0 to 4, the ``Square`` 10 | nut squares those numbers and the ``Collect`` nut collects the results 11 | in a list: 12 | 13 | >>> from nutsflow import Range, Square, Collect 14 | >>> Range(5) >> Square() >> Collect() 15 | [0, 1, 4, 9, 16] 16 | 17 | The data elements of a flow are typically processed element by element, 18 | avoiding loading large amounts of data into memory or processing data 19 | if not needed. For instance, 20 | 21 | >>> from nutsflow import * 22 | >>> Range(10000000) >> Square() >> Take(3) >> Collect() 23 | [0, 1, 4] 24 | 25 | works just fine and does not store 10 million integers in memory. 26 | 27 | 28 | Sources and Sinks 29 | ----------------- 30 | 31 | Every data flow starts with a :ref:`source `, which can be any 32 | *iterable* such as iterators, generators, iterable nuts or 33 | plain Python data structures (string, lists, sets, dictionaries, ...): 34 | 35 | >>> [0, 1, 2, 3, 4] >> Square() >> Collect() 36 | [0, 1, 4, 9, 16] 37 | 38 | >>> "Macadamia" >> Take(4) >> Collect() 39 | ['M', 'a', 'c', 'a'] 40 | 41 | >>> range(5) >> Collect() 42 | [0, 1, 3, 4, 4] 43 | 44 | In addition to the usual Python data sources, **nuts-flow** has its own 45 | sources, e.g. 46 | 47 | >>> Range(5) >> Collect() # Range() == range() 48 | [0, 1, 3, 4, 4] 49 | 50 | >>> Repeat(1) >> Take(3) >> Collect() 51 | [1, 1, 1] 52 | 53 | 54 | Apart from a source, every data flow needs a *sink* at the end that 55 | *pulls* the data. Without a sink the data flow does not process any data 56 | (most nuts are lazy). For example 57 | 58 | >>> Range(5) >> Square() 59 | 60 | 61 | simply returns an iterator object but does neither create any ranged numbers 62 | nor computes the square. :ref:`Sinks ` take iterables as input and return a 63 | result of any type or even nothing 64 | 65 | >>> Range(5) >> Collect() 66 | [0, 1, 2, 3, 4] 67 | 68 | >>> Range(5) >> Sum() 69 | 10 70 | 71 | >>> Range(5) >> Consume() # returns nothing 72 | 73 | Here the sinks are ``Collect()``, ``Sum()`` and ``Consume()``. 74 | 75 | 76 | Functions and Processors 77 | ------------------------ 78 | 79 | Between *sources* and *sinks* a data flow typically contains a sequence of 80 | *nut functions* or *nut processors*. Nut functions read from an iterator 81 | and for each processed element return a new element. ``Square`` is such 82 | a nut function. 83 | 84 | *Nut processors*, on the other hand, can modulate the data flow and might return 85 | more or less elements than read from the input. For instance, ``Pick(n)`` 86 | is a processor that returns only every *n-ths* element from the input iterable 87 | 88 | >>> from nutsflow import Range, Pick, Collect 89 | >>> Range(10) >> Pick(3) >> Collect() 90 | [0, 3, 6, 9] 91 | 92 | Note that nut functions can be used as *normal* functions as well but 93 | must be called with additional brackets 94 | 95 | >>> Square()(3) 96 | 9 97 | 98 | 99 | Iterator depletion 100 | ------------------ 101 | 102 | It is important to remember that *nuts* usually return iterators 103 | that will deplete when used multiple times. See the following example, 104 | where ``Take(2)`` always takes the first *2* elements from its input: 105 | 106 | >>> from nutsflow import Range, Take, Collect 107 | >>> numbers = Range(5) 108 | >>> numbers >> Take(2) >> Collect() 109 | [0, 1] 110 | >>> numbers >> Take(2) >> Collect() 111 | [2, 3] 112 | >>> numbers >> Take(2) >> Collect() 113 | [4] 114 | >>> numbers >> Take(2) >> Collect() 115 | [] 116 | 117 | 118 | New nuts 119 | -------- 120 | 121 | **nuts-flow** can easily be extended with new nuts 122 | (for details see :ref:`Custom nuts` ) 123 | 124 | >>> Tripple = nut_function(lambda x: x * 3) 125 | >>> Range(5) >> Tripple() >> Collect() 126 | [0, 3, 6, 9, 12] 127 | 128 | or combined with plain Python functions as any 129 | other iterator: 130 | 131 | >>> def Squares(n): return Range(n) >> Square() 132 | >>> Squares(3) >> Collect() 133 | [0, 1, 4] 134 | 135 | >>> sum(Range(5) >> Square()) 136 | 30 137 | 138 | When implementing new nuts, or Python functions/classes that 139 | behave like nuts, the name of the nut should start with an uppercase letter. 140 | This makes it easy to distiguish standard functions from nuts: 141 | 142 | >>> from nutsflow import Range, Sum 143 | >>> Range(5) >> Sum() 144 | 10 145 | >>> sum(Range(5)) 146 | 10 147 | >>> range(5) >> Sum() 148 | 10 149 | 150 | 151 | Line breaks 152 | ----------- 153 | 154 | Sometimes data flows get longer than the 79 character limit 155 | that the Python style guide 156 | `PEP 8 `_ 157 | recommends. In such a case flows can be wrapped in brackets 158 | to allow for line breaks: 159 | 160 | >>> (Range(10) >> Pick(2) >> Square() >> Square() >> 161 | ... Take(3) >> Collect()) 162 | [0, 16, 256] 163 | 164 | Alternatively, a flow can be broken into shorter pieces: 165 | 166 | >>> squared = Range(10) >> Pick(2) >> Square() >> Square() 167 | >>> squared >> Take(3) >> Collect() 168 | [0, 16, 256] 169 | 170 | 171 | Summary 172 | ------- 173 | 174 | **nuts-flows** are composed of *nuts* that are connected to flows 175 | via the ``>>`` operator. 176 | A data flow starts with a *source*, ends with a *sink* and 177 | typically contains *nut processors* or *nut functions* inbetween: 178 | 179 | .. code:: 180 | 181 | source >> processor|function >> ... >> sink 182 | 183 | *nut sources* return iterators or iterables when called. *nut sinks* take iterables 184 | as input and return results of any type. 185 | *nut functions* transform the elements of a flow but do not change the number (or order) 186 | of the elements, while *nut processors* can modify the flow in any way. 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/performance.rst.txt: -------------------------------------------------------------------------------- 1 | Performance 2 | =========== 3 | 4 | **nuts-flow** does not support concurrency in general but provides 5 | nuts that can improve performance by caching or parallelization. 6 | 7 | 8 | MapPar 9 | ------ 10 | 11 | Applying a function concurrently to the elements of a flow can be achieved 12 | with the ``MapPar`` nut. The following toy example converts numbers to their 13 | *absolute values* by applying the ``abs`` function in parallel 14 | 15 | >>> from nutsflow import * 16 | 17 | >>> [-1, -2, -3] >> MapPar(abs) >> Collect() 18 | [1, 2, 3] 19 | 20 | Note that the order of the elements in the iterable is preserved. 21 | Currently, ``MapPar`` is of limited use, since 1) the function applied 22 | must be `pickable `_ 23 | and 2) ``MapPar`` creates parallel processes, which are computationally 24 | expensive to start. 25 | 26 | 27 | Cache 28 | ----- 29 | 30 | **nuts-flow** supports the *caching* of results to disk. Here an 31 | example in pseudo code 32 | 33 | .. code:: python 34 | 35 | with Cache() as cache: 36 | for i in range: 37 | data >> expensive_op >> cache >> ... >> Collect() 38 | 39 | Note that *caching* is only useful if 1) the elements to cache are 40 | time-consuming to compute, 2) can be loaded faster than recreated, 41 | and 3) the same data flow is executed multiple times, 42 | where in the first run the cache is filled and then is used in all 43 | subsequent runs. 44 | 45 | A common use case is *machine learning for vision*, where images 46 | are preprocessed and a classifier is trained by repeatedly executing 47 | a data flow: 48 | 49 | .. code:: python 50 | 51 | with Cache() as cache: 52 | for epoch in xrange(100): 53 | images >> preprocess >> cache >> network.train() >> Consume() 54 | 55 | Cached elements are pickled to a temporary folder which is deleted 56 | when the ``with`` block is exited. The cache can be cleared as follows: 57 | 58 | .. code:: python 59 | 60 | with Cache() as cache: 61 | ... 62 | cache.clear() 63 | 64 | 65 | Prefetch 66 | -------- 67 | 68 | *Prefetching* is another common method employed in (GPU-based) machine learning 69 | to speed up a data flow. Here data is pre-fetched (and pre-processed) 70 | on a separate CPU thread while the GPU is performing machine learning 71 | on another chunk of data: 72 | 73 | .. code:: python 74 | 75 | images >> preprocess >> Prefetch() >> network.train() >> Consume() 76 | 77 | 78 | The following two examples demonstrate the difference between processing 79 | a data flow with and without pre-fetching. First a flow *without pre-fetching* 80 | that takes one number and prints it 81 | 82 | .. code:: python 83 | 84 | >>> Range(5) >> Print() >> Take(1) >> Consume() 85 | 0 86 | 87 | now the same flow but *with pre-fetching* 88 | 89 | .. code:: python 90 | 91 | >>> Range(5) >> Print() >> Prefetch() >> Take(1) >> Consume() 92 | 0 93 | 1 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/practice_problems.rst.txt: -------------------------------------------------------------------------------- 1 | Practice problems 2 | ================= 3 | 4 | `Project Euler `_ and 5 | `99 Scala Problems `_ or 6 | `99 Python Problems `_ 7 | contain collections of small coding problems suitable 8 | for functional programming - many of them are also good exercises for **nuts-flow**. 9 | 10 | 11 | 99 Problems 12 | ----------- 13 | 14 | A few `99 Scala Problems `_ with solutions 15 | using **nuts-flow**: 16 | 17 | >>> from nutsflow import * 18 | >>> from nutsflow import _ 19 | 20 | **P01** : Find the last element of a list. 21 | 22 | >>> [1, 1, 2, 3, 5, 8] >> Tail(1) 23 | [8] 24 | 25 | 26 | **P02** : Find the last but one element of a list. 27 | 28 | >>> [1, 1, 2, 3, 5, 8] >> Tail(2) >> Head(1) 29 | [5] 30 | 31 | 32 | **P03** : Find the Kth element of a list. 33 | 34 | >>> [1, 1, 2, 3, 5, 8] >> Nth(2) 35 | 2 36 | 37 | >>> [1, 1, 2, 3, 5, 8] >> Drop(2) >> Head(1) 38 | [2] 39 | 40 | 41 | **P04** : Find the number of elements of a list. 42 | 43 | >>> [1, 1, 2, 3, 5, 8] >> Count() 44 | 6 45 | 46 | 47 | **P14** : Duplicate the elements of a list. 48 | 49 | >>> Range(5) >> Clone(2) >> Collect() 50 | [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] 51 | 52 | 53 | **P15** : Duplicate the elements of a list a given number of times. 54 | 55 | >>> Range(5) >> Clone(n) >> Collect() 56 | [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] 57 | 58 | 59 | **P16** : Drop every Nth element from a list. 60 | 61 | >>> n==3 62 | >>> lst = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'] 63 | >>> lst >> Chunk(n, list) >> Map(Take(n-1)) >> Flatten() >> Collect() 64 | ['a', 'b', 'd', 'e', 'g', 'h', 'j', 'k'] 65 | 66 | 67 | Euler 68 | ----- 69 | 70 | Some `Project Euler `_ with solutions 71 | using **nuts-flow**. 72 | 73 | **P1**: Multiples of 3 and 5 74 | 75 | If we list all the natural numbers below 10 that are multiples of 76 | 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 77 | 78 | Find the sum of all the multiples of 3 or 5 below 1000. 79 | 80 | >>> IsMultiple = nut_filter(lambda x: x % 3 == 0 or x % 5 == 0) 81 | >>> Range(1, 1000) >> IsMultiple() >> Sum() 82 | 233168 83 | 84 | 85 | **P2**: Even Fibonacci numbers 86 | 87 | Each new term in the Fibonacci sequence is generated by adding the 88 | previous two terms. By starting with 1 and 2, the first 10 terms will be: 89 | 90 | 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 91 | 92 | By considering the terms in the Fibonacci sequence whose values do not 93 | exceed four million, find the sum of the even-valued terms. 94 | 95 | >>> @nut_source 96 | ... def Fib(): 97 | ... a, b = 1, 1 98 | ... while True: 99 | ... a, b = b, a + b 100 | ... yield a 101 | >>> IsEven = nut_filter(lambda x: x%2 == 0) 102 | >>> Fib() >> TakeWhile(_ < 4000000) >> IsEven() >> Sum() 103 | 4613732 104 | 105 | 106 | **P4**: Largest palindrome product 107 | 108 | A palindromic number reads the same both ways. The largest palindrome 109 | made from the product of two 2-digit numbers is 9009 = 91 × 99. 110 | 111 | Find the largest palindrome made from the product of two 3-digit numbers. 112 | 113 | >>> n1, n2 = 100, 1000 114 | >>> IsPalindrom = nut_filter(lambda p: p == p[::-1]) 115 | >>> BuildString = nut_function(lambda (a, b): str(a * b)) 116 | >>> product = Product(Range(n1, n2), repeat=2) 117 | >>> product >> BuildString() >> IsPalindrom() >> Max(int) 118 | '906609' 119 | 120 | 121 | **P6**: Sum square difference 122 | 123 | The sum of the squares of the first ten natural numbers is, 124 | 125 | 1^2 + 2^2 + ... + 10^2 = 385 126 | 127 | The square of the sum of the first ten natural numbers is, 128 | 129 | (1 + 2 + ... + 10)^2 = 55^2 = 3025 130 | 131 | Hence the difference between the sum of the squares of the first ten natural 132 | numbers and the square of the sum is 3025 − 385 = 2640. 133 | 134 | Find the difference between the sum of the squares of the first 135 | one hundred natural numbers and the square of the sum. 136 | 137 | >>> sum_sqr = Range(1, 11) >> Square() >> Sum() 138 | >>> sqr_sum = (Range(1, 11) >> Sum())**2 139 | >>> sqr_sum - sum_sqr 140 | 2640 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/prerequisites.rst.txt: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | ============= 3 | 4 | **nuts-flow** is based on *iterators* and makes frequent use of *lambda* functions. 5 | If you are already familiar with these concepts go ahead and skip this section. 6 | 7 | 8 | Lambda functions 9 | ---------------- 10 | 11 | Commonly functions are defined via the ``def`` keyword and a function name, 12 | e.g.: 13 | 14 | .. code:: pythonun 15 | 16 | def add(a, b): 17 | return a + b 18 | 19 | *Lambda* functions or so called *anonymous* functions are an alternative method 20 | to define very short functions (without a name) that are typically used only once. 21 | For instance, the ``add`` function above can be written as follows 22 | 23 | .. code:: python 24 | 25 | lambda a, b: a + b 26 | 27 | Since functions are first class citizens in Python they can be assigned 28 | to variables and called by name as well 29 | 30 | >>> add = lambda a, b: a + b 31 | >>> add(1, 2) 32 | 3 33 | 34 | The most common use case, however, is as a anonymous function for other 35 | functions such as ``sorted``, ``max`` or ``filter``. For example, 36 | to extract numbers greater than 2 from a list we could write 37 | 38 | >>> numbers = [1, 2, 3, 4] 39 | >>> filter(lambda x: x > 2, numbers) 40 | [3, 4] 41 | 42 | **nuts-flow** has a special notation for even shorter function definitions, 43 | following the *underscore notation* from `Scala `_. 44 | Using the underscore, the above filtering can be expressed 45 | even more succinctly as 46 | 47 | >>> from nutsflow import _ 48 | >>> filter(_ > 2, numbers) 49 | [3, 4] 50 | 51 | The underscore essentially serves as a place holder for the numbers of the list. 52 | Note that the underscore notation in **nuts-flow** is very limited and only 53 | simple expression (e.g. ``_ + 1``, ``_ <= 3``, ...) are supported. More details 54 | can be found in Section :ref:`Underscore syntax` . 55 | 56 | 57 | Iterators 58 | --------- 59 | 60 | Iterators are needed to process data that doesn't fit in memory, e.g. lines of a 61 | very large file, permutations of a string, ..., or even infinitely large data such 62 | as counters or random numbers. 63 | 64 | A Python `Iterator `_ is any object that 65 | provides a ``next`` method, which returns elements when called and raises a 66 | ``StopIteration`` exception when depleted. Here an iterator that returns 67 | even numbers up to a given maximum 68 | 69 | >>> class Even(): 70 | ... def __init__(self, maximum): 71 | ... self.counter = 0 72 | ... self.maximum = maximum 73 | ... 74 | ... def __iter__(self): 75 | ... return self 76 | ... 77 | ... def __next__(self): 78 | ... self.counter += 2 79 | ... if self.counter > self.maximum: 80 | ... raise StopIteration 81 | ... return self.counter 82 | ... 83 | 84 | The ``__iter__`` method make the iterator *iterable* and enables 85 | its usage in ``for`` loops, list comprehensions or functions 86 | that take iterables 87 | 88 | >>> even = Even(6) 89 | >>> for e in even: 90 | ... print e 91 | 2 92 | 4 93 | 6 94 | 95 | There are three important properties of iterators to keep in mind. 96 | Firstly, an iterator is lazy. It doesn't produce anything until asked. 97 | There needs to be a consumer. 98 | For instance, ``even = Even(100000)`` creates the iterator but does not 99 | create any numbers. 100 | 101 | Secondly, an iterator has state and subsequent calls will advance its state. 102 | Thirdly, once an iterator is depleted it needs to be recreated to be used 103 | again 104 | 105 | >>> even = Even(10) 106 | 107 | >>> [e for e in even] 108 | [2, 4, 6, 8, 10] 109 | 110 | >>> [e for e in even] 111 | [] 112 | 113 | >>> even = Even(10) 114 | >>> [e for e in even] 115 | [2, 4, 6, 8, 10] 116 | 117 | 118 | Iterators can be chained to build complex data processing pipelines 119 | that consume very little memory. Python's 120 | `itertools `_ 121 | library provides many functions for this purpose. The following toy 122 | example uses itertools to extract the first three integers greater 123 | than five in the interval [0..8[ 124 | 125 | >>> from itertools import islice, ifilter 126 | >>> list(islice(ifilter(lambda x: x > 5, xrange(8)), 3)) 127 | [6, 7] 128 | 129 | 130 | **nuts-flow** is largely based on Python’s itertools but aims to 131 | make the data flow more explict and readable by introducing 132 | the ``>>`` operator for chaining 133 | 134 | >>> from nutsflow import Range, Filter, Take, Collect, _ 135 | >>> Range(8) >> Filter(_ > 5) >> Take(3) >> Collect() 136 | [6, 7] 137 | 138 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/rearranging.rst.txt: -------------------------------------------------------------------------------- 1 | .. _rearranging: 2 | 3 | Rearranging data 4 | ================ 5 | 6 | Another common need is to rearrange or restructure data. The following nuts 7 | can help with that. 8 | 9 | >>> from nutsflow import * 10 | 11 | 12 | Slice 13 | ----- 14 | 15 | ``Slice([start,] stop, [stride])`` takes a slice of the data. Similar to Python's 16 | `slicing `_ 17 | operation it extracts a section of the data. If no ``start`` or ``stride`` 18 | are provided, ``Slice`` extracts the first ``stop`` elements 19 | 20 | >>> [1, 2, 3, 4] >> Slice(2) >> Collect() 21 | [1, 2] 22 | 23 | 24 | If ``start`` and ``stop`` are provided the elements from ``start`` index 25 | to ``stop`` index (excluded) are extracted 26 | 27 | >>> [1, 2, 3, 4] >> Slice(1, 3) >> Collect() 28 | [2, 3] 29 | 30 | 31 | Finally the third parameter allows to specify a ``stride``. In this example 32 | every second element in the slice starting at index 0 and ending at index 4 33 | (exclusive) is extracted 34 | 35 | >>> [1, 2, 3, 4] >> Slice(0, 4, 2) >> Collect() 36 | [1, 3] 37 | 38 | 39 | Chunk 40 | ----- 41 | 42 | ``Chunk(n)`` is a nut to group data in chunks of size ``n``: 43 | 44 | >>> Range(5) >> Chunk(2) >> Map(list) >> Collect() 45 | [[0, 1], [2, 3], [4]] 46 | 47 | 48 | Note that each chunk is an iterator over the elements in the chunk, 49 | which is why ``Map(list)`` is required to convert the chunks to printable lists. 50 | A more interesting example might be the sum of the elements within each chunk 51 | 52 | >>> Range(5) >> Chunk(2) >> Map(sum) >> Collect() 53 | [1, 5, 4] 54 | 55 | 56 | Window 57 | ------ 58 | 59 | ``Window(n)`` provides a sliding window of size ``n`` over the elements 60 | in the input data. For example: 61 | 62 | >>> [1, 2, 3, 4, 5] >> Window(3) >> Collect() 63 | [(1, 2, 3), (2, 3, 4), (3, 4, 5)] 64 | 65 | This works for strings as well. We use ``Join()`` to convert the 66 | individual characters in the generated windows to strings: 67 | 68 | >>> 'abcdefg' >> Window(4) >> Map(Join()) >> Collect() 69 | ['abcd', 'bcde', 'cdef', 'defg'] 70 | 71 | 72 | Cycle 73 | ----- 74 | 75 | Sometimes it is necessary to repeatedly process an iterable. ``Cycle`` takes 76 | all elements from its input iterable, stores them in memory and returns an 77 | iterator that cycles through the elements indefinitely. Here an example that 78 | cycles through 1, 2, 3 and takes the first 10 elements 79 | 80 | >>> [1, 2, 3] >> Cycle() >> Take(10) >> Collect() 81 | [1, 2, 3, 1, 2, 3, 1, 2, 3, 1] 82 | 83 | Note that ``Cycle`` will consume large amounts of memory if the input iterable 84 | is large. 85 | 86 | 87 | Permutate 88 | --------- 89 | 90 | ``Permutate([,r])`` returns successive ``r`` length permutations of 91 | the elements in the input iterable. 92 | 93 | >>> [1, 2, 3] >> Permutate(2) >> Collect() 94 | [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] 95 | 96 | Maybe a more interesting example: What is the number of distinctive 97 | palindroms for a given string: 98 | 99 | >>> IsPalindrom = nut_filter(lambda x: x == x[::-1]) 100 | >>> 'devoved' >> Permutate() >> IsPalindrom() >> Collect(set) >> Count() 101 | 6 102 | 103 | If no permutation size ``r`` is specified then all possible full-length 104 | permutations are generated (r!) and the computation will not finish in 105 | any reasonable time for non-small values of ``r`` ! 106 | 107 | 108 | Combine 109 | ------- 110 | 111 | ``Combine(r)`` return ``r`` length subsequences of the elements from the 112 | input iterable. 113 | 114 | >>> [1, 2, 3] >> Combine(2) >> Collect() 115 | [(1, 2), (1, 3), (2, 3)] 116 | 117 | Note that ``Combine(r)`` returns a subset of ``Permutate(r)`` with permutations 118 | where the order of the elements (as given in the input iterable) is preserved. 119 | 120 | 121 | 122 | Dedupe 123 | ------ 124 | 125 | A very common task is to remove all duplicates from a data set. 126 | ``Dedupe([key])`` performs this task and also takes a key function 127 | that defines which elements are treated as equal. 128 | 129 | ``Dedupe()`` preserves the order of the element in the input. See the 130 | following example 131 | 132 | >>> [2, 3, 1, 1, 2, 4] >> Dedupe() >> Collect() 133 | [2, 3, 1, 4] 134 | 135 | More complex data often require a more sophisticated definition of equality 136 | and the key functions provides this 137 | 138 | >>> data = [(1, 'a'), (2, 'a'), (3, 'b')] 139 | >>> data >> Dedupe(lambda (x, y): y) >> Collect() 140 | [(1, 'a'), (3, 'b')] 141 | 142 | 143 | ``Dedupe()`` memorizes all unique elements of the input iterable in a set 144 | and can potentially consume large amounts of memory! 145 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/recipes.rst.txt: -------------------------------------------------------------------------------- 1 | .. _recipes: 2 | 3 | Recipes 4 | ======= 5 | 6 | A collection of common problems with solutions. Make sure **nutsflow** has been imported. 7 | 8 | 9 | Write CSV file with column names 10 | -------------------------------- 11 | 12 | .. code:: Python 13 | 14 | with WriteCSV(filepath) as writer: 15 | [('Col1', 'Col2')] >> writer 16 | [(1, 2), (3, 4)] >> writer 17 | 18 | 19 | Load a mapping file 20 | ------------------- 21 | 22 | From a file with two columns create a directory that maps 23 | values in one column to the other. For instance, the following 24 | file contains Arabic numbers and their Roman counterparts. 25 | 26 | .. code:: 27 | 28 | arab,roman 29 | 1,I 30 | 2,II 31 | 3,III 32 | 33 | We create a Python dictionary that maps Arabic to Roman numbers by 34 | loading the CSV file, dropping the header line, converting Arabic numbers 35 | (that are loaded as strings) to integers and collecting the results in 36 | a dictionary 37 | 38 | >>> from nutsflow import * 39 | >>> fpath = 'tests/data/arab2num.csv' 40 | >>> arab2roman = ReadCSV(fpath) >> Drop(1) >> MapCol(0, int) >> Collect(dict) 41 | >>> arab2roman[2] 42 | 'II' 43 | 44 | 45 | For the reversed mapping (Roman to Arabic), we just flip the columns via ``GetCols`` 46 | and use ``skipheader=1`` to skip the header line 47 | 48 | >>> roman2arab = (ReadCSV(fpath, skipheader=1) >> MapCol(0, int) >> 49 | ... GetCols(1, 0) >> Collect(dict)) 50 | >>> roman2arab['III'] 51 | 3 52 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/sources.rst.txt: -------------------------------------------------------------------------------- 1 | .. _sources: 2 | 3 | Reading from Sources 4 | ==================== 5 | 6 | All data flows start with a *source*. Sources are Python iterables and a small 7 | set of specific nuts. As a general rule, sources must appear on the left side 8 | of the ``>>`` operator and can never appear on the right side. 9 | 10 | 11 | Iterables 12 | --------- 13 | 14 | Some examples of Python iterables and iterators that can be used as sources: 15 | 16 | >>> from nutsflow import * 17 | 18 | >>> range(5) >> Collect() 19 | [0, 1, 2, 3, 4] 20 | 21 | >>> ['a', 'ab', 'abc'] >> Map(len) >> Collect() 22 | [1, 2, 3] 23 | 24 | >>> 'text' >> Map(lambda c: c.upper()) >> Join() 25 | 'TEXT' 26 | 27 | >>> {1:'one', 2:'two'} >> Collect() 28 | [1, 2] 29 | 30 | >>> {1:'one', 2:'two'}.items() >> Collect() 31 | [(1, 'one'), (2, 'two')] 32 | 33 | .. code:: 34 | 35 | with open(filepath) as lines: 36 | lines >> Filter(lambda l: l.startswith('ERR')) >> Print() >> Consume() 37 | 38 | 39 | Source nuts 40 | ----------- 41 | 42 | **nuts-flow** has a few special source nuts. 43 | 44 | Range 45 | ^^^^^ 46 | 47 | ``Range(start [,end [, step]])`` essential operates the same as ``range`` 48 | but depletes. The following examples demonstrates the difference: 49 | 50 | >>> numbers = Range(5) 51 | >>> numbers >> Head(3) 52 | [0, 1, 2] 53 | >>> numbers >> Head(3) 54 | [3, 4] 55 | >>> numbers >> Head(3) 56 | [] 57 | 58 | Subsequent calls deplete the numbers iterator created with ``Range``, while 59 | ``range`` returns a new iterator every time when called and does not deplete: 60 | 61 | >>> numbers = range(5) 62 | >>> numbers >> Head(3) 63 | [0, 1, 2] 64 | >>> numbers >> Head(3) 65 | [0, 1, 2] 66 | 67 | 68 | Enumerate 69 | ^^^^^^^^^ 70 | 71 | ``Enumerate(start=0 [, step])`` returns an iterator over increasing integer 72 | numbers. In contrast to :ref:`Range` it does not have an upper limit and 73 | iterates indefinitely. 74 | 75 | >>> Enumerate(1) >> Zip('abc') >> Collect() 76 | [(1, 'a'), (2, 'b'), (3, 'c')] 77 | 78 | Often ``Enumerate`` is used to add line numbers to the lines of a file: 79 | 80 | .. code:: 81 | 82 | # Collect line numbers of empty lines 83 | with open(filepath) as lines: 84 | (Enumerate() >> Zip(lines) >> Filter(lambda (i,l): not l) >> 85 | Get(0) >> Collect()) 86 | 87 | 88 | Product 89 | ^^^^^^^ 90 | 91 | ``Product`` is the functional equivalent of a nested loop. It generates the 92 | cartesian product of the input iterables. For instance, the following example 93 | returns the coordinates of a 2x3 grid: 94 | 95 | >>> Product(Range(2), Range(3)) >> Collect() 96 | [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)] 97 | 98 | Each element of each input iterable is combined with each element of the 99 | other input iterables. 100 | 101 | 102 | Repeat 103 | ^^^^^^ 104 | 105 | The ``Repeat(value [, times]))`` nut returns the specified value the given 106 | number of times or indefinitely if not specified: 107 | 108 | >>> Repeat('a', 3) >> Collect() 109 | ['a', 'a', 'a'] 110 | 111 | >>> Repeat(1) >> Take(4) >> Collect() 112 | [1, 1, 1, 1] 113 | 114 | 115 | ReadNamedCSV 116 | ^^^^^^^^^^^^ 117 | 118 | **nuts-flow** supports reading from Comma Separated Format (CSV) files with 119 | header names via the ``ReadNamedCSV(filepath, colnames, fmtfunc, rowname, **kwargs)`` nut. 120 | Given the correct delimiter also files in Tab Separated Format (TSV) or other column 121 | formats can be read. Given a CSV file with the following content 122 | 123 | .. code:: 124 | 125 | A,B,C 126 | 1,2,3 127 | 4,5,6 128 | 129 | the code below reads the rows as named tuples, and converts 130 | the elements of the row into integers (fmtfunc=int): 131 | 132 | >>> filepath = 'tests/data/data.csv' 133 | >>> with ReadNamedCSV(filepath, fmtfunc=int) as reader: 134 | ... reader >> Print() >> Consume() 135 | Row(A=1, B=2, C=3) 136 | Row(A=4, B=5, C=6) 137 | 138 | Different convert functions for columns are suppported: 139 | 140 | 141 | >>> fmtfuncs = (int, str, float) 142 | >>> with ReadNamedCSV(filepath, fmtfunc=fmtfuncs) as reader: 143 | ... reader >> Print() >> Consume() 144 | Row(A=1, B='2', C=3.0) 145 | Row(A=4, B='5', C=6.0) 146 | 147 | ``ReadNamedCSV`` allows to read specific columns in a given/different order. 148 | Here we read columns 'B' and 'C' only in swapped order: 149 | 150 | 151 | >>> with ReadCSV(filepath, ('C', 'B')) as reader: 152 | ... reader >> Print() >> Consume() 153 | Row(C='3', B='2') 154 | Row(C='6', B='5') 155 | 156 | Finally, if 'Row' is not a good tuple name, it can be changed: 157 | 158 | 159 | >>> with ReadNamedCSV(filepath, rowname='Sample') as reader: 160 | ... reader >> Print() >> Consume() 161 | Sample(A='1', B='2', C='3') 162 | Sample(A='4', B='5', C='6') 163 | 164 | 165 | ReadCSV 166 | ^^^^^^^ 167 | 168 | ``ReadCSV()`` is very similar to ``ReadNamedCSV`` but can read CSV files 169 | without header information and returns (unnamed) tuples. 170 | 171 | >>> filepath = 'tests/data/data.csv' 172 | >>> with ReadCSV(filepath, skipheader=1, fmtfunc=int) as reader: 173 | ... reader >> Print() >> Consume() 174 | ... 175 | (1, 2, 3) 176 | (4, 5, 6) 177 | 178 | -------------------------------------------------------------------------------- /docs/html/_sources/tutorial/underscore.rst.txt: -------------------------------------------------------------------------------- 1 | .. _underscore: 2 | 3 | Underscore syntax 4 | ================= 5 | 6 | :ref:`Filter`, :ref:`Map` and other nuts that take a function as argument 7 | often use only very simple expressions. For instance, the following code 8 | extracts all number greater than five within range zero to nine: 9 | 10 | >>> from nutsflow import * 11 | 12 | >>> Range(10) >> Filter(lambda x: x > 5) >> Collect() 13 | [6, 7, 8, 9] 14 | 15 | For such simple expressions **nuts-flow** provides a special 16 | *underscore notation*, borrowed from `Scala `_, 17 | which results in shorter, more readable code 18 | 19 | >>> from nutsflow import _ 20 | 21 | >>> Range(10) >> Filter(_ > 5) >> Collect() 22 | [6, 7, 8, 9] 23 | 24 | This is equivalent to 25 | 26 | >>> Range(10) >> Filter(lambda _: _ > 5) >> Collect() 27 | [6, 7, 8, 9] 28 | 29 | but eliminates the need for the ``lambda`` keyword and its arguments. 30 | Note that the ``_`` must be imported explicitly, since it is also commonly 31 | used in Python as a placeholder for unused variables. 32 | 33 | In contrast to ``lambda`` functions or Scala's underscore the, 34 | underscore notation in **nuts-flow** is very limited and 35 | only supports expressions with arity one, e.g. 36 | ``_ + 1``, ``_ <= 3``, ``_ == 5``, ``_[0]``, ... 37 | More complex or nested expressions such as ``_ + _``, ``_ > 5 and _ < 10`` 38 | or ``len(_)`` are currently not permitted. 39 | 40 | -------------------------------------------------------------------------------- /docs/html/_static/css/badge_only.css: -------------------------------------------------------------------------------- 1 | .fa:before{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/html/_static/css/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/css/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '1.2.4', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false 12 | }; -------------------------------------------------------------------------------- /docs/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/file.png -------------------------------------------------------------------------------- /docs/html/_static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bold.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bold.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bolditalic.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bolditalic.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bolditalic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-bolditalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-bolditalic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-italic.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-italic.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-regular.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-regular.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-regular.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Lato/lato-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Lato/lato-regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Light.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Light.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Regular.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Thin.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/Roboto-Slab-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/Roboto-Slab-Thin.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-bold-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-bold-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-bold.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-bold.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-normal-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-normal-italic.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-normal-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-normal-italic.woff2 -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-normal.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-normal.woff -------------------------------------------------------------------------------- /docs/html/_static/fonts/lato-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/docs/html/_static/fonts/lato-normal.woff2 -------------------------------------------------------------------------------- /docs/html/_static/js/badge_only.js: -------------------------------------------------------------------------------- 1 | !function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=4)}({4:function(e,t,r){}}); -------------------------------------------------------------------------------- /docs/html/_static/js/html5shiv-printshiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3-pre | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/html/_static/js/html5shiv.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | !function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); -------------------------------------------------------------------------------- /docs/html/_static/js/theme.js: -------------------------------------------------------------------------------- 1 | !function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}t.length>0&&($(".wy-menu-vertical .current").removeClass("current"),t.addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l1").parent().addClass("current"),t.closest("li.toctree-l1").addClass("current"),t.closest("li.toctree-l2").addClass("current"),t.closest("li.toctree-l3").addClass("current"),t.closest("li.toctree-l4").addClass("current"),t.closest("li.toctree-l5").addClass("current"),t[0].scrollIntoView())}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current"),e.siblings().find("li.current").removeClass("current"),e.find("> ul li.current").removeClass("current"),e.toggleClass("current")}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t 4 | 5 | 6 | 7 | 8 | 9 | 10 | Search — nutsflow 1.2.4 documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |
47 | 48 | 105 | 106 |
107 | 108 | 109 | 115 | 116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 |
138 | 139 |
    140 | 141 |
  • »
  • 142 | 143 |
  • Search
  • 144 | 145 | 146 |
  • 147 | 148 | 149 | 150 |
  • 151 | 152 |
153 | 154 | 155 |
156 |
157 |
158 |
159 | 160 | 167 | 168 | 169 |
170 | 171 |
172 | 173 |
174 | 175 |
176 |
177 | 178 | 179 |
180 | 181 |
182 |

183 | 184 | © Copyright 2017, IBM Research Australia 185 | 186 | Last updated on Mar 04, 2021. 187 | 188 | 189 |

190 |
191 | 192 |
193 | 194 |
195 |
196 | 197 |
198 | 199 |
200 | 201 | 202 | 207 | 208 | 209 | 210 | 211 | 212 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /nutsflow/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.4' 2 | 3 | from nutsflow.source import (Enumerate, Repeat, Product, Empty, Range, ReadCSV, 4 | ReadNamedCSV) 5 | from nutsflow.processor import (Take, Slice, Concat, Interleave, Zip, ZipWith, 6 | Dedupe, Chunk, Cache, ChunkWhen, ChunkBy, Cycle, 7 | Flatten, FlattenCol, FlatMap, Map, Window, 8 | Filter, FilterFalse, FilterCol, Partition, 9 | TakeWhile, DropWhile, Permutate, Append, Insert, 10 | Combine, Tee, If, Drop, Pick, GroupBy, 11 | GroupBySorted, Clone, Shuffle, 12 | MapCol, MapMulti, MapPar, Prefetch, 13 | PrintProgress, Try) 14 | from nutsflow.function import (Identity, Square, NOP, Get, GetCols, Counter, 15 | Sleep, Format, Print, PrintColType, PrintType) 16 | from nutsflow.sink import (Sort, Sum, Mean, MeanStd, Max, Min, ArgMax, ArgMin, 17 | Reduce, Nth, Next, Consume, Count, Unzip, Head, Tail, 18 | CountValues, Collect, Join, WriteCSV) 19 | from nutsflow.factory import (nut_processor, nut_sink, nut_function, nut_source, 20 | nut_filter, nut_filterfalse) 21 | from nutsflow.base import Nut, NutFunction, NutSink, NutSource 22 | from nutsflow.common import Timer, print_type 23 | from nutsflow.config import Config, load_config 24 | from nutsflow.underscore import _ 25 | -------------------------------------------------------------------------------- /nutsflow/base.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: base 3 | :synopsis: Base classes and functions. 4 | """ 5 | 6 | from six.moves import map 7 | 8 | 9 | class Nut(object): 10 | """ 11 | Base class for all Nuts. Iterables or functions wrapped in Nuts can be 12 | chained using the '>>' operator. The aim is code with an explicit 13 | data flow. See the following example using Python iterators versus Nuts: 14 | 15 | >>> from six.moves import filter, range 16 | >>> from itertools import islice 17 | >>> list(islice(filter(lambda x: x > 5, range(10)), 3)) 18 | [6, 7, 8] 19 | 20 | >>> from nutsflow import Range, Filter, Take, Collect, _ 21 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 22 | [6, 7, 8] 23 | """ 24 | 25 | def __init__(self, *args, **kwargs): 26 | """ 27 | Constructor. Nuts (and derived classes) can have arbitrary arguments. 28 | 29 | :param args args: Positional arguments. 30 | :param kwargs kwargs: Keyword arguments. 31 | """ 32 | self.args = args 33 | self.kwargs = kwargs 34 | 35 | def __call__(self, iterable): 36 | """ 37 | Nut (processor) can be called as a function and mapped on 38 | iterable elements within an iterable. 39 | 40 | :param iterable iterable: Iterable to process. 41 | :return: Iterable 42 | :rtype: iterable 43 | """ 44 | return self.__rrshift__(iterable) 45 | 46 | def __rrshift__(self, iterable): 47 | """ 48 | Chaining operator for Nuts. Needs to be overridden! 49 | 50 | Takes an input iterable and produces some output iterable. 51 | If the number of elements in the input and the output iterable 52 | does not change consider NutFunction instead. 53 | 54 | :param iterable iterable: Iterable to process. 55 | :return: Iterable 56 | :rtype: iterable 57 | :raise: NotImplementedError if not implemented. 58 | """ 59 | raise NotImplementedError( 60 | 'Needs to implement __rrshift__ : ' + str(self.__class__.__name__)) 61 | 62 | 63 | class NutFunction(Nut): 64 | """ 65 | Nut functions are are mapped onto each element of the input iterable. 66 | 67 | Example: Square is a Nut function 68 | 69 | >>> from nutsflow import Square, Collect, _ 70 | >>> [1,2,3] >> Square() >> Collect() 71 | [1, 4, 9] 72 | """ 73 | 74 | def __call__(self, element): 75 | """ 76 | Override this method to transform the elements of an iterable. 77 | 78 | :param element: Element the function is applied to. 79 | :return: A transformed element 80 | :rtype: any 81 | :raise: NotImplementedError if not implemented. 82 | """ 83 | raise NotImplementedError( 84 | 'Needs to implement __call__() : ' + str(self.__class__.__name__)) 85 | 86 | def __rrshift__(self, iterable): 87 | """ 88 | Map function onto iterable and return transformed iterable. 89 | Do not override! 90 | 91 | :param iterable: function is applied to the elements of the iterable. 92 | :return: transformed iterable. 93 | :rtype: iterable 94 | """ 95 | return map(self, iterable) 96 | 97 | 98 | class NutSource(Nut): 99 | """ 100 | Sources are nuts that have no input iterable but produce an output 101 | iterable. 102 | """ 103 | 104 | def __rrshift__(self, iterable): 105 | """ 106 | Raises an exception when called. Sources have not input! 107 | Do not override! Override __iter__() instead. 108 | 109 | :param iterable iterable: Iterable 110 | :raise: SyntaxError if called. 111 | """ 112 | raise SyntaxError( 113 | "Sources don't have inputs: " + str(self.__class__.__name__)) 114 | 115 | def __iter__(self): 116 | """ 117 | Return iterator over some data. Needs to be overridden! 118 | 119 | :return: Iterator for source data. 120 | :rtype: iterator 121 | :raise: NotImplementedError if not implemented. 122 | """ 123 | raise NotImplementedError( 124 | 'Needs to implement __iter__() :' + str(self.__class__.__name__)) 125 | 126 | 127 | class NutSink(Nut): 128 | """ 129 | Sinks are nuts that typically consume the entire input stream. 130 | 131 | Sinks are typically at the end of a flow and aggregate the flow 132 | to a single output, e.g. the sum of its elements. 133 | Need to override __rrshift__()! 134 | """ 135 | 136 | def __iter__(self): 137 | """ 138 | Raises an exception when called. Sinks should not serve as sources. 139 | 140 | :raise: SyntaxError if called. 141 | """ 142 | raise SyntaxError( 143 | 'Sinks cannot be inputs: ' + str(self.__class__.__name__)) 144 | 145 | def __call__(self, iterable): 146 | """ 147 | Sinks can serve as functions applied to iterables within a flow. 148 | 149 | :param iterable: Sink takes iterable as input 150 | :return: Output of sink 151 | :rtype: any 152 | """ 153 | return iter(iterable) >> self 154 | -------------------------------------------------------------------------------- /nutsflow/config.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: config 3 | :synopsis: Handling of configuration files. 4 | """ 5 | 6 | import os 7 | import yaml 8 | import json 9 | 10 | 11 | class Config(dict): 12 | """ 13 | Dictionary that allows access via keys or attributes. 14 | 15 | Used to store and access configuration data. 16 | """ 17 | 18 | def __init__(self, *args, **kwargs): 19 | """ 20 | Create dictionary. 21 | 22 | >>> contact = Config({'name':'stefan', 'address':{'number':12}}) 23 | >>> contact['name'] 24 | 'stefan' 25 | 26 | >>> contact.name 27 | 'stefan' 28 | 29 | >>> contact.address.number 30 | 12 31 | 32 | >>> contact.surname = 'maetschke' 33 | >>> contact.surname 34 | 'maetschke' 35 | 36 | :param args args: See dict 37 | :param kwargs kwargs: See dict 38 | """ 39 | wrap = lambda v: Config(v) if type(v) is dict else v 40 | contents = ((k, wrap(v)) for k, v in dict(*args, **kwargs).items()) 41 | super(Config, self).__init__(contents) 42 | self.__dict__ = self 43 | 44 | @staticmethod 45 | def isjson(filepath): 46 | """ 47 | Return true if filepath ends with '.json'. 48 | 49 | :param str filepath: Filepaht 50 | :return: True if filepath points ot JSON file. 51 | :rtype: bool 52 | """ 53 | return filepath.lower().endswith('.json') 54 | 55 | def __repr__(self): 56 | return json.dumps(self, indent=2, sort_keys=True) 57 | 58 | def load(self, filepath): 59 | """ 60 | Load configuration from file in JSON or YAML format. 61 | 62 | >>> cfg = Config().load('tests/data/configuration.json') 63 | >>> cfg.number 64 | 13 65 | 66 | :param str filepath: Path to JSON or YAML file. 67 | :return: returns loaded configuration. 68 | :rtype: Config 69 | """ 70 | yaml_load = lambda fp: yaml.load(fp, Loader=yaml.SafeLoader) 71 | reader = json.load if Config.isjson(filepath) else yaml_load 72 | with open(filepath, 'r') as f: 73 | self.__init__(reader(f)) 74 | return self 75 | 76 | def save(self, filepath): 77 | """ 78 | Save configuration to file in JSON or YAML format. 79 | 80 | >>> cfg = Config({'number': 13, 'name': 'Stefan'}) 81 | >>> cfg.save('tests/data/configuration.yaml') 82 | 83 | :param str filepath: Filepath. Should end with '.json' or '.yaml' 84 | """ 85 | writer = json.dump if Config.isjson(filepath) else yaml.dump 86 | with open(filepath, 'w') as f: 87 | writer(dict(self), f) 88 | 89 | 90 | def load_config(filename): 91 | """ 92 | Load configuration file in YAML format from locations in defined order. 93 | 94 | The search order for the config file is: 95 | 1) user home dir 96 | 2) current dir 97 | 3) full path 98 | 99 | | Example file: 'tests/data/config.yaml' 100 | | filepath : c:/Maet 101 | | imagesize : [100, 200] 102 | 103 | >>> cfg = load_config('tests/data/config.yaml') 104 | >>> cfg.filepath 105 | 'c:/Maet' 106 | 107 | >>> cfg['imagesize'] 108 | [100, 200] 109 | 110 | :param filename: Name or full path of configuration file. 111 | :return: dictionary with config data. Note that config data can be 112 | accessed by key or attribute, e.g. cfg.filepath or cfg.['filepath'] 113 | :rtype: ConfigDict 114 | """ 115 | filepaths = [] 116 | for dirpath in os.path.expanduser('~'), os.curdir, '': 117 | try: 118 | filepath = os.path.join(dirpath, filename) 119 | filepaths.append(filepath) 120 | with open(filepath, 'r') as f: 121 | return Config(yaml.safe_load(f)) 122 | except IOError: 123 | pass 124 | raise IOError('Configuration file not found: ' + ', '.join(filepaths)) 125 | -------------------------------------------------------------------------------- /nutsflow/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/nutsflow/examples/__init__.py -------------------------------------------------------------------------------- /nutsflow/examples/examples.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | 3 | from six.moves import range, zip 4 | from nutsflow import _ 5 | from nutsflow import * 6 | 7 | 8 | @nut_processor 9 | def MyClone(iterable, n): 10 | for e in iterable: 11 | for _ in range(n): 12 | yield e 13 | 14 | 15 | @nut_filter 16 | def MyGreaterThan(x, threshold): 17 | return x > threshold 18 | 19 | 20 | @nut_filterfalse 21 | def MySmallerOrEqualThan(x, threshold): 22 | return x > threshold 23 | 24 | 25 | @nut_source 26 | def MyRange(start, end): 27 | return iter(range(start, end)) 28 | 29 | 30 | @nut_sink 31 | def MyCollect(iterable): 32 | return list(iterable) 33 | 34 | 35 | @nut_function 36 | def MyTimes(x, c): 37 | return c * x 38 | 39 | 40 | @nut_processor 41 | def MyPipeline(iterable, func): 42 | Dup = nut_function(lambda x: (x, x)) 43 | return iterable >> Dup() >> Flatten() >> Map(func) 44 | 45 | 46 | def run(datapath): 47 | print(Range(10) >> Shuffle(1) >> Collect()) 48 | 49 | print(Range(5) >> MyPipeline(Square()) >> Collect()) 50 | 51 | print(MyRange(1, 10) >> MySmallerOrEqualThan(5) >> MyCollect()) 52 | 53 | print(MyRange(1, 10) >> MyTimes(3) >> MyGreaterThan(5) >> MyCollect()) 54 | 55 | print(MyRange(1, 5) >> MyClone(2) >> MyCollect()) 56 | 57 | print(Range(5, 10) >> Zip(Repeat('a')) >> Collect()) 58 | 59 | print(Range(10) >> Head(5)) 60 | 61 | print(Range(10) >> Tail(5)) 62 | 63 | print(Range(10) >> Drop(3) >> Collect()) 64 | 65 | print([1, 2, 2, 3, 3, 3] >> CountValues()) 66 | 67 | print(Product([1, 2], ['a', 'b']) >> Collect()) 68 | 69 | print([1, 2, 3] >> Permutate(2) >> Collect()) 70 | 71 | print([1, 2, 3] >> Map(_ * 2) >> Collect()) 72 | 73 | print(Range(10) >> Identity() >> Collect()) 74 | 75 | print(Range(10) >> Take(5) >> Collect()) 76 | 77 | print(range(10) >> TakeWhile(_ < 5) >> Collect()) 78 | 79 | print(range(10) >> Filter(_ < 5) >> Collect()) 80 | 81 | print(range(10) >> FilterFalse(_ < 5) >> Collect()) 82 | 83 | print(range(10) >> Map(_ * 5) >> Collect()) 84 | 85 | print('abc' >> Cycle() >> Take(5) >> Collect()) 86 | 87 | print('aabacddeaf' >> Dedupe(_ < 'c') >> Collect()) 88 | 89 | print(range(10) >> Zip('abcd') >> Collect()) 90 | 91 | print(zip([1, 2, 3], 'abc') >> Map(_[::-1]) >> Collect()) 92 | 93 | print(range(10) >> Zip('abcd') >> Map(_[0]) >> Collect()) 94 | 95 | print(range(10) >> Interleave('abcd') >> Collect()) 96 | 97 | print(Empty() >> Collect()) 98 | 99 | print(range(20) >> Prefetch() >> Collect()) 100 | 101 | with ReadCSV(datapath + 'data.csv') as reader: 102 | print('data.csv:', reader >> Collect()) 103 | 104 | with open(datapath + 'numbers.txt') as f: 105 | print('numbers.txt:', f >> Collect()) 106 | 107 | nums, twos, greater5 = Range(10) >> MapMulti(_, _ * 2, _ > 5) 108 | nums >> Zip(twos, greater5) >> Print() >> Consume() 109 | 110 | debug = False 111 | Range(10) >> If(debug, Print()) >> Consume() 112 | 113 | do_square = True 114 | print(Range(10) >> If(do_square, Square()) >> Collect()) 115 | 116 | # numbers = range(100) 117 | # numbers >> PrintProgress(numbers, 0) >> Sleep(0.1) >> Consume() 118 | 119 | 120 | if __name__ == '__main__': # pragma: no cover 121 | run(r'../../tests/data/') 122 | -------------------------------------------------------------------------------- /nutsflow/underscore.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: underscore 3 | :synopsis: Enables underscore to be used as anonymous variable. 4 | """ 5 | from __future__ import division 6 | 7 | from functools import partial, wraps 8 | import operator as op 9 | 10 | 11 | def _wrap(func, args, flip=True): 12 | """Return partial function with flipped args if flip=True 13 | 14 | :param function func: Any function 15 | :param args args: Function arguments 16 | :param bool flip: If true reverse order of arguments. 17 | :return: Returns function 18 | :rtype: function 19 | """ 20 | 21 | @wraps(func) 22 | def flippedfunc(*args): 23 | return func(*args[::-1]) 24 | 25 | return partial(flippedfunc if flip else func, args) 26 | 27 | 28 | # TODO: support len(_), (_ in 'test'), (_ + _) and similar constructs 29 | class _Underscore(object): 30 | """ 31 | Placeholder class for anonymous variables. Allows constructs such as: 32 | 33 | >>> list(map(_ * 2, range(5))) 34 | [0, 2, 4, 6, 8] 35 | 36 | >>> list(filter(_ < 3, range(5))) 37 | [0, 1, 2] 38 | """ 39 | 40 | __call__ = lambda self, arg: arg 41 | __add__ = lambda self, arg: _wrap(op.add, arg) 42 | __radd__ = lambda self, arg: _wrap(op.add, arg, False) 43 | __sub__ = lambda self, arg: _wrap(op.sub, arg) 44 | __rsub__ = lambda self, arg: _wrap(op.sub, arg, False) 45 | __mul__ = lambda self, arg: _wrap(op.mul, arg) 46 | __rmul__ = lambda self, arg: _wrap(op.mul, arg, False) 47 | __truediv__ = lambda self, arg: _wrap(op.truediv, arg) 48 | __rtruediv__ = lambda self, arg: _wrap(op.truediv, arg, False) 49 | __floordiv__ = lambda self, arg: _wrap(op.floordiv, arg) 50 | __rfloordiv__ = lambda self, arg: _wrap(op.floordiv, arg, False) 51 | __mod__ = lambda self, arg: _wrap(op.mod, arg) 52 | __rmod__ = lambda self, arg: _wrap(op.mod, arg, False) 53 | 54 | __eq__ = lambda self, arg: _wrap(op.eq, arg) 55 | __ne__ = lambda self, arg: _wrap(op.ne, arg) 56 | __lt__ = lambda self, arg: _wrap(op.lt, arg) 57 | __le__ = lambda self, arg: _wrap(op.le, arg) 58 | __gt__ = lambda self, arg: _wrap(op.gt, arg) 59 | __ge__ = lambda self, arg: _wrap(op.ge, arg) 60 | 61 | __getitem__ = lambda self, arg: _wrap(op.getitem, arg) 62 | 63 | 64 | _ = _Underscore() 65 | -------------------------------------------------------------------------------- /origin): -------------------------------------------------------------------------------- 1 | ============================= test session starts ============================= 2 | platform win32 -- Python 3.8.5, pytest-6.0.0, py-1.9.0, pluggy-0.13.0 3 | rootdir: C:\Maet\Projects\Python\nuts-flow, configfile: pytest.ini 4 | plugins: cov-2.10.1 5 | collected 0 items 6 | 7 | ============================ no tests ran in 0.02s ============================ 8 | -------------------------------------------------------------------------------- /pics/nutsflow_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/pics/nutsflow_logo.gif -------------------------------------------------------------------------------- /push_docs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # based on 3 | # http://www.willmcginnis.com/2016/02/29/automating-documentation-workflow-with-sphinx-and-github-pages/ 4 | # Run this script in the project root 5 | 6 | # Windows: 7 | # use make tool from gnuwin32 and add to system path 8 | # http://gnuwin32.sourceforge.net/packages/make.htm 9 | # and use Windows git shell (https://gitforwindows.org/) to run script as 10 | # ./push_docs 11 | 12 | # build the docs 13 | cd sphinx 14 | make clean 15 | make html 16 | cd .. 17 | 18 | # commit and push 19 | git add -A 20 | git commit -m "documentation updated" 21 | git push origin master 22 | 23 | # switch to gh-pages branch and pull docs 24 | git checkout gh-pages 25 | rm -rf ./docs 26 | rm -rf ./tutorial 27 | rm -rf _* 28 | touch .nojekyll 29 | git checkout master docs/html 30 | 31 | # move docs to root and delete docs 32 | mv ./docs/html/* ./ 33 | rm -rf ./docs 34 | 35 | # push updated gh-pages 36 | git add -A 37 | git commit -m "documentation updated" 38 | git push origin gh-pages 39 | 40 | # switch back to master 41 | git checkout master -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | norecursedirs = build dist .tox .eggs html data nuts_flow.egg-info 3 | addopts = --doctest-modules --ignore=setup.py --doctest-glob='*.rst_DISABLED' 4 | doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import glob 4 | import shutil 5 | import nutsflow 6 | 7 | from setuptools import setup, find_packages, Command 8 | from setuptools.command.test import test as TestCommand 9 | 10 | 11 | class CleanCommand(Command): 12 | user_options = [] 13 | 14 | def initialize_options(self): 15 | pass 16 | 17 | def finalize_options(self): 18 | pass 19 | 20 | def run(self): 21 | for folder in ['build', 'dist']: 22 | if os.path.exists(folder): 23 | shutil.rmtree(folder) 24 | for egg_file in glob.glob('*egg-info'): 25 | shutil.rmtree(egg_file) 26 | 27 | 28 | class PyTest(TestCommand): 29 | def finalize_options(self): 30 | TestCommand.finalize_options(self) 31 | self.test_args = [] 32 | self.test_suite = True 33 | 34 | def run_tests(self): 35 | import pytest 36 | errcode = pytest.main(self.test_args) 37 | sys.exit(errcode) 38 | 39 | 40 | def load_readme(): 41 | with open('README.rst') as f: 42 | return f.read() 43 | 44 | 45 | setup( 46 | name=nutsflow.__name__, 47 | version=nutsflow.__version__, 48 | url='https://maet3608.github.io/nuts-flow', 49 | download_url='https://github.com/maet3608/nuts-flow', 50 | license='Apache Software License (http://www.apache.org/licenses/LICENSE-2.0)', 51 | author='Stefan Maetschke', 52 | author_email='stefan.maetschke@gmail.com', 53 | description='A simple data-flow framework based on iterator chaining', 54 | long_description=load_readme(), 55 | long_description_content_type='text/x-rst', 56 | install_requires=['six >= 1.10.0', 'pyyaml >= 3.12'], 57 | tests_require=['pytest-cov', 'pytest >= 3.0.3', 'numpy >= 1.14'], 58 | packages=find_packages(exclude=['setup']), 59 | include_package_data=True, 60 | platforms='any', 61 | cmdclass={ 62 | 'test': PyTest, 63 | 'clean': CleanCommand, 64 | }, 65 | classifiers=[ 66 | 'Development Status :: 5 - Production/Stable', 67 | 'Programming Language :: Python :: 2.7', 68 | 'Programming Language :: Python :: 3.5', 69 | 'Programming Language :: Python :: 3.6', 70 | 'Programming Language :: Python :: 3.7', 71 | 'Programming Language :: Python :: 3.8', 72 | 'Programming Language :: Python :: 3.9', 73 | 'Natural Language :: English', 74 | 'Intended Audience :: Developers', 75 | 'Intended Audience :: Science/Research', 76 | 'License :: OSI Approved :: Apache Software License', 77 | 'Operating System :: OS Independent', 78 | 'Topic :: Software Development :: Libraries :: Python Modules', 79 | ], 80 | ) 81 | -------------------------------------------------------------------------------- /sphinx/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/sphinx/.nojekyll -------------------------------------------------------------------------------- /sphinx/source/contributions.rst: -------------------------------------------------------------------------------- 1 | Contributions 2 | ============= 3 | 4 | Contributions to **nuts-flow** are welcome. Please document the code following 5 | the examples, provide unit tests and ensure that ``pytest`` runs without 6 | errors. 7 | 8 | 9 | Environment 10 | ----------- 11 | 12 | You will need git, Python, pytest, Sphinx installed. 13 | 14 | 15 | Unit tests 16 | ---------- 17 | 18 | .. code :: 19 | 20 | $ cd nutsflow 21 | $ pytest 22 | ============================= test session starts ============================= 23 | platform win32 -- Python 2.7.13, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 24 | rootdir: C:\Maet\Projects\Python\nuts-flow, inifile: pytest.ini 25 | plugins: cov-2.3.1 26 | collected 138 items 27 | 28 | README.rst . 29 | nutsflow\base.py .. 30 | nutsflow\common.py .... 31 | nutsflow\function.py ......... 32 | nutsflow\iterfunction.py ........... 33 | nutsflow\processor.py ................... 34 | nutsflow\sink.py ............ 35 | nutsflow\source.py ...... 36 | nutsflow\underscore.py . 37 | sphinx\source\installation.rst . 38 | sphinx\source\introduction.rst . 39 | sphinx\source\tutorial\custom_nuts.rst . 40 | sphinx\source\tutorial\debugging.rst . 41 | sphinx\source\tutorial\divide_conquer.rst . 42 | sphinx\source\tutorial\error_messages.rst . 43 | sphinx\source\tutorial\filtering.rst . 44 | sphinx\source\tutorial\nuts_basics.rst . 45 | sphinx\source\tutorial\performance.rst . 46 | sphinx\source\tutorial\practice_problems.rst . 47 | sphinx\source\tutorial\prerequisites.rst . 48 | sphinx\source\tutorial\rearranging.rst . 49 | sphinx\source\tutorial\recipes.rst . 50 | sphinx\source\tutorial\sinks.rst . 51 | sphinx\source\tutorial\sources.rst . 52 | sphinx\source\tutorial\transforming.rst . 53 | sphinx\source\tutorial\underscore.rst . 54 | tests\nutsflow\test_base.py .... 55 | tests\nutsflow\test_common.py ...... 56 | tests\nutsflow\test_factory.py ......... 57 | tests\nutsflow\test_function.py ......... 58 | tests\nutsflow\test_iterfunction.py ........... 59 | tests\nutsflow\test_processor.py ................................... 60 | tests\nutsflow\test_sink.py ................ 61 | tests\nutsflow\test_source.py ....... 62 | tests\nutsflow\test_underscore.py .. 63 | tests\nutsflow\examples\test_examples.py . 64 | 65 | ========================= 182 passed in 19.58 seconds ========================= 66 | 67 | 68 | 69 | We are aiming at a code coverage of 100%. Run ``pytest --cov`` for verification. 70 | 71 | .. code :: 72 | 73 | $ cd nutsflow 74 | $ pytest --cov 75 | 76 | ---------- coverage: platform win32, python 2.7.13-final-0 ----------- 77 | Name Stmts Miss Cover 78 | --------------------------------------------------- 79 | nutsflow\base.py 22 0 100% 80 | nutsflow\common.py 27 0 100% 81 | nutsflow\examples\examples.py 57 0 100% 82 | nutsflow\factory.py 39 0 100% 83 | nutsflow\function.py 69 0 100% 84 | nutsflow\iterfunction.py 65 0 100% 85 | nutsflow\processor.py 160 0 100% 86 | nutsflow\sink.py 85 0 100% 87 | nutsflow\source.py 40 0 100% 88 | nutsflow\underscore.py 26 0 100% 89 | --------------------------------------------------- 90 | TOTAL 590 0 100% 91 | 92 | 93 | 94 | Documentation 95 | ------------- 96 | 97 | Update Sphinx/HTML documentation as follows 98 | 99 | .. code:: 100 | 101 | cd sphinx 102 | make clean 103 | make html 104 | make test 105 | 106 | cd .. 107 | ./push_docs 108 | 109 | 110 | Style guide 111 | ----------- 112 | 113 | Code should be formatted following the `PEP-8 `_ 114 | style guide. 115 | 116 | Names of *nuts* shoulds be in CamelCase (just like class names) and describe an action, 117 | e.g. ``ReadCSV`` but not ``CSVReader``. 118 | 119 | Prefer *immutable* data types, e.g. tuples over lists, for outputs of nuts and 120 | avoid nuts with *side-effects. Nuts should not *mutate* their input data but create 121 | copies. 122 | 123 | If a nut has no input it should be a *source*, for instance like ``Range``. 124 | If it doesn't output a generator or iterator it should be a *sink*, 125 | see ``Collect`` for example. 126 | If a nut outputs the same number of elements it reads, it probably 127 | is a *function* (e.g. ``Square``) otherwise a *processor*. -------------------------------------------------------------------------------- /sphinx/source/index.rst: -------------------------------------------------------------------------------- 1 | .. Nutsflow documentation master file 2 | 3 | Welcome to nuts-flow 4 | ==================== 5 | 6 | .. image:: pics/nutsflow_logo.gif 7 | :align: center 8 | 9 | **nuts-flow** is a flow-programming framework based on the chaining of 10 | iterators using the ``>>`` operator. Here a small example 11 | 12 | >>> from nutsflow import Range, Filter, Take, Collect, _ 13 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 14 | [6, 7, 8] 15 | 16 | For a quick start have a look at the :ref:`Introduction` and for 17 | more detailed information see the :ref:`Tutorial` . Skim over the 18 | short description of all nuts in the :ref:`Overview` for an 19 | overall impression of the available functionality. 20 | 21 | 22 | .. toctree:: 23 | :maxdepth: 1 24 | 25 | introduction 26 | installation 27 | overview 28 | tutorial/introduction 29 | contributions 30 | links 31 | nutsflow 32 | 33 | 34 | Indices 35 | ======= 36 | 37 | * :ref:`genindex` 38 | * :ref:`modindex` 39 | * :ref:`search` 40 | 41 | -------------------------------------------------------------------------------- /sphinx/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | Standard 6 | -------- 7 | 8 | Installation via ``pip`` from `PyPi `_ 9 | 10 | .. code:: 11 | 12 | pip install nutsflow 13 | 14 | 15 | Verification 16 | ------------ 17 | 18 | Quick check that it works 19 | 20 | .. doctest:: 21 | 22 | python 23 | >>> from nutsflow import * 24 | >>> Range(5) >> Square() >> Collect() 25 | [0, 1, 4, 9, 16] 26 | 27 | 28 | You can also run the entire unit test suite using ``pytest``: 29 | 30 | .. code:: 31 | 32 | cd my_python_path/site-packages/nutflow 33 | pytest 34 | 35 | 36 | .. note:: 37 | 38 | If you don't know where your ``site-packages`` are, run the following code 39 | 40 | .. code:: 41 | 42 | python -c "import site; print(site.getsitepackages())" 43 | ['C:\\Maet\\Software\\Anaconda', 'C:\\Maet\\Software\\Anaconda\\lib\\site-packages'] 44 | 45 | 46 | Bleeding-edge 47 | ------------- 48 | 49 | If you want the bleeding-edge version, install via 50 | ``git clone`` from `GitHub `_ 51 | 52 | .. code:: 53 | 54 | git clone https://github.com/maet3608/nuts-flow.git 55 | cd nuts-flow 56 | python setup.py install 57 | pytest 58 | 59 | 60 | Upgrade 61 | ------- 62 | 63 | For upgrading an existing installation 64 | 65 | .. code:: 66 | 67 | pip install nutsflow --upgrade 68 | 69 | or if installed via ``git clone`` and ``setup.py`` 70 | 71 | .. code:: 72 | 73 | cd nuts-flow 74 | python setup.py install --force 75 | 76 | 77 | Virtual environment 78 | ------------------- 79 | 80 | Create virtual environment: 81 | 82 | .. code:: 83 | 84 | pip install virtualenv 85 | cd my_projects 86 | virtualenv vnuts 87 | 88 | 89 | Activate/deactivate environment: 90 | 91 | **Linux, Mac** 92 | 93 | .. code:: 94 | 95 | $ source vnuts/bin/activate 96 | $ deactivate 97 | 98 | 99 | **Windows** 100 | 101 | .. code:: 102 | 103 | > vnuts\Scripts\activate.bat 104 | > vnuts\Scripts\deactivate.bat 105 | 106 | 107 | Install **nuts-flow** in virtual environment (here for Linux, Mac) 108 | 109 | .. code:: 110 | 111 | source vnuts/bin/activate 112 | pip install nutsflow 113 | 114 | 115 | -------------------------------------------------------------------------------- /sphinx/source/introduction.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | **nuts-flow** is a data processing pipeline that is largely based on Python's 5 | `itertools `_. 6 | 7 | **nuts** are thin wrappers around itertool functions and 8 | provide a ``>>`` operator to chain iterators in pipelines 9 | to construct data flows. The result is an easier to read flow of data. 10 | 11 | The following two examples show the same data flow. The first is 12 | using Python's itertools and the second is using **nuts-flow**: 13 | 14 | >>> from itertools import islice 15 | >>> list(islice(filter(lambda x: x > 5, range(10)), 3)) 16 | [6, 7, 8] 17 | 18 | >>> from nutsflow import Range, Filter, Take, Collect, _ 19 | >>> Range(10) >> Filter(_ > 5) >> Take(3) >> Collect() 20 | [6, 7, 8] 21 | 22 | Both data flows extract the first three integers 23 | in the interval [0, 8[ that are greater than five. However, 24 | the linear arrangment of processing steps with **nuts-flow** is 25 | easier to read than the nested calls of itertool functions. 26 | 27 | **nuts-flow** is the base library for 28 | `nuts-ml `_, a 29 | data pre-processing pipeline for deep learning. -------------------------------------------------------------------------------- /sphinx/source/links.rst: -------------------------------------------------------------------------------- 1 | Links 2 | ===== 3 | 4 | Links to other interesting projects regarding data flows or functional programming 5 | under Python. 6 | 7 | 8 | - `toolz `_ 9 | - `PyFunctional `_ 10 | - `fn `_ 11 | -------------------------------------------------------------------------------- /sphinx/source/modules.rst: -------------------------------------------------------------------------------- 1 | nutsflow 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | nutsflow 8 | -------------------------------------------------------------------------------- /sphinx/source/nutsflow.examples.rst: -------------------------------------------------------------------------------- 1 | nutsflow.examples package 2 | ========================= 3 | 4 | Submodules 5 | ---------- 6 | 7 | nutsflow.examples.examples module 8 | --------------------------------- 9 | 10 | .. automodule:: nutsflow.examples.examples 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | 16 | Module contents 17 | --------------- 18 | 19 | .. automodule:: nutsflow.examples 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | -------------------------------------------------------------------------------- /sphinx/source/nutsflow.rst: -------------------------------------------------------------------------------- 1 | nutsflow package 2 | ================ 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | nutsflow.examples 10 | 11 | Submodules 12 | ---------- 13 | 14 | nutsflow.base module 15 | -------------------- 16 | 17 | .. automodule:: nutsflow.base 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | nutsflow.common module 23 | ---------------------- 24 | 25 | .. automodule:: nutsflow.common 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | nutsflow.factory module 31 | ----------------------- 32 | 33 | .. automodule:: nutsflow.factory 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | nutsflow.function module 39 | ------------------------ 40 | 41 | .. automodule:: nutsflow.function 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | nutsflow.iterfunction module 47 | ---------------------------- 48 | 49 | .. automodule:: nutsflow.iterfunction 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | nutsflow.processor module 55 | ------------------------- 56 | 57 | .. automodule:: nutsflow.processor 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | nutsflow.sink module 63 | -------------------- 64 | 65 | .. automodule:: nutsflow.sink 66 | :members: 67 | :undoc-members: 68 | :show-inheritance: 69 | 70 | nutsflow.source module 71 | ---------------------- 72 | 73 | .. automodule:: nutsflow.source 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | nutsflow.underscore module 79 | -------------------------- 80 | 81 | .. automodule:: nutsflow.underscore 82 | :members: 83 | :undoc-members: 84 | :show-inheritance: 85 | 86 | 87 | Module contents 88 | --------------- 89 | 90 | .. automodule:: nutsflow 91 | :members: 92 | :undoc-members: 93 | :show-inheritance: 94 | -------------------------------------------------------------------------------- /sphinx/source/pics/nutsflow_logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/sphinx/source/pics/nutsflow_logo.gif -------------------------------------------------------------------------------- /sphinx/source/tutorial/divide_conquer.rst: -------------------------------------------------------------------------------- 1 | .. _divide_conquer: 2 | 3 | Divide and conquer 4 | ================== 5 | 6 | It is frequently necessary to either split a data flow into multiple flows 7 | or combine data flows. The following nuts are specifically designed for this 8 | purpose. In this context the :ref:`Partition` and the :ref:`MapMulti` nuts 9 | might be of interest as well. 10 | 11 | 12 | Zip 13 | ^^^ 14 | 15 | ``Zip(*iterables)`` combines two or more iterables like a *zipper* taking at 16 | every step an element from each iterable and outputting a tuple of the 17 | grouped elements. Here an example 18 | 19 | >>> from nutsflow import * 20 | 21 | >>> numbers = [0, 1, 2] 22 | >>> letters = ['a', 'b', 'c'] 23 | >>> numbers >> Zip(letters) >> Collect() 24 | [(0, 'a'), (1, 'b'), (2, 'c')] 25 | 26 | ``Zip`` finishes when the shortest iterable is exhausted. See 27 | 28 | >>> Range(100) >> Zip('abc') >> Collect() 29 | [(0, 'a'), (1, 'b'), (2, 'c')] 30 | 31 | Note that ``Zip`` can zip more than two iterables: 32 | 33 | >>> '12' >> Zip('ab', '+-') >> Collect() 34 | [('1', 'a', '+'), ('2', 'b', '-')] 35 | 36 | If the output of ``Zip`` is required to be flat ``Flatten`` can be called 37 | 38 | >>> [0, 1, 2] >> Zip('abc') >> Flatten() >> Collect() 39 | [0, 'a', 1, 'b', 2, 'c'] 40 | 41 | but using :ref:`Interleave` is simpler in this case. 42 | 43 | Instead of nuts-flow's ``Zip``, Python's ``zip`` could be used alternatively: 44 | 45 | >>> zip(numbers, letters) >> Print() >> Consume() 46 | (0, 'a') 47 | (1, 'b') 48 | (2, 'c') 49 | 50 | 51 | Unzip 52 | ^^^^^ 53 | 54 | ``Unzip(container=None)`` reverses a :ref:`Zip` operation: 55 | 56 | >>> numbers, letters = [0, 1, 2] >> Zip('abc') >> Unzip() 57 | >>> list(numbers) 58 | [0, 1, 2] 59 | >>> list(letters) 60 | ['a', 'b', 'c'] 61 | 62 | Per default ``Unzip`` returns iterators but often the results are required 63 | as lists or other collections (see above). ``Unzip`` allows to provide a 64 | container to collect the results: 65 | 66 | >>> zip([0, 1, 2], 'abc') >> Unzip(list) >> Collect() 67 | [[0, 1, 2], ['a', 'b', 'c']] 68 | 69 | This equivalent to ``Unzip() >> Map(list) >> Collect()`` but shorter. 70 | 71 | 72 | 73 | Interleave 74 | ^^^^^^^^^^ 75 | 76 | ``Interleave`` works like :ref:`Zip` but does not group zipped results in 77 | tuples. Instead an iterator over a flattened sequence of interleaved elements 78 | is returned: 79 | 80 | >>> numbers = [0, 1, 2] 81 | >>> letters = ['a', 'b', 'c'] 82 | >>> numbers >> Interleave(letters) >> Collect() 83 | [0, 'a', 1, 'b', 2, 'c'] 84 | 85 | Also in contrast to ``Zip``, ``Interleave`` does not stop when the shortest 86 | input iterable is depleted. Elements are returned until all inputs are 87 | depleted: 88 | 89 | >>> Range(10) >> Interleave('abc') >> Collect() 90 | [0, 'a', 1, 'b', 2, 'c', 3, 4, 5, 6, 7, 8, 9] 91 | 92 | 93 | Concat 94 | ^^^^^^ 95 | 96 | Apart from zipping or interleaving iterators, they can also be concatenated 97 | using ``Concat``: 98 | 99 | >>> Range(5) >> Concat('abc') >> Collect() 100 | [0, 1, 2, 3, 4, 'a', 'b', 'c'] 101 | 102 | >>> '12' >> Concat('abcd', [3, 4, 5]) >> Collect() 103 | ['1', '2', 'a', 'b', 'c', 'd', 3, 4, 5] 104 | 105 | Note that ``Concat`` is memory efficient and does not materialize any of the 106 | input iterables or the concatenated result in memory; e.g. in contrast to the 107 | following code: 108 | 109 | >>> list(Range(5)) + list('abc') 110 | [0, 1, 2, 3, 4, 'a', 'b', 'c'] 111 | 112 | 113 | Tee 114 | ^^^ 115 | 116 | ``Tee([n=2])`` creates multiple independent iterators from a single iterable. 117 | 118 | >>> numbers1, numbers2 = Range(5) >> Tee(2) 119 | >>> numbers1 >> Collect() 120 | [0, 1, 2, 3, 4] 121 | 122 | >>> numbers2 >> Collect() 123 | [0, 1, 2, 3, 4] 124 | 125 | ``Tee`` is only useful if the returned iterators are advanced largely 126 | synchronously. Otherwise the memory consumption is identical to simply 127 | materializing the input iterable and referencing it, e.g. 128 | 129 | >>> numbers1 = Range(5) >> Collect() 130 | >>> numbers2 = numbers1 131 | 132 | A simple example where ``Tee`` is useful would be to add each number in the 133 | input iterable to its predecessor: 134 | 135 | >>> add = lambda a, b: a + b 136 | >>> numbers1, numbers2 = Range(5) >> Tee(2) 137 | >>> numbers1 >> Drop(1) >> Map(add, numbers2) >> Collect() 138 | [1, 3, 5, 7] 139 | 140 | Iterators, in contrast to streams, do not allow to go back and ``Tee`` provides 141 | a way to overcome this limitation. 142 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/error_messages.rst: -------------------------------------------------------------------------------- 1 | Common error messages 2 | ===================== 3 | 4 | ``'Wrapper' object is not callable`` 5 | ------------------------------------------------------------------ 6 | 7 | Additional brackets when calling nut: 8 | 9 | >>> from nutsflow import * 10 | >>> greater0 = Filter(lambda x : x > 0) 11 | >>> [2, -1, 3] >> greater0() >> Collect() # doctest: +SKIP 12 | ... 13 | TypeError: 'Wrapper' object is not callable 14 | 15 | Should be: 16 | 17 | >>> greater0 = Filter(lambda x : x > 0) 18 | >>> [2, -1, 3] >> greater0 >> Collect() 19 | [2, 3] 20 | 21 | 22 | 23 | ``unsupported operand type(s) for >>`` 24 | ------------------------------------------------------------------ 25 | 26 | Missing brackets when calling nut: 27 | 28 | >>> Greater0 = nut_filter(lambda x: x > 0) 29 | >>> [2, -1, 3] >> Greater0 >> Collect() # doctest: +SKIP 30 | ... 31 | TypeError: unsupported operand type(s) for >>: 'list' and 'type' 32 | 33 | Should be: 34 | 35 | >>> Greater0 = nut_filter(lambda x: x > 0) 36 | >>> [2, -1, 3] >> Greater0() >> Collect() 37 | [2, 3] 38 | 39 | 40 | 41 | 42 | ``name '_' is not defined`` 43 | ------------------------------------------------------------------ 44 | 45 | Typically encountered when using ``_`` without importing it. 46 | Example: 47 | 48 | >>> from nutsflow import * 49 | >>> [2, -1, 3] >> Filter(_ > 0) >> Collect() # doctest: +SKIP 50 | ... 51 | NameError: name '_' is not defined 52 | 53 | Since ``_`` is a common name for place-holder variables the 54 | explicit import of ``_`` is required: 55 | 56 | >>> from nutsflow import * 57 | >>> from nutsflow import _ 58 | >>> [2, -1, 3] >> Filter(_ > 0) >> Collect() 59 | [2, 3] 60 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/exceptions.rst: -------------------------------------------------------------------------------- 1 | .. _exceptions: 2 | 3 | Handling exceptions 4 | ==================== 5 | 6 | Data processing pipelines are typically composed of multiple nuts 7 | and process a stream of data. If one of the nuts within the pipeline 8 | fails the data stream breaks and processing stops. 9 | 10 | Sometimes a more graceful handling of errors is needed and **nuts-flow** 11 | provides the ``Try`` nut for this purpose. 12 | 13 | 14 | Try 15 | --- 16 | 17 | ``Try(func, default)`` can wrap any nut function 18 | (but not other types of nuts such as processors) and handle exceptions 19 | raised by the wrapped nut. 20 | In the following example a nut ``Div`` is defined, which computes 10/x 21 | and is applied to a sequence of numbers: 22 | 23 | >>> from nutsflow import * 24 | >>> Div = nut_function(lambda x : 10/x) 25 | >>> [1, 5, 10] >> Div() >> Collect() 26 | [10, 2, 1] 27 | 28 | As it is this pipeline will break and not collect any results 29 | if any of the input elements is zero: 30 | 31 | >>> [1, 0, 10] >> Div() >> Collect() 32 | Traceback (most recent call last): 33 | ... 34 | ZeroDivisionError: integer division or modulo by zero 35 | 36 | Wrapping the ``Div`` function within a ``Try`` allows the pipeline 37 | to ignore the input element that causes ``Div`` to fail. The problematic 38 | element and the error message are printed to standard out 39 | but the pipeline does not break and collects all other elements: 40 | 41 | >>> [1, 0, 10] >> Try(Div(), 'STDOUT') >> Collect() 42 | ERROR: 0 : integer division or modulo by zero 43 | [10, 1] 44 | 45 | ``Try`` allows defining a default value to be returned if the wrapped 46 | function fails. In this case no error is printed the offending input element 47 | is replaced by the provided default value: 48 | 49 | >>> [1, 0, 10] >> Try(Div(), -1) >> Collect() 50 | [10, -1, 1] 51 | 52 | This kind of exception handling can be performed for nut functions 53 | or plain Python functions (user-defined or built-in). Here an example 54 | where Python's logarithm function is wrapped and zero values are 55 | ignored: 56 | 57 | >>> from math import log 58 | >>> [1, 0, 10] >> Try(log, default='STDOUT') >> Collect() 59 | ERROR: 0 : math domain error 60 | [0.0, 2.302585092994046] 61 | 62 | The ``default`` parameter can also be a function that takes the offending 63 | input element ``x`` and the exception ``e`` as parameters. This allows 64 | to replace offending inputs depending on the input value or the types of 65 | exception raised. 66 | In the following example negative input elements are replaced by 67 | their absolute value and zero is replaced by ``None``. 68 | 69 | >>> if_invalid = lambda x, e: -x if x < 0 else None 70 | >>> [1, 0, -1, 10] >> Try(log, if_invalid) >> Collect() 71 | [0.0, None, 1, 2.302585092994046] 72 | 73 | As a last example, invalid inputs are replaced by the exception they 74 | cause: 75 | 76 | >>> if_invalid = lambda x, e: e 77 | >>> [1, -1, 10] >> Try(log, if_invalid) >> Collect() 78 | [0.0, ValueError('math domain error',), 2.302585092994046] 79 | 80 | The default value for ``Try(x,default)`` is ``default='STDERR'``, 81 | which ignores all elements that raise exceptions and prints error 82 | message to stderr. For ``default='IGNORE'``, offending inputs are 83 | ignored and no error messages are printed. 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/filtering.rst: -------------------------------------------------------------------------------- 1 | .. _filtering: 2 | 3 | Filtering 4 | ========= 5 | 6 | Apart from :ref:`reading ` and :ref:`writing `, 7 | :ref:`filtering ` and :ref:`transforming ` 8 | are the most common operations within data flows. This 9 | sections presents various nuts used to filter, partition 10 | or group data. 11 | 12 | 13 | Filter 14 | ------ 15 | 16 | A common task is to remove elements from a data flow. **nuts-flow** 17 | provides ``Filter`` and ``FilterFalse`` for this purpose. In the 18 | following example all number greater than five are extracted: 19 | 20 | >>> from nutsflow import * 21 | 22 | >>> Range(10) >> Filter(lambda x: x > 5) >> Collect() 23 | [6, 7, 8, 9] 24 | 25 | ``FilterFalse`` is simply the negation of ``Filter`` and extracts 26 | number smaller or equal to five: 27 | 28 | >>> Range(10) >> FilterFalse(lambda x: x > 5) >> Collect() 29 | [0, 1, 2, 3, 4, 5] 30 | 31 | ``Filter`` and ``FilterFalse`` take a predicate (Lambda) function that 32 | must return a boolean value. If the predicate function is very simple 33 | it can be written shorter using :ref:`underscore syntax `: 34 | 35 | >>> from nutsflow import _ 36 | 37 | >>> Range(10) >> Filter(_ > 5) >> Collect() 38 | [6, 7, 8, 9] 39 | 40 | >>> Range(10) >> FilterFalse(_ > 5) >> Collect() 41 | [0, 1, 2, 3, 4, 5] 42 | 43 | 44 | Partition 45 | --------- 46 | 47 | If both 'sides' of a filter, the elements accepted **and** the elements 48 | rejected, are wanted the ``Partition`` nut can be used: 49 | 50 | >>> greater, smaller = Range(10) >> Partition(_ > 5) 51 | >>> greater >> Collect() 52 | [6, 7, 8, 9] 53 | >>> smaller >> Collect() 54 | [0, 1, 2, 3, 4, 5] 55 | 56 | >>> odd, even = Range(10) >> Partition(_ % 2) 57 | >>> odd >> Collect() 58 | [1, 3, 5, 7, 9] 59 | >>> even >> Collect() 60 | [0, 2, 4, 6, 8] 61 | 62 | Note that ``Partition`` returns a tuple containing two iterators. 63 | 64 | 65 | GroupBy 66 | ------- 67 | 68 | Similar, but more powerful than ``Partition`` is ``GroupBy``, which allows 69 | to group the elements of the flow according to a key function: 70 | 71 | >>> Range(10) >> GroupBy(_ > 5) >> Collect() 72 | [(False, [0, 1, 2, 3, 4, 5]), (True, [6, 7, 8, 9])] 73 | 74 | ``GroupBy`` returns an iterator over the groups, where each group is 75 | a tuple with the result of the key function first and the elements of 76 | the group second. If the result of the key function is not required 77 | the *nokey* flag can be set to ``True``: 78 | 79 | >>> Range(10) >> GroupBy(_ > 5, nokey=True) >> Collect() 80 | [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9]] 81 | 82 | In contrast to ``Partition``, ``GroupBy`` is not limited to a boolean 83 | key function. For instance, to group by the remainder of the division 84 | by 3 simply call 85 | 86 | >>> Range(10) >> GroupBy(_ % 3) >> Collect() 87 | [(0, [0, 3, 6, 9]), (1, [1, 4, 7]), (2, [2, 5, 8])] 88 | 89 | ``GroupBy`` loads all data in memory and should be avoided for large data sets. 90 | If the data is sorted ``GroupBySorted`` can be used instead. 91 | 92 | 93 | TakeWhile and DropWhile 94 | ----------------------- 95 | 96 | Occasionally, it is necessary to run a data flow until a certain 97 | condition is met. ``TakeWhile(func)`` takes elements from the 98 | iterable as long as the predicate function is true. 99 | In the following example all number are collected until 100 | the **first** negative number is encountered: 101 | 102 | >>> [2, 1, -1, 3, 4, -1] >> TakeWhile(_ > 0) >> Collect() 103 | [2, 1] 104 | 105 | Similarily, ``DropWhile(func)`` skips all elements while the predicate function 106 | is true and returns the remainder of the iterable: 107 | 108 | >>> [2, 1, -1, 3, 4, -1] >> DropWhile(_ > 0) >> Collect() 109 | [-1, 3, 4, -1] 110 | 111 | 112 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/introduction.rst: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | This tutorial introduces the basic concepts of **nuts-flow** and 5 | provides examples of its usage. 6 | 7 | .. toctree:: 8 | :maxdepth: 2 9 | 10 | prerequisites 11 | nuts_basics 12 | debugging 13 | filtering 14 | transforming 15 | rearranging 16 | divide_conquer 17 | sources 18 | sinks 19 | exceptions 20 | custom_nuts 21 | underscore 22 | performance 23 | recipes 24 | practice_problems 25 | error_messages 26 | 27 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/nuts_basics.rst: -------------------------------------------------------------------------------- 1 | Nuts basics 2 | =========== 3 | 4 | Flows 5 | ----- 6 | 7 | **nuts-flow** data pipelines are composed of **nuts** that 8 | are chained together with the ``>>`` operator. For instance, in the 9 | following data flow, ``Range`` generates number from 0 to 4, the ``Square`` 10 | nut squares those numbers and the ``Collect`` nut collects the results 11 | in a list: 12 | 13 | >>> from nutsflow import Range, Square, Collect 14 | >>> Range(5) >> Square() >> Collect() 15 | [0, 1, 4, 9, 16] 16 | 17 | The data elements of a flow are typically processed element by element, 18 | avoiding loading large amounts of data into memory or processing data 19 | if not needed. For instance, 20 | 21 | >>> from nutsflow import * 22 | >>> Range(10000000) >> Square() >> Take(3) >> Collect() 23 | [0, 1, 4] 24 | 25 | works just fine and does not store 10 million integers in memory. 26 | 27 | 28 | Sources and Sinks 29 | ----------------- 30 | 31 | Every data flow starts with a :ref:`source `, which can be any 32 | *iterable* such as iterators, generators, iterable nuts or 33 | plain Python data structures (string, lists, sets, dictionaries, ...): 34 | 35 | >>> [0, 1, 2, 3, 4] >> Square() >> Collect() 36 | [0, 1, 4, 9, 16] 37 | 38 | >>> "Macadamia" >> Take(4) >> Collect() 39 | ['M', 'a', 'c', 'a'] 40 | 41 | >>> range(5) >> Collect() 42 | [0, 1, 3, 4, 4] 43 | 44 | In addition to the usual Python data sources, **nuts-flow** has its own 45 | sources, e.g. 46 | 47 | >>> Range(5) >> Collect() # Range() == range() 48 | [0, 1, 3, 4, 4] 49 | 50 | >>> Repeat(1) >> Take(3) >> Collect() 51 | [1, 1, 1] 52 | 53 | 54 | Apart from a source, every data flow needs a *sink* at the end that 55 | *pulls* the data. Without a sink the data flow does not process any data 56 | (most nuts are lazy). For example 57 | 58 | >>> Range(5) >> Square() 59 | 60 | 61 | simply returns an iterator object but does neither create any ranged numbers 62 | nor computes the square. :ref:`Sinks ` take iterables as input and return a 63 | result of any type or even nothing 64 | 65 | >>> Range(5) >> Collect() 66 | [0, 1, 2, 3, 4] 67 | 68 | >>> Range(5) >> Sum() 69 | 10 70 | 71 | >>> Range(5) >> Consume() # returns nothing 72 | 73 | Here the sinks are ``Collect()``, ``Sum()`` and ``Consume()``. 74 | 75 | 76 | Functions and Processors 77 | ------------------------ 78 | 79 | Between *sources* and *sinks* a data flow typically contains a sequence of 80 | *nut functions* or *nut processors*. Nut functions read from an iterator 81 | and for each processed element return a new element. ``Square`` is such 82 | a nut function. 83 | 84 | *Nut processors*, on the other hand, can modulate the data flow and might return 85 | more or less elements than read from the input. For instance, ``Pick(n)`` 86 | is a processor that returns only every *n-ths* element from the input iterable 87 | 88 | >>> from nutsflow import Range, Pick, Collect 89 | >>> Range(10) >> Pick(3) >> Collect() 90 | [0, 3, 6, 9] 91 | 92 | Note that nut functions can be used as *normal* functions as well but 93 | must be called with additional brackets 94 | 95 | >>> Square()(3) 96 | 9 97 | 98 | 99 | Iterator depletion 100 | ------------------ 101 | 102 | It is important to remember that *nuts* usually return iterators 103 | that will deplete when used multiple times. See the following example, 104 | where ``Take(2)`` always takes the first *2* elements from its input: 105 | 106 | >>> from nutsflow import Range, Take, Collect 107 | >>> numbers = Range(5) 108 | >>> numbers >> Take(2) >> Collect() 109 | [0, 1] 110 | >>> numbers >> Take(2) >> Collect() 111 | [2, 3] 112 | >>> numbers >> Take(2) >> Collect() 113 | [4] 114 | >>> numbers >> Take(2) >> Collect() 115 | [] 116 | 117 | 118 | New nuts 119 | -------- 120 | 121 | **nuts-flow** can easily be extended with new nuts 122 | (for details see :ref:`Custom nuts` ) 123 | 124 | >>> Tripple = nut_function(lambda x: x * 3) 125 | >>> Range(5) >> Tripple() >> Collect() 126 | [0, 3, 6, 9, 12] 127 | 128 | or combined with plain Python functions as any 129 | other iterator: 130 | 131 | >>> def Squares(n): return Range(n) >> Square() 132 | >>> Squares(3) >> Collect() 133 | [0, 1, 4] 134 | 135 | >>> sum(Range(5) >> Square()) 136 | 30 137 | 138 | When implementing new nuts, or Python functions/classes that 139 | behave like nuts, the name of the nut should start with an uppercase letter. 140 | This makes it easy to distiguish standard functions from nuts: 141 | 142 | >>> from nutsflow import Range, Sum 143 | >>> Range(5) >> Sum() 144 | 10 145 | >>> sum(Range(5)) 146 | 10 147 | >>> range(5) >> Sum() 148 | 10 149 | 150 | 151 | Line breaks 152 | ----------- 153 | 154 | Sometimes data flows get longer than the 79 character limit 155 | that the Python style guide 156 | `PEP 8 `_ 157 | recommends. In such a case flows can be wrapped in brackets 158 | to allow for line breaks: 159 | 160 | >>> (Range(10) >> Pick(2) >> Square() >> Square() >> 161 | ... Take(3) >> Collect()) 162 | [0, 16, 256] 163 | 164 | Alternatively, a flow can be broken into shorter pieces: 165 | 166 | >>> squared = Range(10) >> Pick(2) >> Square() >> Square() 167 | >>> squared >> Take(3) >> Collect() 168 | [0, 16, 256] 169 | 170 | 171 | Summary 172 | ------- 173 | 174 | **nuts-flows** are composed of *nuts* that are connected to flows 175 | via the ``>>`` operator. 176 | A data flow starts with a *source*, ends with a *sink* and 177 | typically contains *nut processors* or *nut functions* inbetween: 178 | 179 | .. code:: 180 | 181 | source >> processor|function >> ... >> sink 182 | 183 | *nut sources* return iterators or iterables when called. *nut sinks* take iterables 184 | as input and return results of any type. 185 | *nut functions* transform the elements of a flow but do not change the number (or order) 186 | of the elements, while *nut processors* can modify the flow in any way. 187 | 188 | 189 | 190 | 191 | 192 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/performance.rst: -------------------------------------------------------------------------------- 1 | Performance 2 | =========== 3 | 4 | **nuts-flow** does not support concurrency in general but provides 5 | nuts that can improve performance by caching or parallelization. 6 | 7 | 8 | MapPar 9 | ------ 10 | 11 | Applying a function concurrently to the elements of a flow can be achieved 12 | with the ``MapPar`` nut. The following toy example converts numbers to their 13 | *absolute values* by applying the ``abs`` function in parallel 14 | 15 | >>> from nutsflow import * 16 | 17 | >>> [-1, -2, -3] >> MapPar(abs) >> Collect() 18 | [1, 2, 3] 19 | 20 | Note that the order of the elements in the iterable is preserved. 21 | Currently, ``MapPar`` is of limited use, since 1) the function applied 22 | must be `pickable `_ 23 | and 2) ``MapPar`` creates parallel processes, which are computationally 24 | expensive to start. 25 | 26 | 27 | Cache 28 | ----- 29 | 30 | **nuts-flow** supports the *caching* of results to disk. Here an 31 | example in pseudo code 32 | 33 | .. code:: python 34 | 35 | with Cache() as cache: 36 | for i in range: 37 | data >> expensive_op >> cache >> ... >> Collect() 38 | 39 | Note that *caching* is only useful if 1) the elements to cache are 40 | time-consuming to compute, 2) can be loaded faster than recreated, 41 | and 3) the same data flow is executed multiple times, 42 | where in the first run the cache is filled and then is used in all 43 | subsequent runs. 44 | 45 | A common use case is *machine learning for vision*, where images 46 | are preprocessed and a classifier is trained by repeatedly executing 47 | a data flow: 48 | 49 | .. code:: python 50 | 51 | with Cache() as cache: 52 | for epoch in xrange(100): 53 | images >> preprocess >> cache >> network.train() >> Consume() 54 | 55 | Cached elements are pickled to a temporary folder which is deleted 56 | when the ``with`` block is exited. The cache can be cleared as follows: 57 | 58 | .. code:: python 59 | 60 | with Cache() as cache: 61 | ... 62 | cache.clear() 63 | 64 | 65 | Prefetch 66 | -------- 67 | 68 | *Prefetching* is another common method employed in (GPU-based) machine learning 69 | to speed up a data flow. Here data is pre-fetched (and pre-processed) 70 | on a separate CPU thread while the GPU is performing machine learning 71 | on another chunk of data: 72 | 73 | .. code:: python 74 | 75 | images >> preprocess >> Prefetch() >> network.train() >> Consume() 76 | 77 | 78 | The following two examples demonstrate the difference between processing 79 | a data flow with and without pre-fetching. First a flow *without pre-fetching* 80 | that takes one number and prints it 81 | 82 | .. code:: python 83 | 84 | >>> Range(5) >> Print() >> Take(1) >> Consume() 85 | 0 86 | 87 | now the same flow but *with pre-fetching* 88 | 89 | .. code:: python 90 | 91 | >>> Range(5) >> Print() >> Prefetch() >> Take(1) >> Consume() 92 | 0 93 | 1 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/practice_problems.rst: -------------------------------------------------------------------------------- 1 | Practice problems 2 | ================= 3 | 4 | `Project Euler `_ and 5 | `99 Scala Problems `_ or 6 | `99 Python Problems `_ 7 | contain collections of small coding problems suitable 8 | for functional programming - many of them are also good exercises for **nuts-flow**. 9 | 10 | 11 | 99 Problems 12 | ----------- 13 | 14 | A few `99 Scala Problems `_ with solutions 15 | using **nuts-flow**: 16 | 17 | >>> from nutsflow import * 18 | >>> from nutsflow import _ 19 | 20 | **P01** : Find the last element of a list. 21 | 22 | >>> [1, 1, 2, 3, 5, 8] >> Tail(1) 23 | [8] 24 | 25 | 26 | **P02** : Find the last but one element of a list. 27 | 28 | >>> [1, 1, 2, 3, 5, 8] >> Tail(2) >> Head(1) 29 | [5] 30 | 31 | 32 | **P03** : Find the Kth element of a list. 33 | 34 | >>> [1, 1, 2, 3, 5, 8] >> Nth(2) 35 | 2 36 | 37 | >>> [1, 1, 2, 3, 5, 8] >> Drop(2) >> Head(1) 38 | [2] 39 | 40 | 41 | **P04** : Find the number of elements of a list. 42 | 43 | >>> [1, 1, 2, 3, 5, 8] >> Count() 44 | 6 45 | 46 | 47 | **P14** : Duplicate the elements of a list. 48 | 49 | >>> Range(5) >> Clone(2) >> Collect() 50 | [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] 51 | 52 | 53 | **P15** : Duplicate the elements of a list a given number of times. 54 | 55 | >>> Range(5) >> Clone(n) >> Collect() 56 | [0, 0, 1, 1, 2, 2, 3, 3, 4, 4] 57 | 58 | 59 | **P16** : Drop every Nth element from a list. 60 | 61 | >>> n==3 62 | >>> lst = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k'] 63 | >>> lst >> Chunk(n, list) >> Map(Take(n-1)) >> Flatten() >> Collect() 64 | ['a', 'b', 'd', 'e', 'g', 'h', 'j', 'k'] 65 | 66 | 67 | Euler 68 | ----- 69 | 70 | Some `Project Euler `_ with solutions 71 | using **nuts-flow**. 72 | 73 | **P1**: Multiples of 3 and 5 74 | 75 | If we list all the natural numbers below 10 that are multiples of 76 | 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 77 | 78 | Find the sum of all the multiples of 3 or 5 below 1000. 79 | 80 | >>> IsMultiple = nut_filter(lambda x: x % 3 == 0 or x % 5 == 0) 81 | >>> Range(1, 1000) >> IsMultiple() >> Sum() 82 | 233168 83 | 84 | 85 | **P2**: Even Fibonacci numbers 86 | 87 | Each new term in the Fibonacci sequence is generated by adding the 88 | previous two terms. By starting with 1 and 2, the first 10 terms will be: 89 | 90 | 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 91 | 92 | By considering the terms in the Fibonacci sequence whose values do not 93 | exceed four million, find the sum of the even-valued terms. 94 | 95 | >>> @nut_source 96 | ... def Fib(): 97 | ... a, b = 1, 1 98 | ... while True: 99 | ... a, b = b, a + b 100 | ... yield a 101 | >>> IsEven = nut_filter(lambda x: x%2 == 0) 102 | >>> Fib() >> TakeWhile(_ < 4000000) >> IsEven() >> Sum() 103 | 4613732 104 | 105 | 106 | **P4**: Largest palindrome product 107 | 108 | A palindromic number reads the same both ways. The largest palindrome 109 | made from the product of two 2-digit numbers is 9009 = 91 × 99. 110 | 111 | Find the largest palindrome made from the product of two 3-digit numbers. 112 | 113 | >>> n1, n2 = 100, 1000 114 | >>> IsPalindrom = nut_filter(lambda p: p == p[::-1]) 115 | >>> BuildString = nut_function(lambda (a, b): str(a * b)) 116 | >>> product = Product(Range(n1, n2), repeat=2) 117 | >>> product >> BuildString() >> IsPalindrom() >> Max(int) 118 | '906609' 119 | 120 | 121 | **P6**: Sum square difference 122 | 123 | The sum of the squares of the first ten natural numbers is, 124 | 125 | 1^2 + 2^2 + ... + 10^2 = 385 126 | 127 | The square of the sum of the first ten natural numbers is, 128 | 129 | (1 + 2 + ... + 10)^2 = 55^2 = 3025 130 | 131 | Hence the difference between the sum of the squares of the first ten natural 132 | numbers and the square of the sum is 3025 − 385 = 2640. 133 | 134 | Find the difference between the sum of the squares of the first 135 | one hundred natural numbers and the square of the sum. 136 | 137 | >>> sum_sqr = Range(1, 11) >> Square() >> Sum() 138 | >>> sqr_sum = (Range(1, 11) >> Sum())**2 139 | >>> sqr_sum - sum_sqr 140 | 2640 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/prerequisites.rst: -------------------------------------------------------------------------------- 1 | Prerequisites 2 | ============= 3 | 4 | **nuts-flow** is based on *iterators* and makes frequent use of *lambda* functions. 5 | If you are already familiar with these concepts go ahead and skip this section. 6 | 7 | 8 | Lambda functions 9 | ---------------- 10 | 11 | Commonly functions are defined via the ``def`` keyword and a function name, 12 | e.g.: 13 | 14 | .. code:: pythonun 15 | 16 | def add(a, b): 17 | return a + b 18 | 19 | *Lambda* functions or so called *anonymous* functions are an alternative method 20 | to define very short functions (without a name) that are typically used only once. 21 | For instance, the ``add`` function above can be written as follows 22 | 23 | .. code:: python 24 | 25 | lambda a, b: a + b 26 | 27 | Since functions are first class citizens in Python they can be assigned 28 | to variables and called by name as well 29 | 30 | >>> add = lambda a, b: a + b 31 | >>> add(1, 2) 32 | 3 33 | 34 | The most common use case, however, is as a anonymous function for other 35 | functions such as ``sorted``, ``max`` or ``filter``. For example, 36 | to extract numbers greater than 2 from a list we could write 37 | 38 | >>> numbers = [1, 2, 3, 4] 39 | >>> filter(lambda x: x > 2, numbers) 40 | [3, 4] 41 | 42 | **nuts-flow** has a special notation for even shorter function definitions, 43 | following the *underscore notation* from `Scala `_. 44 | Using the underscore, the above filtering can be expressed 45 | even more succinctly as 46 | 47 | >>> from nutsflow import _ 48 | >>> filter(_ > 2, numbers) 49 | [3, 4] 50 | 51 | The underscore essentially serves as a place holder for the numbers of the list. 52 | Note that the underscore notation in **nuts-flow** is very limited and only 53 | simple expression (e.g. ``_ + 1``, ``_ <= 3``, ...) are supported. More details 54 | can be found in Section :ref:`Underscore syntax` . 55 | 56 | 57 | Iterators 58 | --------- 59 | 60 | Iterators are needed to process data that doesn't fit in memory, e.g. lines of a 61 | very large file, permutations of a string, ..., or even infinitely large data such 62 | as counters or random numbers. 63 | 64 | A Python `Iterator `_ is any object that 65 | provides a ``next`` method, which returns elements when called and raises a 66 | ``StopIteration`` exception when depleted. Here an iterator that returns 67 | even numbers up to a given maximum 68 | 69 | >>> class Even(): 70 | ... def __init__(self, maximum): 71 | ... self.counter = 0 72 | ... self.maximum = maximum 73 | ... 74 | ... def __iter__(self): 75 | ... return self 76 | ... 77 | ... def __next__(self): 78 | ... self.counter += 2 79 | ... if self.counter > self.maximum: 80 | ... raise StopIteration 81 | ... return self.counter 82 | ... 83 | 84 | The ``__iter__`` method make the iterator *iterable* and enables 85 | its usage in ``for`` loops, list comprehensions or functions 86 | that take iterables 87 | 88 | >>> even = Even(6) 89 | >>> for e in even: 90 | ... print e 91 | 2 92 | 4 93 | 6 94 | 95 | There are three important properties of iterators to keep in mind. 96 | Firstly, an iterator is lazy. It doesn't produce anything until asked. 97 | There needs to be a consumer. 98 | For instance, ``even = Even(100000)`` creates the iterator but does not 99 | create any numbers. 100 | 101 | Secondly, an iterator has state and subsequent calls will advance its state. 102 | Thirdly, once an iterator is depleted it needs to be recreated to be used 103 | again 104 | 105 | >>> even = Even(10) 106 | 107 | >>> [e for e in even] 108 | [2, 4, 6, 8, 10] 109 | 110 | >>> [e for e in even] 111 | [] 112 | 113 | >>> even = Even(10) 114 | >>> [e for e in even] 115 | [2, 4, 6, 8, 10] 116 | 117 | 118 | Iterators can be chained to build complex data processing pipelines 119 | that consume very little memory. Python's 120 | `itertools `_ 121 | library provides many functions for this purpose. The following toy 122 | example uses itertools to extract the first three integers greater 123 | than five in the interval [0..8[ 124 | 125 | >>> from itertools import islice, ifilter 126 | >>> list(islice(ifilter(lambda x: x > 5, xrange(8)), 3)) 127 | [6, 7] 128 | 129 | 130 | **nuts-flow** is largely based on Python’s itertools but aims to 131 | make the data flow more explict and readable by introducing 132 | the ``>>`` operator for chaining 133 | 134 | >>> from nutsflow import Range, Filter, Take, Collect, _ 135 | >>> Range(8) >> Filter(_ > 5) >> Take(3) >> Collect() 136 | [6, 7] 137 | 138 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/rearranging.rst: -------------------------------------------------------------------------------- 1 | .. _rearranging: 2 | 3 | Rearranging data 4 | ================ 5 | 6 | Another common need is to rearrange or restructure data. The following nuts 7 | can help with that. 8 | 9 | >>> from nutsflow import * 10 | 11 | 12 | Slice 13 | ----- 14 | 15 | ``Slice([start,] stop, [stride])`` takes a slice of the data. Similar to Python's 16 | `slicing `_ 17 | operation it extracts a section of the data. If no ``start`` or ``stride`` 18 | are provided, ``Slice`` extracts the first ``stop`` elements 19 | 20 | >>> [1, 2, 3, 4] >> Slice(2) >> Collect() 21 | [1, 2] 22 | 23 | 24 | If ``start`` and ``stop`` are provided the elements from ``start`` index 25 | to ``stop`` index (excluded) are extracted 26 | 27 | >>> [1, 2, 3, 4] >> Slice(1, 3) >> Collect() 28 | [2, 3] 29 | 30 | 31 | Finally the third parameter allows to specify a ``stride``. In this example 32 | every second element in the slice starting at index 0 and ending at index 4 33 | (exclusive) is extracted 34 | 35 | >>> [1, 2, 3, 4] >> Slice(0, 4, 2) >> Collect() 36 | [1, 3] 37 | 38 | 39 | Chunk 40 | ----- 41 | 42 | ``Chunk(n)`` is a nut to group data in chunks of size ``n``: 43 | 44 | >>> Range(5) >> Chunk(2) >> Map(list) >> Collect() 45 | [[0, 1], [2, 3], [4]] 46 | 47 | 48 | Note that each chunk is an iterator over the elements in the chunk, 49 | which is why ``Map(list)`` is required to convert the chunks to printable lists. 50 | A more interesting example might be the sum of the elements within each chunk 51 | 52 | >>> Range(5) >> Chunk(2) >> Map(sum) >> Collect() 53 | [1, 5, 4] 54 | 55 | 56 | Window 57 | ------ 58 | 59 | ``Window(n)`` provides a sliding window of size ``n`` over the elements 60 | in the input data. For example: 61 | 62 | >>> [1, 2, 3, 4, 5] >> Window(3) >> Collect() 63 | [(1, 2, 3), (2, 3, 4), (3, 4, 5)] 64 | 65 | This works for strings as well. We use ``Join()`` to convert the 66 | individual characters in the generated windows to strings: 67 | 68 | >>> 'abcdefg' >> Window(4) >> Map(Join()) >> Collect() 69 | ['abcd', 'bcde', 'cdef', 'defg'] 70 | 71 | 72 | Cycle 73 | ----- 74 | 75 | Sometimes it is necessary to repeatedly process an iterable. ``Cycle`` takes 76 | all elements from its input iterable, stores them in memory and returns an 77 | iterator that cycles through the elements indefinitely. Here an example that 78 | cycles through 1, 2, 3 and takes the first 10 elements 79 | 80 | >>> [1, 2, 3] >> Cycle() >> Take(10) >> Collect() 81 | [1, 2, 3, 1, 2, 3, 1, 2, 3, 1] 82 | 83 | Note that ``Cycle`` will consume large amounts of memory if the input iterable 84 | is large. 85 | 86 | 87 | Permutate 88 | --------- 89 | 90 | ``Permutate([,r])`` returns successive ``r`` length permutations of 91 | the elements in the input iterable. 92 | 93 | >>> [1, 2, 3] >> Permutate(2) >> Collect() 94 | [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)] 95 | 96 | Maybe a more interesting example: What is the number of distinctive 97 | palindroms for a given string: 98 | 99 | >>> IsPalindrom = nut_filter(lambda x: x == x[::-1]) 100 | >>> 'devoved' >> Permutate() >> IsPalindrom() >> Collect(set) >> Count() 101 | 6 102 | 103 | If no permutation size ``r`` is specified then all possible full-length 104 | permutations are generated (r!) and the computation will not finish in 105 | any reasonable time for non-small values of ``r`` ! 106 | 107 | 108 | Combine 109 | ------- 110 | 111 | ``Combine(r)`` return ``r`` length subsequences of the elements from the 112 | input iterable. 113 | 114 | >>> [1, 2, 3] >> Combine(2) >> Collect() 115 | [(1, 2), (1, 3), (2, 3)] 116 | 117 | Note that ``Combine(r)`` returns a subset of ``Permutate(r)`` with permutations 118 | where the order of the elements (as given in the input iterable) is preserved. 119 | 120 | 121 | 122 | Dedupe 123 | ------ 124 | 125 | A very common task is to remove all duplicates from a data set. 126 | ``Dedupe([key])`` performs this task and also takes a key function 127 | that defines which elements are treated as equal. 128 | 129 | ``Dedupe()`` preserves the order of the element in the input. See the 130 | following example 131 | 132 | >>> [2, 3, 1, 1, 2, 4] >> Dedupe() >> Collect() 133 | [2, 3, 1, 4] 134 | 135 | More complex data often require a more sophisticated definition of equality 136 | and the key functions provides this 137 | 138 | >>> data = [(1, 'a'), (2, 'a'), (3, 'b')] 139 | >>> data >> Dedupe(lambda (x, y): y) >> Collect() 140 | [(1, 'a'), (3, 'b')] 141 | 142 | 143 | ``Dedupe()`` memorizes all unique elements of the input iterable in a set 144 | and can potentially consume large amounts of memory! 145 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/recipes.rst: -------------------------------------------------------------------------------- 1 | .. _recipes: 2 | 3 | Recipes 4 | ======= 5 | 6 | A collection of common problems with solutions. Make sure **nutsflow** has been imported. 7 | 8 | 9 | Write CSV file with column names 10 | -------------------------------- 11 | 12 | .. code:: Python 13 | 14 | with WriteCSV(filepath) as writer: 15 | [('Col1', 'Col2')] >> writer 16 | [(1, 2), (3, 4)] >> writer 17 | 18 | 19 | Load a mapping file 20 | ------------------- 21 | 22 | From a file with two columns create a directory that maps 23 | values in one column to the other. For instance, the following 24 | file contains Arabic numbers and their Roman counterparts. 25 | 26 | .. code:: 27 | 28 | arab,roman 29 | 1,I 30 | 2,II 31 | 3,III 32 | 33 | We create a Python dictionary that maps Arabic to Roman numbers by 34 | loading the CSV file, dropping the header line, converting Arabic numbers 35 | (that are loaded as strings) to integers and collecting the results in 36 | a dictionary 37 | 38 | >>> from nutsflow import * 39 | >>> fpath = 'tests/data/arab2num.csv' 40 | >>> arab2roman = ReadCSV(fpath) >> Drop(1) >> MapCol(0, int) >> Collect(dict) 41 | >>> arab2roman[2] 42 | 'II' 43 | 44 | 45 | For the reversed mapping (Roman to Arabic), we just flip the columns via ``GetCols`` 46 | and use ``skipheader=1`` to skip the header line 47 | 48 | >>> roman2arab = (ReadCSV(fpath, skipheader=1) >> MapCol(0, int) >> 49 | ... GetCols(1, 0) >> Collect(dict)) 50 | >>> roman2arab['III'] 51 | 3 52 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/sources.rst: -------------------------------------------------------------------------------- 1 | .. _sources: 2 | 3 | Reading from Sources 4 | ==================== 5 | 6 | All data flows start with a *source*. Sources are Python iterables and a small 7 | set of specific nuts. As a general rule, sources must appear on the left side 8 | of the ``>>`` operator and can never appear on the right side. 9 | 10 | 11 | Iterables 12 | --------- 13 | 14 | Some examples of Python iterables and iterators that can be used as sources: 15 | 16 | >>> from nutsflow import * 17 | 18 | >>> range(5) >> Collect() 19 | [0, 1, 2, 3, 4] 20 | 21 | >>> ['a', 'ab', 'abc'] >> Map(len) >> Collect() 22 | [1, 2, 3] 23 | 24 | >>> 'text' >> Map(lambda c: c.upper()) >> Join() 25 | 'TEXT' 26 | 27 | >>> {1:'one', 2:'two'} >> Collect() 28 | [1, 2] 29 | 30 | >>> {1:'one', 2:'two'}.items() >> Collect() 31 | [(1, 'one'), (2, 'two')] 32 | 33 | .. code:: 34 | 35 | with open(filepath) as lines: 36 | lines >> Filter(lambda l: l.startswith('ERR')) >> Print() >> Consume() 37 | 38 | 39 | Source nuts 40 | ----------- 41 | 42 | **nuts-flow** has a few special source nuts. 43 | 44 | Range 45 | ^^^^^ 46 | 47 | ``Range(start [,end [, step]])`` essential operates the same as ``range`` 48 | but depletes. The following examples demonstrates the difference: 49 | 50 | >>> numbers = Range(5) 51 | >>> numbers >> Head(3) 52 | [0, 1, 2] 53 | >>> numbers >> Head(3) 54 | [3, 4] 55 | >>> numbers >> Head(3) 56 | [] 57 | 58 | Subsequent calls deplete the numbers iterator created with ``Range``, while 59 | ``range`` returns a new iterator every time when called and does not deplete: 60 | 61 | >>> numbers = range(5) 62 | >>> numbers >> Head(3) 63 | [0, 1, 2] 64 | >>> numbers >> Head(3) 65 | [0, 1, 2] 66 | 67 | 68 | Enumerate 69 | ^^^^^^^^^ 70 | 71 | ``Enumerate(start=0 [, step])`` returns an iterator over increasing integer 72 | numbers. In contrast to :ref:`Range` it does not have an upper limit and 73 | iterates indefinitely. 74 | 75 | >>> Enumerate(1) >> Zip('abc') >> Collect() 76 | [(1, 'a'), (2, 'b'), (3, 'c')] 77 | 78 | Often ``Enumerate`` is used to add line numbers to the lines of a file: 79 | 80 | .. code:: 81 | 82 | # Collect line numbers of empty lines 83 | with open(filepath) as lines: 84 | (Enumerate() >> Zip(lines) >> Filter(lambda (i,l): not l) >> 85 | Get(0) >> Collect()) 86 | 87 | 88 | Product 89 | ^^^^^^^ 90 | 91 | ``Product`` is the functional equivalent of a nested loop. It generates the 92 | cartesian product of the input iterables. For instance, the following example 93 | returns the coordinates of a 2x3 grid: 94 | 95 | >>> Product(Range(2), Range(3)) >> Collect() 96 | [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)] 97 | 98 | Each element of each input iterable is combined with each element of the 99 | other input iterables. 100 | 101 | 102 | Repeat 103 | ^^^^^^ 104 | 105 | The ``Repeat(value [, times]))`` nut returns the specified value the given 106 | number of times or indefinitely if not specified: 107 | 108 | >>> Repeat('a', 3) >> Collect() 109 | ['a', 'a', 'a'] 110 | 111 | >>> Repeat(1) >> Take(4) >> Collect() 112 | [1, 1, 1, 1] 113 | 114 | 115 | ReadNamedCSV 116 | ^^^^^^^^^^^^ 117 | 118 | **nuts-flow** supports reading from Comma Separated Format (CSV) files with 119 | header names via the ``ReadNamedCSV(filepath, colnames, fmtfunc, rowname, **kwargs)`` nut. 120 | Given the correct delimiter also files in Tab Separated Format (TSV) or other column 121 | formats can be read. Given a CSV file with the following content 122 | 123 | .. code:: 124 | 125 | A,B,C 126 | 1,2,3 127 | 4,5,6 128 | 129 | the code below reads the rows as named tuples, and converts 130 | the elements of the row into integers (fmtfunc=int): 131 | 132 | >>> filepath = 'tests/data/data.csv' 133 | >>> with ReadNamedCSV(filepath, fmtfunc=int) as reader: 134 | ... reader >> Print() >> Consume() 135 | Row(A=1, B=2, C=3) 136 | Row(A=4, B=5, C=6) 137 | 138 | Different convert functions for columns are suppported: 139 | 140 | 141 | >>> fmtfuncs = (int, str, float) 142 | >>> with ReadNamedCSV(filepath, fmtfunc=fmtfuncs) as reader: 143 | ... reader >> Print() >> Consume() 144 | Row(A=1, B='2', C=3.0) 145 | Row(A=4, B='5', C=6.0) 146 | 147 | ``ReadNamedCSV`` allows to read specific columns in a given/different order. 148 | Here we read columns 'B' and 'C' only in swapped order: 149 | 150 | 151 | >>> with ReadCSV(filepath, ('C', 'B')) as reader: 152 | ... reader >> Print() >> Consume() 153 | Row(C='3', B='2') 154 | Row(C='6', B='5') 155 | 156 | Finally, if 'Row' is not a good tuple name, it can be changed: 157 | 158 | 159 | >>> with ReadNamedCSV(filepath, rowname='Sample') as reader: 160 | ... reader >> Print() >> Consume() 161 | Sample(A='1', B='2', C='3') 162 | Sample(A='4', B='5', C='6') 163 | 164 | 165 | ReadCSV 166 | ^^^^^^^ 167 | 168 | ``ReadCSV()`` is very similar to ``ReadNamedCSV`` but can read CSV files 169 | without header information and returns (unnamed) tuples. 170 | 171 | >>> filepath = 'tests/data/data.csv' 172 | >>> with ReadCSV(filepath, skipheader=1, fmtfunc=int) as reader: 173 | ... reader >> Print() >> Consume() 174 | ... 175 | (1, 2, 3) 176 | (4, 5, 6) 177 | 178 | -------------------------------------------------------------------------------- /sphinx/source/tutorial/underscore.rst: -------------------------------------------------------------------------------- 1 | .. _underscore: 2 | 3 | Underscore syntax 4 | ================= 5 | 6 | :ref:`Filter`, :ref:`Map` and other nuts that take a function as argument 7 | often use only very simple expressions. For instance, the following code 8 | extracts all number greater than five within range zero to nine: 9 | 10 | >>> from nutsflow import * 11 | 12 | >>> Range(10) >> Filter(lambda x: x > 5) >> Collect() 13 | [6, 7, 8, 9] 14 | 15 | For such simple expressions **nuts-flow** provides a special 16 | *underscore notation*, borrowed from `Scala `_, 17 | which results in shorter, more readable code 18 | 19 | >>> from nutsflow import _ 20 | 21 | >>> Range(10) >> Filter(_ > 5) >> Collect() 22 | [6, 7, 8, 9] 23 | 24 | This is equivalent to 25 | 26 | >>> Range(10) >> Filter(lambda _: _ > 5) >> Collect() 27 | [6, 7, 8, 9] 28 | 29 | but eliminates the need for the ``lambda`` keyword and its arguments. 30 | Note that the ``_`` must be imported explicitly, since it is also commonly 31 | used in Python as a placeholder for unused variables. 32 | 33 | In contrast to ``lambda`` functions or Scala's underscore the, 34 | underscore notation in **nuts-flow** is very limited and 35 | only supports expressions with arity one, e.g. 36 | ``_ + 1``, ``_ <= 3``, ``_ == 5``, ``_[0]``, ... 37 | More complex or nested expressions such as ``_ + _``, ``_ > 5 and _ < 10`` 38 | or ``len(_)`` are currently not permitted. 39 | 40 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/arab2num.csv: -------------------------------------------------------------------------------- 1 | arab,roman 2 | 1,I 3 | 2,II 4 | 3,III -------------------------------------------------------------------------------- /tests/data/config.yaml: -------------------------------------------------------------------------------- 1 | filepath : c:/Maet 2 | imagesize : [100, 200] -------------------------------------------------------------------------------- /tests/data/configuration.json: -------------------------------------------------------------------------------- 1 | {"number": 13, "name": "Stefan"} -------------------------------------------------------------------------------- /tests/data/configuration.yaml: -------------------------------------------------------------------------------- 1 | name: Stefan 2 | number: 13 3 | -------------------------------------------------------------------------------- /tests/data/data.csv: -------------------------------------------------------------------------------- 1 | A,B,C 2 | 1,2,3 3 | 4,5,6 4 | -------------------------------------------------------------------------------- /tests/data/data.tsv: -------------------------------------------------------------------------------- 1 | A B C 2 | 1 2 3 3 | 4 5 6 4 | -------------------------------------------------------------------------------- /tests/data/numbers.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 -------------------------------------------------------------------------------- /tests/nutsflow/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/tests/nutsflow/__init__.py -------------------------------------------------------------------------------- /tests/nutsflow/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maet3608/nuts-flow/0d7b8eefc80cb45c079b155ff5062d1d93ff2caf/tests/nutsflow/examples/__init__.py -------------------------------------------------------------------------------- /tests/nutsflow/examples/test_examples.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: examples 3 | :synopsis: Unit tests for examples module 4 | """ 5 | import nutsflow.examples.examples as examples 6 | 7 | from nutsflow.common import Redirect 8 | 9 | 10 | # Just check that examples are not crashing. 11 | def test_run_examples(): 12 | with Redirect() as out: 13 | examples.run('tests/data/') 14 | assert out.getvalue() 15 | -------------------------------------------------------------------------------- /tests/nutsflow/test_base.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test_base 3 | :synopsis: Unit tests for base module 4 | """ 5 | 6 | import pytest 7 | from nutsflow.base import Nut, NutFunction, NutSource, NutSink 8 | 9 | 10 | def test_Nut(): 11 | nut = Nut(1, 2, num=3) 12 | assert nut.args == (1, 2) 13 | assert nut.kwargs == {'num': 3} 14 | 15 | with pytest.raises(NotImplementedError) as ex: 16 | [1, 2] >> nut 17 | assert str(ex.value).startswith('Needs to implement __rrshift__') 18 | 19 | class Ident(Nut): 20 | def __rrshift__(self, iterable): 21 | return iterable 22 | 23 | assert Ident()([1, 2]) == [1, 2] 24 | 25 | 26 | def test_NutFunction(): 27 | func = NutFunction() 28 | 29 | with pytest.raises(NotImplementedError) as ex: 30 | func(1) 31 | assert str(ex.value).startswith('Needs to implement __call__()') 32 | 33 | class Identity(NutFunction): 34 | def __call__(self, element): 35 | return element 36 | 37 | assert list([1, 2] >> Identity()) == [1, 2] 38 | 39 | 40 | def test_NutSource(): 41 | source = NutSource() 42 | 43 | with pytest.raises(SyntaxError) as ex: 44 | [1, 2] >> source 45 | assert str(ex.value).startswith("Sources don't have inputs") 46 | 47 | with pytest.raises(NotImplementedError) as ex: 48 | for _ in source: 49 | pass 50 | assert str(ex.value).startswith("Needs to implement __iter__()") 51 | 52 | 53 | def test_NutSink(): 54 | sink = NutSink() 55 | 56 | with pytest.raises(SyntaxError) as ex: 57 | for _ in sink: 58 | pass 59 | assert str(ex.value).startswith("Sinks cannot be inputs:") 60 | 61 | class Len(NutSink): 62 | def __rrshift__(self, iterable): 63 | return len(list(iterable)) 64 | 65 | assert [1, 2, 3] >> Len() == 3 66 | assert Len()([1, 2, 3]) == 3 # Sink can operate as function 67 | assert list(map(Len(), ['a', 'bb', 'cc'])) == [1, 2, 2] 68 | -------------------------------------------------------------------------------- /tests/nutsflow/test_common.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test_common 3 | :synopsis: Unit tests for common module 4 | """ 5 | 6 | from __future__ import print_function 7 | 8 | import sys 9 | import time 10 | 11 | import numpy as np 12 | 13 | from pytest import approx 14 | from time import sleep 15 | from collections import namedtuple 16 | from nutsflow import MeanStd 17 | from nutsflow.common import (sec_to_hms, timestr, Redirect, as_tuple, as_set, 18 | as_list, is_iterable, istensor, stype, shapestr, 19 | isnan, colfunc, console, itemize, StableRandom, 20 | print_type, Timer) 21 | 22 | 23 | def test_isnan(): 24 | assert not isnan(1) 25 | assert not isnan(0) 26 | assert isnan(np.NaN) 27 | 28 | 29 | def test_is_iterable(): 30 | assert is_iterable([1, 2]) 31 | assert not is_iterable('12') 32 | 33 | 34 | def test_istensor(): 35 | assert istensor(np.zeros((2, 3))) 36 | assert not istensor([1, 2]) 37 | 38 | 39 | def test_as_tuple(): 40 | assert as_tuple(1) == (1,) 41 | assert as_tuple((1, 2)) == (1, 2) 42 | assert as_tuple([1, 2]) == (1, 2) 43 | 44 | 45 | def test_as_list(): 46 | assert as_list(1) == [1] 47 | assert as_list((1, 2)) == [1, 2] 48 | assert as_list([1, 2]) == [1, 2] 49 | 50 | 51 | def test_as_set(): 52 | assert as_set(1) == (1,) 53 | assert as_set((1, 2)) == {1, 2} 54 | assert as_set([1, 2]) == {1, 2} 55 | 56 | 57 | def test_itemize(): 58 | assert itemize([]) == [] 59 | assert itemize([3]) == 3 60 | assert itemize([3, 2, 1]) == [3, 2, 1] 61 | 62 | 63 | def test_sec_to_hms(): 64 | assert sec_to_hms('80') == (0, 1, 20) 65 | assert sec_to_hms(3 * 60 * 60 + 2 * 60 + 1) == (3, 2, 1) 66 | 67 | 68 | def test_timestr(): 69 | assert timestr('') == '' 70 | assert timestr('80') == '0:01:20' 71 | 72 | 73 | def test_shapestr(): 74 | assert shapestr(np.array([1, 2])) == '2' 75 | assert shapestr(np.zeros((3, 4))) == '3x4' 76 | assert shapestr(np.zeros((3, 4), dtype='uint8'), True) == '3x4:uint8' 77 | 78 | 79 | def test_stype(): 80 | a = np.zeros((3, 4), dtype='uint8') 81 | b = np.zeros((1, 2), dtype='float32') 82 | assert stype(1.1) == ' 1.1' 83 | assert stype([1, 2]) == '[ 1, 2]' 84 | assert stype((1, 2)) == '( 1, 2)' 85 | assert stype({1, 2}) == '{ 1, 2}' 86 | assert stype([1, (2, 3.1)]) == '[ 1, ( 2, 3.1)]' 87 | assert stype(a) == ' 3x4:uint8' 88 | assert stype(b) == ' 1x2:float32' 89 | expect = '[ 3x4:uint8, [ 1x2:float32]]' 90 | assert stype([a, [b]]) == expect 91 | expect = '[[ 3x4:uint8], [ 1x2:float32]]' 92 | assert stype([[a], [b]]) == expect 93 | expect = '{a: 3x4:uint8, b: 1x2:float32}' 94 | assert stype({'a': a, 'b': b}) == expect 95 | Sample = namedtuple('Sample', 'x,y') 96 | expect = 'Sample(x= 3x4:uint8, y= 1)' 97 | assert stype(Sample(a, 1)) == expect 98 | 99 | 100 | def test_print_type(): 101 | with Redirect() as out: 102 | a = np.zeros((3, 4), dtype='uint8') 103 | data = [[a], (1.1, 2)] 104 | print_type(data) 105 | expected = '[[ 3x4:uint8], ( 1.1, 2)]\n' 106 | assert out.getvalue() == expected 107 | 108 | 109 | def test_colfunc(): 110 | data = ['a3', 'b2', 'c1'] 111 | assert list(map(colfunc(None), data)) == data 112 | assert list(map(colfunc(0), data)) == ['a', 'b', 'c'] 113 | assert list(map(colfunc(1), data)) == ['3', '2', '1'] 114 | expected = [['3', 'a'], ['2', 'b'], ['1', 'c']] 115 | assert list(map(colfunc((1, 0)), data)) == expected 116 | 117 | 118 | def test_console(): 119 | with Redirect() as out: 120 | console('test') 121 | assert out.getvalue() == 'test\n' 122 | 123 | 124 | def test_Redirect(): 125 | with Redirect() as out: 126 | print('test') 127 | assert out.getvalue() == 'test\n' 128 | 129 | with Redirect('STDERR') as out: 130 | print('error', file=sys.stderr) 131 | assert out.getvalue() == 'error\n' 132 | 133 | 134 | def test_StableRandom(): 135 | rnd = StableRandom(1) 136 | assert rnd.randint(1, 10) == 5 137 | assert rnd.uniform(-10, 10) == approx(9.943696167306904) 138 | assert rnd.random() == approx(0.7203244894557457) 139 | assert rnd.sample(range(10), 3) == [9, 0, 1] 140 | 141 | lst = [1, 2, 3, 4, 5] 142 | rnd.shuffle(lst) 143 | assert lst == [5, 3, 1, 4, 2] 144 | 145 | rnd = StableRandom() 146 | assert max(rnd.random() for _ in range(1000)) < 1.0 147 | assert min(rnd.random() for _ in range(1000)) >= 0.0 148 | 149 | rnd1, rnd2 = StableRandom(0), StableRandom(0) 150 | for _ in range(100): 151 | assert rnd1.random() == rnd2.random() 152 | 153 | rnd1, rnd2 = StableRandom(0), StableRandom(0) 154 | rnd2.jumpahead(10) 155 | for _ in range(100): 156 | assert rnd1.random() != rnd2.random() 157 | rnd2.setstate(rnd1.getstate()) 158 | for _ in range(100): 159 | assert rnd1.random() == rnd2.random() 160 | 161 | rnd1, rnd2 = StableRandom(0), StableRandom(1) 162 | for _ in range(100): 163 | assert rnd1.random() != rnd2.random() 164 | 165 | rnd1 = StableRandom() 166 | sleep(0.5) # seed is based on system time. 167 | rnd2 = StableRandom() 168 | for _ in range(100): 169 | assert rnd1.random() != rnd2.random() 170 | 171 | rnd = StableRandom() 172 | numbers = [rnd._randbelow(10) for _ in range(1000)] 173 | assert max(numbers) < 10 174 | assert min(numbers) >= 0 175 | 176 | rnd = StableRandom() 177 | numbers = [rnd.gauss_next() for _ in range(10000)] 178 | my, std = numbers >> MeanStd() 179 | assert 0.0 == approx(my, abs=0.1) 180 | assert 1.0 == approx(std, abs=0.1) 181 | 182 | 183 | def test_timer(): 184 | t = Timer() 185 | time.sleep(1.3) 186 | assert str(t) == '00:01' 187 | 188 | with Timer() as t: 189 | time.sleep(1.3) 190 | assert str(t) == '00:01' 191 | 192 | t = Timer() 193 | time.sleep(0.5) 194 | t.start() 195 | time.sleep(1.3) 196 | t.stop() 197 | time.sleep(0.5) 198 | assert str(t) == '00:01' 199 | -------------------------------------------------------------------------------- /tests/nutsflow/test_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test_config 3 | :synopsis: Unit tests for config module 4 | """ 5 | 6 | import pytest 7 | import json 8 | 9 | import nutsflow.config as nc 10 | 11 | 12 | def test_Config(): 13 | cfg = nc.Config({'name': 'stefan', 'address': {'number': 12}}) 14 | assert cfg['name'] == 'stefan' 15 | assert cfg.name == 'stefan' 16 | assert cfg['address']['number'] == 12 17 | assert cfg.address.number == 12 18 | 19 | cfg.address.number = 7 20 | assert cfg['address']['number'] == 7 21 | assert cfg.address.number == 7 22 | 23 | 24 | def test_repr(): 25 | data = {'a': '1', 'b': {'c': 2}} 26 | cfg = nc.Config(data) 27 | expected = json.dumps(data, indent=2, sort_keys=True) 28 | assert cfg.__repr__() == expected 29 | 30 | 31 | def test_isjson(): 32 | assert nc.Config.isjson('mydir/somefile.json') 33 | assert nc.Config.isjson('mydir/somefile.JSON') 34 | assert not nc.Config.isjson('mydir/somefile.yaml') 35 | 36 | 37 | def test_save_load(): 38 | cfg = nc.Config({'number': 13, 'name': 'Stefan'}) 39 | 40 | cfg.save('tests/data/configuration.yaml') 41 | newcfg = nc.Config() 42 | loaded_cfg = newcfg.load('tests/data/configuration.yaml') 43 | assert newcfg.number == 13 44 | assert newcfg == cfg 45 | assert loaded_cfg == cfg 46 | 47 | cfg.save('tests/data/configuration.json') 48 | newcfg = nc.Config() 49 | newcfg.load('tests/data/configuration.json') 50 | assert newcfg.number == 13 51 | assert newcfg == cfg 52 | 53 | 54 | def test_load_config(): 55 | cfg = nc.load_config('tests/data/config.yaml') 56 | assert cfg.filepath == 'c:/Maet' 57 | assert cfg['imagesize'] == [100, 200] 58 | 59 | with pytest.raises(IOError) as ex: 60 | nc.load_config('does not exist') 61 | assert str(ex.value).startswith('Configuration file not found') 62 | -------------------------------------------------------------------------------- /tests/nutsflow/test_factory.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: factory 3 | :synopsis: Unit tests for factory module 4 | """ 5 | 6 | from six.moves import range 7 | from nutsflow.base import Nut 8 | from nutsflow.sink import Collect 9 | from nutsflow.factory import (_arg_insert, _create_nut_wrapper, _wrap, 10 | _create_filter_wrapper, nut_processor, nut_sink, 11 | nut_function, nut_source, nut_filter, 12 | nut_filterfalse) 13 | 14 | 15 | def test_arg_insert(): 16 | args = (1, 2) 17 | assert _arg_insert(args, 'a') == ['a', 1, 2] 18 | assert _arg_insert(args, 'a', 1) == [1, 'a', 2] 19 | assert _arg_insert(args, 'a', 3) == [1, 2, 'a'] 20 | assert _arg_insert(args, 'a', None) == [1, 2, 'a'] 21 | 22 | 23 | def test_wrap(): 24 | class Wrapper(Nut): 25 | pass 26 | 27 | def func(a, b): pass 28 | 29 | wrapped = _wrap(Wrapper, func) 30 | assert wrapped.__doc__ == func.__doc__ 31 | 32 | 33 | def test_create_nut_wrapper(): 34 | func = lambda iterable, arg1, arg2: (iterable, arg1, arg2) 35 | wrapper = _create_nut_wrapper(Nut, func, 0) 36 | assert isinstance(wrapper(), Nut) 37 | assert ['a'] >> wrapper(2, 3) >> Collect() == [['a'], 2, 3] 38 | 39 | 40 | def test_create_filter_wrapper(): 41 | greaterThan = lambda x, t: x > t 42 | wrapper = _create_filter_wrapper(greaterThan) 43 | assert isinstance(wrapper(), Nut) 44 | assert [1, 2, 3, 4] >> wrapper(2) >> Collect() == [3, 4] 45 | 46 | wrapper = _create_filter_wrapper(greaterThan, True) 47 | assert [1, 2, 3, 4] >> wrapper(2) >> Collect() == [1, 2] 48 | 49 | not_empty = lambda x: x 50 | wrapper = _create_filter_wrapper(not_empty) 51 | assert [[1], [], [3], []] >> wrapper() >> Collect() == [[1], [3]] 52 | assert [1, 0, 3, 0] >> wrapper() >> Collect() == [1, 3] 53 | assert [1, [], [3], []] >> wrapper() >> Collect() == [1, [3]] 54 | 55 | 56 | def test_nut_processor(): 57 | @nut_processor 58 | def CloneN(iterable, n): 59 | for e in iterable: 60 | for _ in range(n): 61 | yield e 62 | 63 | assert [1, 2] >> CloneN(2) >> Collect() == [1, 1, 2, 2] 64 | 65 | 66 | def test_nut_sink(): 67 | @nut_sink 68 | def MyCollect(iterable, container): 69 | return container(iterable) 70 | 71 | assert [1, 2, 2] >> MyCollect(set) == {1, 2} 72 | 73 | 74 | def test_nut_function(): 75 | @nut_function 76 | def TimesN(x, n): 77 | return x * n 78 | 79 | assert [1, 2] >> TimesN(2) >> Collect() == [2, 4] 80 | 81 | 82 | def test_nut_source(): 83 | @nut_source 84 | def MyRange(start, end): 85 | return iter(range(start, end)) 86 | 87 | assert MyRange(1, 4) >> Collect() == [1, 2, 3] 88 | 89 | 90 | def test_nut_filter(): 91 | @nut_filter 92 | def GreaterThan(x, threshold): 93 | return x > threshold 94 | 95 | assert [1, 2, 3, 4] >> GreaterThan(2) >> Collect() == [3, 4] 96 | 97 | 98 | def test_nut_filterfalse(): 99 | @nut_filterfalse 100 | def NotGreaterThan(x, threshold): 101 | return x > threshold 102 | 103 | assert [1, 2, 3, 4] >> NotGreaterThan(2) >> Collect() == [1, 2] 104 | -------------------------------------------------------------------------------- /tests/nutsflow/test_iterfunction.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test_iterfunction 3 | :synopsis: Unit tests for iterfunction module 4 | """ 5 | 6 | import time 7 | import nutsflow.iterfunction as itf 8 | 9 | from six.moves import range 10 | 11 | 12 | def test_length(): 13 | assert itf.length(range(10)) == 10 14 | assert itf.length([]) == 0 15 | 16 | 17 | def test_interleave(): 18 | it1 = [1, 2] 19 | it2 = 'abc' 20 | it = itf.interleave(it1, it2) 21 | assert list(it) == [1, 'a', 2, 'b', 'c'] 22 | assert list(itf.interleave([], [])) == [] 23 | assert list(itf.interleave('12', [])) == ['1', '2'] 24 | 25 | 26 | def test_take(): 27 | it = itf.take(range(10), 3) 28 | assert list(it) == [0, 1, 2] 29 | it = itf.take(range(10), 0) 30 | assert list(it) == [] 31 | it = itf.take(range(0), 3) 32 | assert list(it) == [] 33 | 34 | 35 | def test_nth(): 36 | assert itf.nth(range(10), 2) == 2 37 | assert itf.nth(range(10), 100) is None 38 | assert itf.nth(range(10), 100, -1) == -1 39 | 40 | 41 | def test_unique(): 42 | assert list(itf.unique([1, 2, 3])) == [1, 2, 3] 43 | assert list(itf.unique([2, 3, 1, 1, 2, 4])) == [2, 3, 1, 4] 44 | assert list(itf.unique([])) == [] 45 | 46 | data = [(1, 'a'), (2, 'a'), (3, 'b')] 47 | it = itf.unique(data, key=lambda t: t[1]) 48 | assert list(it) == [(1, 'a'), (3, 'b')] 49 | 50 | 51 | def test_chunked(): 52 | it = itf.chunked(range(5), 2) 53 | assert list(map(tuple, it)) == [(0, 1), (2, 3), (4,)] 54 | it = itf.chunked(range(6), 3) 55 | assert list(map(tuple, it)) == [(0, 1, 2), (3, 4, 5)] 56 | assert list(itf.chunked([], 2)) == [] 57 | 58 | 59 | def test_consume(): 60 | it = iter(range(10)) 61 | itf.consume(it) 62 | assert next(it, None) is None 63 | it = iter(range(10)) 64 | itf.consume(it, 5) 65 | assert next(it, None) == 5 66 | 67 | 68 | def test_flatten(): 69 | assert list(itf.flatten([])) == [] 70 | iterable = [(1, 2), (3, 4, 5)] 71 | assert list(itf.flatten(iterable)) == [1, 2, 3, 4, 5] 72 | 73 | 74 | def test_flatmap(): 75 | f = lambda n: str(n) * n 76 | it = itf.flatmap(f, [1, 2, 3]) 77 | assert list(it) == ['1', '2', '2', '3', '3', '3'] 78 | it = itf.flatmap(f, []) 79 | assert list(it) == [] 80 | 81 | 82 | def test_partition(): 83 | pred = lambda x: x < 6 84 | smaller, larger = itf.partition(range(10), pred) 85 | assert list(smaller) == [0, 1, 2, 3, 4, 5] 86 | assert list(larger) == [6, 7, 8, 9] 87 | 88 | 89 | def test_prefetch_iterator_speed(): 90 | def sleep(): 91 | time.sleep(0.01) 92 | 93 | def number_generator(): 94 | for i in range(10): 95 | sleep() 96 | yield i 97 | 98 | start = time.time() 99 | for _ in number_generator(): 100 | sleep() 101 | duration1 = time.time() - start 102 | 103 | start = time.time() 104 | for _ in itf.PrefetchIterator(number_generator()): 105 | sleep() 106 | duration2 = time.time() - start 107 | 108 | assert duration2 < duration1 109 | 110 | 111 | def test_prefetch_iterator_thread_safe(): 112 | from multiprocessing.pool import ThreadPool 113 | 114 | data = set(range(100)) 115 | prefetch_it = itf.PrefetchIterator(data) 116 | 117 | pool = ThreadPool() 118 | result = set(pool.map(lambda x: 2 * x - x, prefetch_it)) 119 | assert result == data 120 | 121 | 122 | -------------------------------------------------------------------------------- /tests/nutsflow/test_source.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test_source 3 | :synopsis: Unit tests for source module 4 | """ 5 | 6 | from six.moves import range 7 | from collections import namedtuple 8 | from nutsflow import * 9 | 10 | 11 | def test_Enumerate(): 12 | assert Enumerate() >> Take(3) >> Collect() == [0, 1, 2] 13 | assert Enumerate(1, 2) >> Take(3) >> Collect() == [1, 3, 5] 14 | 15 | 16 | def test_Repeat(): 17 | assert Repeat(1) >> Take(4) >> Collect() == [1, 1, 1, 1] 18 | 19 | fx = lambda x: x 20 | assert Repeat(fx, 2) >> Take(3) >> Collect() == [2, 2, 2] 21 | 22 | 23 | def test_Product(): 24 | assert Product([]) >> Collect() == [] 25 | 26 | result = [('a', 0), ('a', 1), ('a', 2), ('b', 0), ('b', 1), ('b', 2)] 27 | assert Product('ab', range(3)) >> Collect() == result 28 | 29 | result = [(1, 1), (1, 2), (2, 1), (2, 2)] 30 | assert Product([1, 2], repeat=2) >> Collect() == result 31 | 32 | 33 | def test_Empty(): 34 | assert Empty() >> Collect() == [] 35 | 36 | 37 | def test_Range(): 38 | numbers = Range(3) 39 | assert numbers >> Take(2) >> Collect() == [0, 1] 40 | assert numbers >> Take(2) >> Collect() == [2] 41 | assert numbers >> Take(2) >> Collect() == [] 42 | 43 | assert Range(4) >> Collect() == [0, 1, 2, 3] 44 | assert Range(1, 5) >> Collect() == [1, 2, 3, 4] 45 | 46 | 47 | def test_ReadCSV(): 48 | filepath = 'tests/data/data.csv' 49 | with ReadCSV(filepath) as reader: 50 | assert reader >> Collect() == [('A', 'B', 'C'), 51 | ('1', '2', '3'), 52 | ('4', '5', '6')] 53 | with ReadCSV(filepath, skipheader=1, fmtfunc=int) as reader: 54 | assert reader >> Collect() == [(1, 2, 3), (4, 5, 6)] 55 | with ReadCSV(filepath, skipheader=1, fmtfunc=(int, str, float)) as reader: 56 | assert reader >> Collect() == [(1, '2', 3.), (4, '5', 6.)] 57 | with ReadCSV(filepath, columns=(2, 1)) as reader: 58 | assert reader >> Collect() == [('C', 'B'), ('3', '2'), ('6', '5')] 59 | with ReadCSV(filepath, columns=0) as reader: 60 | assert reader >> Collect() == ['A', '1', '4'] 61 | with ReadCSV(filepath, columns=0, skipheader=1, fmtfunc=(int,)) as reader: 62 | assert reader >> Collect() == [1, 4] 63 | 64 | 65 | def test_ReadCSV_tsv(): 66 | filepath = 'tests/data/data.tsv' 67 | with ReadCSV(filepath, delimiter='\t') as reader: 68 | assert reader >> Collect() == [('A', 'B', 'C'), 69 | ('1', '2', '3'), 70 | ('4', '5', '6')] 71 | with ReadCSV(filepath, skipheader=1, fmtfunc=int, 72 | delimiter='\t') as reader: 73 | assert reader >> Collect() == [(1, 2, 3), (4, 5, 6)] 74 | 75 | 76 | def test_ReadNamedCSV(): 77 | filepath = 'tests/data/data.csv' 78 | Sample = namedtuple('Sample', 'A,B,C') 79 | with ReadNamedCSV(filepath, rowname='Sample') as reader: 80 | assert reader >> Collect() == [Sample(A='1', B='2', C='3'), 81 | Sample(A='4', B='5', C='6')] 82 | Row = namedtuple('Row', 'A,B,C') 83 | with ReadNamedCSV(filepath, fmtfunc=(int, float, str)) as reader: 84 | assert reader >> Collect() == [Row(A=1, B=2.0, C='3'), 85 | Row(A=4, B=5.0, C='6')] 86 | 87 | colnames = ('C', 'A') 88 | Row = namedtuple('Row', colnames) 89 | with ReadNamedCSV(filepath, colnames=colnames, fmtfunc=int) as reader: 90 | assert reader >> Collect() == [Row(C=3, A=1), 91 | Row(C=6, A=4)] 92 | 93 | Number = namedtuple('Row', 'A') 94 | with ReadNamedCSV(filepath, colnames=('A',), fmtfunc=(float,)) as reader: 95 | assert reader >> Collect() == [Number(A=1.0), 96 | Number(A=4.0)] 97 | -------------------------------------------------------------------------------- /tests/nutsflow/test_underscore.py: -------------------------------------------------------------------------------- 1 | """ 2 | .. module:: test underscore 3 | :synopsis: Unit tests for underscore module 4 | """ 5 | 6 | from __future__ import division 7 | 8 | from nutsflow.underscore import _wrap, _ 9 | 10 | 11 | def test_wrap(): 12 | def f(first, second): 13 | return first + second 14 | 15 | assert f('1', '2') == '12' 16 | assert _wrap(f, '1')('2') == '21' 17 | 18 | 19 | def test_underscore(): 20 | assert (_ + 1)(2) == 3 21 | assert (1 + _)(2) == 3 22 | assert (_ - 1)(2) == 1 23 | assert (1 - _)(2) == -1 24 | assert (_ * 2)(4) == 8 25 | assert (2 * _)(4) == 8 26 | assert (_ / 2)(4) == 2 27 | assert (4 / _)(2) == 2 28 | assert (_ % 2)(5) == 1 29 | assert (5 % _)(2) == 1 30 | 31 | assert (_ == 1)(1) 32 | assert not (_ == 1)(2) 33 | assert (_ != 1)(2) 34 | assert not (_ != 1)(1) 35 | assert (_ > 1)(2) 36 | assert (_ >= 1)(2) 37 | assert (_ >= 2)(2) 38 | assert (_ < 2)(1) 39 | assert (_ <= 2)(1) 40 | assert (_ <= 2)(2) 41 | 42 | assert (_[1])([0, 1, 2]) == 1 43 | assert (_[1:3])([0, 1, 2, 4]) == [1, 2] 44 | --------------------------------------------------------------------------------