Skip to content

python-utilities/hybrid-iterator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Hybrid Iterator Home

Python 2/3 iterator cross-bred with a Dictionary

Byte size of init.py Open Issues Open Pull Requests Latest commits


Table of Contents


Quick Start

Bash Variables

_module_name='hybrid-iterator'
_module_https_url="https://github.com/python-utilities/${_module_name}.git"
_module_relative_path="lib/modules/${_module_name//-/_}"

Bash Submodule Commands

cd "<your-git-project-path>"

git checkout masters
mkdir -vp "lib/modules"

git submodule add\
 -b master --name "${_module_name}"\
 "${_module_https_url}" "${_module_relative_path}"

Your ReadMe File

Suggested additions for your ReadMe.md file so everyone has a good time with submodules

Clone with the following to avoid incomplete downloads


    git clone --recurse-submodules <url-for-your-project>


Update/upgrade submodules via


    git submodule update --init --merge --recursive

Utilize Hybrid Iterator

#!/usr/bin/env python


from lib.modules.hybrid_iterator import Hybrid_Iterator


class Priority_Buffer(Hybrid_Iterator):
    """
    Priority_Buffer

    ## Arguments

    - `graph`, with `{name: sub_graph}` and `sub_graph[key_name]` to compare
    - `buffer_size`, `int` of desired `{name: sub_graph}` pairs to buffer
    - `priority`, dictionary containing the following data structure
        - `key_name`, withing `graph` to compare with `_bound`s bellow
        - `GE_bound`, buffers those greater than or equal to `graph[key_name]`
        - `LE_bound`, buffers those less than or equal to `graph[key_name]`
    - `step`, dictionary containing the following `{key: value}` pairs
        - `amount`, to increment or decrement `_bound`s to ensure full buffer
        - `GE_min`/`LE_max`, bounds the related `_bounds` above
    - `modifier` if set __must__ accept `{key: value}` pairs from `graph`
    """

    def __init__(self, graph, priority, buffer_size, step, modifier = None, **kwargs):
        super(Priority_Buffer, self).__init__(**kwargs)
        self.update(
            graph = graph,
            priority = priority,
            buffer_size = buffer_size,
            step = step,
            modifier = modifier,
            buffer = {})

    @property
    def is_buffered(self):
        """
        Returns `True` if buffer is satisfied or graph is empty, `False`
        otherwise. Used by `next()` to detect conditions to `return` on.
        """
        if len(self['buffer'].keys()) >= self['buffer_size']:
            return True

        if len(self['graph'].keys()) <= 0:
            return True

        if self['step'].get('GE_min') is not None:
            if self['priority']['GE_bound'] < self['step']['GE_min']:
                return True
        elif self['step'].get('LE_max') is not None:
            if self['priority']['LE_bound'] > self['step']['LE_max']:
                return True
        else:
            raise ValueError("self['priority'] missing step missing min/max")

        return False

    def top_priority(self, graph = None):
        """
        Yields `dict`s from `graph` where value of `graph[key_name]`,
        as set by `self['priority']['key_name']`, is within range of
        `self['GE_bound']` or `self['LE_bound']`

        - `graph`, dictionary that is __destructively__ read (`pop`ed) from

        > if `graph` is `None` then `top_priority` reads from `self['graph']`
        """
        if graph is None:
            graph = self['graph']

        key_name = self['priority']['key_name']
        for name, node in graph.items():
            if self['priority'].get('GE_bound') is not None:
                if node[key_name] >= self['priority']['GE_bound']:
                    yield {name: graph.pop(name)}
            elif self['priority'].get('LE_bound') is not None:
                if node[key_name] <= self['priority']['LE_bound']:
                    yield {name: graph.pop(name)}
            else:
                raise ValueError('Misconfiguration, either `GE_`/`LE_bound`s ')

        self.throw(GeneratorExit)

    def next(self):
        """
        Sets `self['buffer']` from `self.top_priority()` and returns `self`
        """
        if not self['graph']:
            self.throw(GeneratorExit)

        self['buffer'] = {}
        priority_gen = self.top_priority()
        while not self.is_buffered:
            try:
            except (StopIteration, GeneratorExit):
                if self['priority'].get('GE_bound'):
                    self['priority']['GE_bound'] += self['step']['amount']
                    priority_gen = self.top_priority()
                elif self['priority'].get('LE_bound'):
                    self['priority']['LE_bound'] += self['step']['amount']
                    priority_gen = self.top_priority()
                else:
                    raise ValueError("self['priority'] missing bounds")
            else:
                try:
                    self['buffer'].update(self['modifier'](next_sub_graph))
                except TypeError:
                    self['buffer'].update(next_sub_graph)

        return self


if __name__ == '__main__':
    """
    The following are run when this file is executed as
    a script, eg. `python priority_buffer.py`
    but not executed when imported as a module, thus a
    good place to put unit tests.
    """
    from random import randint

    print("Initalizing unit test.\n{0}".format("".join(['_' for x in range(9)])))
    graph = {}
    for i in range(0, 21, 1):
        graph.update({
            "sub_graph_{0}".format(i): {
                'points': {},
                'first_to_compute': randint(0, 9),
            }
        })

    print("Sample graph head.\n{0}".format("".join(['_' for x in range(9)])))
    head_counter = 0
    for k, v in graph.items():
        print("{0} -> {1}".format(k, v))
        head_counter += 1
        if head_counter > 3:
            break

    buffer = Priority_Buffer(
        graph = graph,
        priority = {'key_name': 'first_to_compute',
                    'GE_bound': 7},
        step = {'amount': -2,
                'GE_min': -1},
        buffer_size = 5,
    )

    print("Iterating over sample graph.\n{0}".format("".join(['_' for x in range(9)])))
    counter = 0
    c_max = int(len(graph.keys()) / buffer['buffer_size'] + 1)
    # ... (21 / 5) + 1 -> int -> 5
    for chunk in buffer:
        print("Chunk {count} of ~ {max}".format(
            count = counter, max = c_max - 1))

        for key, val in chunk['buffer'].items():
            print("\t{k} -> {v}".format(**{
                'k': key, 'v': val}))

        counter += 1

        if counter > c_max:
            raise Exception("Hunt for bugs!")

    print("Finished test.\n{0}".format("".join(['_' for x in range(9)])))

Commit and Push

git add .gitmodules
git add lib/modules/hybrid_iterator


## Add any changed files too


git commit -F- <<'EOF'
:heavy_plus_sign: Adds `python-utilities/hybrid-iterator#1` submodule



**Additions**


- `.gitmodules`, tracks submodules AKA Git within Git _fanciness_

- `README.md`, updates installation and updating guidance

- `lib/modules/hybrid_iterator`, builds list of pages for a named collection
EOF


git push origin master

πŸŽ‰ Excellent πŸŽ‰ your repository is now ready to begin unitizing code from this project!


Notes

Hybrid Iterator is intended for importing and modification, and not for stand-alone use.


License

Legal bits of Open Source software

Hybrid Iterator ReadMe documenting how things like this could be utilized
Copyright (C) 2019  S0AndS0

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation; version 3 of the License.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.