Commits

Lynn Rees  committed d57d2e8

- polish docs

  • Participants
  • Parent commits ef2b4ff
  • Tags 0.6.0

Comments (0)

Files changed (85)

+syntax:glob
+*.pyc
+*.egg-info
+*~
+.DS_Store
+.figleaf
+.coverage
+build/
+dist/
+.project
+.pydevproject
+.git/
+.settings/
+*.DS_Store
+_FOSSIL_
+unfinished/
+src/
+static/
+media/
+*.orig
+syntax: glob
+.tox/*
+syntax: regexp
+^stuff$

File CHANGELOG

-What's new in 0.5.5
-===================
-
-* fix issue with traverse method
-
-What's new in 0.5.4
-===================
-
-* remove compatibility imports to separate packages
-
-What's new in 0.5.3
-===================
-
-* speedups
-
-What's new in 0.5.2
-===================
-
-* verify PyPy 1.8, Python 3.1 compatibility
-* made randomizing methods lazier
-* memoize some routines
-
-What's new in 0.5.6
-===================
-
-- selectively memoize
-
-What's new in 0.5.7
-===================
-
-- fix more issues with traversal
-
-What's new in 0.5.8
-===================
-
-- add new search expression support
-
-What's new in 0.5.9
-===================
-
-- update to stuf 0.9.0
-`blade` is a powerful `Python <http://docs.python.org/>`_ multitool
-loosely inspired by `Underscore.js <http://documentcloud.github.com/underscore/>`_
-but remixed for maximum `pythonicity <http://docs.python.org/glossary.html#term-pythonic>`_. 
+*blade* Documentation
+#####################
 
-`blade` concentrates power that is normally dispersed across the entire
-Python universe in one convenient shrink-wrapped package.
+:mod:`blade` is a powerful set of iterable processing tools extracted from
+`knife <http://pypi.python.org/knife/>`_ for standalone use.
 
 Vitals
 ======
 
-`blade` works with CPython 2.6, 2.7, 3.1. and 3.2 and PyPy 1.8.
+:mod:`blade` works with CPython 2.6, 2.7, 3.2, 3.3 and PyPy 1.8.
 
-`blade` documentation is at http://readthedocs.org/docs/blade/en/latest/ or
-http://packages.python.org/blade/
+:mod:`blade` documentation can be found at http://packages.python.org/blade/
 
 Installation
 ============
 
-Install `blade` with `pip <http://www.pip-installer.org/en/latest/index.html>`_...::
+Install :mod:`blade` with `pip <http://www.pip-installer.org/en/latest/index.html>`_...::
 
   $ pip install blade
   [... possibly exciting stuff happening ...]
   Successfully installed blade
-  
+
 ...or `easy_install <http://packages.python.org/distribute/>`_...::
 
   $ easy_install blade
   [... possibly exciting stuff happening ...]
   Finished processing dependencies for blade
-  
-...or old school by downloading `blade` from http://pypi.python.org/pypi/blade/::
+
+...or old school by downloading :mod:`blade` from http://pypi.python.org/pypi/blade/::
 
   $ python setup.py install
   [... possibly exciting stuff happening ...]
  * Public repository: https://bitbucket.org/lcrees/blade.
  * Mirror: https://github.com/kwarterthieves/blade/
  * Issue tracker: https://bitbucket.org/lcrees/blade/issues
- * License: `BSD <http://www.opensource.org/licenses/bsd-license.php>`_
-
-3 second *blade*
-================
-
-Things go in:
-
-  >>> from blade import __
-  >>> gauntlet = __(5, 4, 3, 2, 1)
-  
-Things get bladed:
-
-  >>> gauntlet.initial().rest().slice(1, 2).last()
-  blade.lazy.lazyblade ([IN: ([3]) => WORK: ([]) => HOLD: ([]) => OUT: ([3])])
-
-Things come out:
-
-  >>> gauntlet.get()
-  3
-
-Slightly more *blade*
-=====================
-
-`blade` has 40 plus methods that can be `chained <https://en.wikipedia.org/
-wiki/Fluent_interface>`_ into pipelines...
-
-contrived example:
-^^^^^^^^^^^^^^^^^^
-
-  >>> __(5, 4, 3, 2, 1).initial().rest().slice(1, 2).last().get()
-  3
-
-...or used object-oriented style.
-
-contrived example:
-^^^^^^^^^^^^^^^^^^
-
-  >>> from blade import blade
-  >>> oo = blade(5, 4, 3, 2, 1)
-  >>> oo.initial()
-  blade.active.activeblade ([IN: ([5, 4, 3, 2, 1]) => WORK: ([]) => HOLD: ([]) => OUT: ([5, 4, 3, 2])])
-  >>> oo.rest()
-  blade.active.activeblade ([IN: ([5, 4, 3, 2]) => WORK: ([]) => HOLD: ([]) => OUT: ([4, 3, 2])])
-  >>> oo.slice(1, 2)
-  blade.active.activeblade ([IN: ([4, 3, 2]) => WORK: ([]) => HOLD: ([]) => OUT: ([3])])
-  >>> oo.last()
-  blade.active.activeblade ([IN: ([3]) => WORK: ([]) => HOLD: ([]) => OUT: ([3])])
-  >>> oo.get()
-  3
-  
-A `blade` object can roll its current state back to previous states
-like snapshots of immediately preceding operations, a baseline snapshot, or even 
-a snapshot of the original arguments.
-
-contrived example:
-^^^^^^^^^^^^^^^^^^
-  
-  >>> undone = __(1, 2, 3).prepend(1, 2, 3, 4, 5, 6)
-  >>> undone.peek()
-  [1, 2, 3, 4, 5, 6, 1, 2, 3]
-  >>> undone.append(1).undo().peek()
-  [1, 2, 3, 4, 5, 6, 1, 2, 3]
-  >>> undone.append(1).append(2).undo(2).peek()
-  [1, 2, 3, 4, 5, 6, 1, 2, 3]
-  >>> undone.snapshot().append(1).append(2).baseline().peek()
-  [1, 2, 3, 4, 5, 6, 1, 2, 3]
-  >>> undone.original().peek()
-  [1, 2, 3]
-
-`blade` objects come in two flavors: `active` and `lazy`.
-`active.blade` objects evaluate the result of calling a
-method immediately after the call. Calling the same method with
-a `lazy.blade` object only yields results when it is iterated over
-or `blade.lazy.lazyblade.get` is called to get results.
-  
-`blade.lazy.lazyblade` combines all `blade` methods in one class:
-
-  >>> from blade import lazyblade
-
-It can be imported under its *dunderscore* (`blade.__`) alias.
-
-  >>> from blade import __  
-  
-`blade.active.activeblade` also combines every `blade` method in one
-combo `blade` class:
-
-  >>> from blade import activeblade
-
-It can be imported under its `blade.blade` alias:
- 
-  >>> from blade import blade
-
-`blade` methods are available in more focused classes that group related 
-methods together. These classes can also be chained into pipelines.
-
-contrived example:
-^^^^^^^^^^^^^^^^^^
-
-  >>> from blade.active import mathblade, reduceblade
-  >>> one = mathblade(10, 5, 100, 2, 1000)
-  >>> two = reduceblade()
-  >>> one.minmax().pipe(two).merge().back().min().get()
-  2
-  >>> one.original().minmax().pipe(two).merge().back().max().get()
-  1000
-  >>> one.original().minmax().pipe(two).merge().back().sum().get()
-  1002
+ * License: `BSD <http://www.opensource.org/licenses/bsd-license.php>`_

File blade/__init__.py

 # -*- coding: utf-8 -*-
-'''Things go in. Things happen. Things come out.'''
+'''Powerful iterable processing tools extracted from knife.'''
 
 
-__version__ = (0, 5, 11)
+__version__ = (0, 6, 0)

File blade/core.py

-# -*- coding: utf-8 -*-
-'''blade mixins.'''
-
-from math import fsum
-from copy import deepcopy
-from inspect import getmro, isclass
-from random import randrange, shuffle
-from functools import reduce, partial
-from collections import deque, namedtuple
-from operator import attrgetter, itemgetter, methodcaller, truediv
-from itertools import (
-    chain, combinations, groupby, islice, repeat, permutations, starmap, tee)
-
-from stuf.deep import selfname, members
-from stuf.base import identity, dualidentity
-from stuf.six.moves import filterfalse, zip_longest  # @UnresolvedImport
-from stuf.collects import OrderedDict, Counter, ChainMap
-from stuf.iterable import deferfunc, deferiter, count, partmap, partstar
-from stuf.six import (
-    filter, items, keys as keyz, map as xmap, isstring, values as valuez, next)
-
-Count = namedtuple('Count', 'least most overall')
-GroupBy = namedtuple('Group', 'keys groups')
-MinMax = namedtuple('MinMax', 'min max')
-TrueFalse = namedtuple('TrueFalse', 'true false')
-slicer = partial(lambda n, i, x, y: n(i(x, y, None)), next, islice)
-xmerge = chain.from_iterable
-xsort = sorted
-xreverse = reversed
-xpermutate = permutations
-xcombine = combinations
-xzip = partial(lambda zl, i: zl(*i), zip_longest)
-
-################################################################################
-## BLADE COMPARISON OPERATIONS #################################################
-################################################################################
-
-def xall(iterable, func, cut=False):
-    '''
-    Discover if `func` is :data:`True` for **all** items in `iterable`.
-
-   :argument iterable: iterable object
-    :return: :func:`bool`
-
-    >>> from blade import xall, oneorall
-    >>> xall(lambda x: x % 2 == 0, (2, 4, 6, 8))
-    True
-    '''
-    return all(xmap(func, iterable))
-
-def xany(iterable, func):
-    '''
-    Discover if `func` is :data:`True` for **any** items in `iterable`.
-
-   :argument iterable: iterable object
-    :return: :func:`bool`
-
-    >>> from blade import xany
-    >>> oneorall(list(xany(lambda x: x % 2 == 0, (1, 4, 5, 9))))
-    True
-    '''
-    return any(xmap(func, iterable))
-
-def xdiff(iterable, symmetric=False):
-    '''
-    Discover difference within a series of iterable items in `iterable`.
-
-    :argument iterable: iterable object
-    :keyword bool symmetric: do a symmetric difference operation
-
-    :return: :func:`list`
-
-    >>> from blade import xdiff
-    >>> # default behavior
-    >>> list(xdiff(([1, 2, 3, 4, 5], [5, 2, 10], [10, 11, 2])))
-    [1, 3, 4]
-    >>> # symmetric difference
-    >>> list(xdiff(([1, 2, 3, 4, 5], [5, 2, 10], [10, 11, 2]), False))
-    [1, 2, 3, 4, 11]
-    '''
-    if symmetric:
-        test = partial(lambda s, x, y: s(x).symmetric_difference(y), set)
-    else:
-        test = partial(lambda s, x, y: s(x).difference(y), set)
-    return reduce(test, iterable)
-
-def xintersect(iterable):
-    '''
-    Discover intersection within a series of iterable items in `iterable`.
-
-    :argument iterable: iterable object
-    :return: :func:`list`
-
-    >>> from blade import xintersect
-    >>> list(xintersect(([1, 2, 3], [101, 2, 1, 10], [2, 1])))
-    [1, 2]
-    '''
-    return reduce(partial(lambda s, x, y: s(x).intersection(y), set), iterable)
-
-def xunion(iterable):
-    '''
-    Discover union within a series of iterable items in `iterable`.
-
-    :argument iterable: iterable object
-    :return: :func:`list`
-
-    >>> from blade import xunion
-    >>> list(xunion(([1, 2, 3], [101, 2, 1, 10], [2, 1])))
-    [1, 10, 3, 2, 101]
-    '''
-    return reduce(partial(lambda s, x, y: s(x).union(y), set), iterable)
-
-def xunique(iterable, func=None):
-    '''
-    Discover unique items in `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> from blade import xunique
-    >>> # no key function
-    >>> list(xunique((1, 2, 1, 3, 1, 4)))
-    [1, 2, 3, 4]
-    >>> # with key function
-    >>> list(xunique(round, (1, 2, 1, 3, 1, 4)))
-    [1.0, 2.0, 3.0, 4.0]
-    '''
-    def unique(key, iterable, _n=next):
-        seen = set()
-        seenadd = seen.add
-        try:
-            while 1:
-                element = key(_n(iterable))
-                if element not in seen:
-                    yield element
-                    seenadd(element)
-        except StopIteration:
-            pass
-    return unique(identity if func is None else func, iter(iterable))
-
-################################################################################
-## BLADE MATH OPERATIONS #######################################################
-################################################################################
-
-def xaverage(iterable):
-    '''
-    Discover average value of numbers in `iterable`.
-
-    :argument iterable: iterable object
-    :return: a number
-
-    >>> from blade import xaverage
-    >>> oneorall(list(xaverage((10, 40, 45))))
-    31.666666666666668
-    '''
-    i1, i2 = tee(iterable)
-    return truediv(sum(i1, 0.0), count(i2))
-
-def xcount(iterable):
-    '''
-    Discover how common each item in `iterable` is and the overall count of
-    each item in `iterable`.
-
-    :argument iterable: iterable object
-
-    :return: Collects :func:`~collections.namedtuple` ``Count(least=int,
-      most=int, overall=[(thing1, int), (thing2, int), ...])``
-
-    >>> common = __(11, 3, 5, 11, 7, 3, 5, 11).count().get()
-    >>> # least common thing
-    >>> common.least
-    7
-    >>> # most common thing
-    >>> common.most
-    11
-    >>> # total count for every thing
-    >>> common.overall
-    [(11, 3), (3, 2), (5, 2), (7, 1)]
-    '''
-    cnt = Counter(iterable).most_common
-    commonality = cnt()
-    return Count(
-        # least common
-        commonality[:-2:-1][0][0],
-        # most common (mode)
-        cnt(1)[0][0],
-        # overall commonality
-        commonality,
-    )
-
-def xmedian(iterable):
-    '''
-    Discover the median value among items in `iterable`.
-
-    :argument iterable: iterable object
-    :return: a number
-
-    >>> __(4, 5, 7, 2, 1).median().get()
-    4
-    >>> __(4, 5, 7, 2, 1, 8).median().get()
-    4.5
-    '''
-    i1, i2 = tee(sorted(iterable))
-    result = truediv(count(i1) - 1, 2)
-    pint = int(result)
-    if result % 2 == 0:
-        return slicer(i2, pint)
-    i3, i4 = tee(i2)
-    return truediv(slicer(i3, pint) + slicer(i4, pint + 1), 2)
-
-def xminmax(iterable):
-    '''
-    Discover the minimum and maximum values among items in `iterable`.
-
-    :argument iterable: iterable object
-    :return:  :func:`~collections.namedtuple` ``MinMAx(min=value, max=value)``.
-
-    >>> minmax = __(1, 2, 4).minmax().get()
-    >>> minmax.min
-    1
-    >>> minmax.max
-    4
-    '''
-    i1, i2 = tee(iterable)
-    return MinMax(min(i1), max(i2))
-
-def xinterval(iterable):
-    '''
-    Discover the length of the smallest interval that can contain the value of
-    every items in `iterable`.
-
-    :argument iterable: iterable object
-
-    :return: a number
-
-    >>> __(3, 5, 7, 3, 11).range().get()
-    8
-    '''
-    i1, i2 = tee(sorted(iterable))
-    return deque(i1, maxlen=1).pop() - next(i2)
-
-def xsum(iterable, start=0, precision=False):
-    '''
-    Discover the total value of adding `start` and items in `iterable` together.
-
-    :argument iterable: iterable object
-    :keyword start: starting number
-    :type start: :func:`int` or :func:`float`
-    :keyword bool precision: add floats with extended precision
-
-    >>> # default behavior
-    >>> __(1, 2, 3).sum().get()
-    6
-    >>> # with a starting mumber
-    >>> __(1, 2, 3).sum(start=1).get()
-    7
-    >>> # add floating points with extended precision
-    >>> __(.1, .1, .1, .1, .1, .1, .1, .1).sum(precision=True).get()
-    0.8
-    '''
-    return fsum(iterable) if precision else sum(iterable, start)
-
-################################################################################
-## BLADE ORDERING OPERATIONS ###################################################
-################################################################################
-
-def xgroup(iterable, func=None):
-    '''
-    Group items in `iterable` using `func` as the :term:`key function`.
-
-    :argument iterable: iterable object
-
-    :return: :func:`~collections.namedtuple` ``Group(keys=keys, groups=tuple)``
-
-    >>> from blade import __
-    >>> # default grouping
-    >>> __(1.3, 2.1).group().get()
-    [Group(keys=1.3, groups=(1.3,)), Group(keys=2.1, groups=(2.1,))]
-    >>> from math import floor
-    >>> # use func for key function
-    >>> __(1.3, 2.1, 2.4).func(floor).group().get()
-    [Group(keys=1.0, groups=(1.3,)), Group(keys=2.0, groups=(2.1, 2.4))]
-    '''
-    def grouper(func, iterable, _n=next, _g=GroupBy, _t=tuple):
-        try:
-            it = groupby(sorted(iterable, key=func), func)
-            while 1:
-                k, v = _n(it)
-                yield _g(k, _t(v))
-        except StopIteration:
-            pass
-    return grouper(identity if func is None else func, iterable)
-
-
-def xshuffle(iterable):
-    '''
-    Randomly sort items in `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> __(5, 4, 3, 2, 1).shuffle().get() # doctest: +SKIP
-    [3, 1, 5, 4, 2]
-    '''
-    iterable = list(iterable)
-    shuffle(iterable)
-    return iterable
-
-################################################################################
-## BLADE REPEATING OPERATIONS ##################################################
-################################################################################
-
-def xcopy(iterable):
-    '''
-    Duplicate each items in `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> __([[1, [2, 3]], [4, [5, 6]]]).copy().get()
-    [[1, [2, 3]], [4, [5, 6]]]
-    '''
-    return xmap(deepcopy, iterable)
-
-def xrepeat(iterable, n=None, func=False):
-    '''
-    Repeat items in `iterable` `n` times or invoke `func` `n` times.
-
-    :argument iterable: iterable object
-    :keyword int n: number of times to repeat
-    :keyword bool func: repeat results of invoking `func`
-
-    >>> # repeat iterable
-    >>> __(40, 50, 60).repeat(3).get()
-    [(40, 50, 60), (40, 50, 60), (40, 50, 60)]
-    >>> def test(*args):
-    ...    return list(args)
-    >>> # with func
-    >>> __(40, 50, 60).func(test).repeat(n=3, func=True).get()
-    [[40, 50, 60], [40, 50, 60], [40, 50, 60]]
-    '''
-    if not func:
-        return repeat(tuple(iterable), n)
-    return starmap(func, repeat(tuple(iterable), n))
-
-
-################################################################################
-## BLADE MAPPING OPERATIONS ####################################################
-################################################################################
-
-def xargmap(iterable, func, merge=False, *args):
-    '''
-    Feed each items in `iterable` to `func` as :term:`positional
-    argument`\s.
-
-    :argument iterable: iterable object
-
-    :keyword bool merge: merge global positional :meth:`params` with
-      positional arguments derived from items in `iterable` when passed to
-      `func`
-
-    >>> from blade import __
-    >>> # default behavior
-    >>> test = __((1, 2), (2, 3), (3, 4))
-    >>> test.func(lambda x, y: x * y).argmap().get()
-    [2, 6, 12]
-    >>> # merge global positional arguments with iterable arguments
-    >>> test.original().func(
-    ...   lambda x, y, z, a, b: x * y * z * a * b
-    ... ).params(7, 8, 9).argmap(merge=True).get()
-    [1008, 3024, 6048]
-    '''
-    if merge:
-        return partstar(lambda f, *arg: f(*(arg + args)), iterable, func)
-    return starmap(func, iterable)
-
-def xinvoke(iterable, name, *args, **kw):
-    '''
-    Feed global :term:`positional argument`\s and :term:`keyword
-    argument`\s to each items in `iterable`'s `name` :term:`method`.
-
-    .. note::
-
-      The original thing is returned if the return value of :term:`method`
-      `name` is :data:`None`.
-
-    :argument iterable: iterable object
-    :argument str name: method name
-
-    :rtype: :obj:`blade` :term:`object`
-
-    >>> # invoke list.index()
-    >>> __([5, 1, 7], [3, 2, 1]).params(1).invoke('index').get()
-    [1, 2]
-    >>> # invoke list.sort() but return sorted list instead of None
-    >>> __([5, 1, 7], [3, 2, 1]).invoke('sort').get()
-    [[1, 5, 7], [1, 2, 3]]
-    '''
-    def invoke(caller, thing):
-        read = caller(thing)
-        return thing if read is None else read
-    return partmap(invoke, iterable, methodcaller(name, *args, **kw))
-
-def xkwargmap(iterable, func, merge=False, *args, **kw):
-    '''
-    Feed each items in `iterable` as a :func:`tuple` of
-    :term:`positional argument`\s and :term:`keyword argument`\s to
-    `func`.
-
-    :argument iterable: iterable object
-
-    :keyword bool merge: merge global positional or keyword :meth:`params`
-      with positional and keyword arguments derived from items in `iterable`
-      into a single :func:`tuple` of wildcard positional and keyword
-      arguments like ``(*iterable_args + global_args, **global_kwargs +
-      iterable_kwargs)`` when passed to `func`
-
-    >>> # default behavior
-    >>> test = __(
-    ...  ((1, 2), {'a': 2}), ((2, 3), {'a': 2}), ((3, 4), {'a': 2})
-    ... )
-    >>> def tester(*args, **kw):
-    ...    return sum(args) * sum(kw.values())
-    >>> test.func(tester).kwargmap().get()
-    [6, 10, 14]
-    >>> # merging global and iterable derived positional and keyword args
-    >>> test.original().func(tester).params(
-    ...   1, 2, 3, b=5, w=10, y=13
-    ... ).kwargmap(merge=True).get()
-    [270, 330, 390]
-    '''
-    if merge:
-        def kwargmap(func, *params):
-            arg, kwarg = params
-            kwarg.update(kw)
-            return func(*(arg + args), **kwarg)
-    else:
-        kwargmap = lambda f, x, y: f(*x, **y)
-    return partstar(kwargmap, iterable, func)
-
-def xkeyvalue(iterable, func=None, keys=False, values=False):
-    '''
-    Run `func` on incoming :term:`mapping` things.
-
-    :argument iterable: iterable object
-    :keyword bool keys: collect mapping keys only
-    :keyword bool values: collect mapping values only
-
-    >>> # filter items
-    >>> __(dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])
-    ... ).func(lambda x, y: x * y).mapping().get()
-    [2, 6, 12, 2, 6, 12]
-    >>> # mapping keys only
-    >>> __(dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])
-    ... ).mapping(keys=True).get()
-    [1, 2, 3, 1, 2, 3]
-    >>> # mapping values only
-    >>> __(dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])
-    ... ).mapping(values=True).get()
-    [2, 3, 4, 2, 3, 4]
-    '''
-
-    if keys:
-        func = identity if func is None else func
-        return xmap(func, xmerge(xmap(keyz, iterable)))
-    elif values:
-        func = identity if func is None else func
-        return xmap(func, xmerge(xmap(valuez, iterable)))
-    func = dualidentity if func is None else func
-    return starmap(func, xmerge(xmap(items, iterable)))
-
-################################################################################
-## BLADE FILTERING OPERATIONS ##################################################
-################################################################################
-
-def xattrs(iterable, *names):
-    '''
-    Collect :term:`attribute` values from items in `iterable` that match an
-    **attribute name** found in `names`.
-
-    :argument iterable: iterable object
-    :argument str names: attribute names
-
-    >>> from blade import __
-    >>> from stuf import stuf
-    >>> stooge = [
-    ...    stuf(name='moe', age=40),
-    ...    stuf(name='larry', age=50),
-    ...    stuf(name='curly', age=60),
-    ... ]
-    >>> __(*stooge).attrs('name').get()
-    ['moe', 'larry', 'curly']
-    >>> # multiple attribute names
-    >>> __(*stooge).attrs('name', 'age').get()
-    [('moe', 40), ('larry', 50), ('curly', 60)]
-    >>> # no attrs named 'place'
-    >>> __(*stooge).attrs('place').get()
-    []
-    '''
-    def attrs(iterable, _n=next):
-        try:
-            get = attrgetter(*names)
-            while 1:
-                try:
-                    yield get(_n(iterable))
-                except AttributeError:
-                    pass
-        except StopIteration:
-            pass
-    return attrs(iter(iterable))
-
-def xtruefalse(iterable, func):
-    '''
-    Divide items in `iterable` into two `iterable`\s, the first everything
-    `func` is :data:`True` for and the second everything `func` is :data:`False`
-    for.
-
-    :argument iterable: iterable object
-
-    >>> divide = xtruefalse([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0)
-    >>> divide.true
-    (2, 4, 6)
-    >>> divide.false
-    (1, 3, 5)
-    '''
-    truth, false = tee(iterable)
-    return TrueFalse(
-        tuple(filter(func, truth)), tuple(filterfalse(func, false))
-    )
-
-def xfilter(iterable, func, invert=False):
-    '''
-    Collect items in `iterable` matched by `func`.
-
-    :argument iterable: iterable object
-
-    :keyword bool invert: collect items in `iterable` `func` is
-      :data:`False` rather than :data:`True` for
-
-    >>> # filter for true values
-    >>> list(xfilter([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0)
-    [2, 4, 6]
-    >>> # filter for false values
-    >>> list(xfilter([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0, invert=True)
-    [1, 3, 5]
-    '''
-    return (filterfalse if invert else filter)(func, iterable)
-
-def xitems(iterable, *keys):
-    '''
-    Collect values from items in `iterable` (usually a :term:`sequence` or
-    :term:`mapping`) that match a **key** found in `keys`.
-
-    :argument iterable: iterable object
-
-    :argument str keys: keys or indices
-
-    >>> stooge = [
-    ...    dict(name='moe', age=40),
-    ...    dict(name='larry', age=50),
-    ...    dict(name='curly', age=60)
-    ... ]
-    >>> # get items from mappings like dictionaries, etc...
-    >>> list(xitems(stooge, 'name'))
-    ['moe', 'larry', 'curly']
-    >>> list(xitems(stooge, 'name', 'age'))
-    [('moe', 40), ('larry', 50), ('curly', 60)]
-    >>> # get items from sequences like lists, tuples, etc...
-    >>> stooge = [['moe', 40], ['larry', 50], ['curly', 60]]
-    >>> list(xitems(stooge, 0))
-    ['moe', 'larry', 'curly']
-    >>> list(xitems(stooge, 1))
-    [40, 50, 60]
-    >>> list(xitems(stooge, 'place'))
-    []
-    '''
-    def itemz(iterable, _n=next):
-        try:
-            get = itemgetter(*keys)
-            while 1:
-                try:
-                    yield get(_n(iterable))
-                except (IndexError, KeyError, TypeError):
-                    pass
-        except StopIteration:
-            pass
-    return itemz(iter(iterable))
-
-def xtraverse(iterable, func, invert=False):
-    '''
-    Collect values from deeply :term:`nested scope`\s from items in `iterable`
-    matched by `func`.
-
-    :argument iterable: iterable object
-
-    :keyword bool invert: collect items in `iterable` that `func` is
-      :data:`False` rather than :data:`True` for
-
-    :returns: :term:`sequence` of `ChainMaps <http://docs.python.org/dev/
-      library/collections.html#collections.ChainMap>`_ containing
-      :class:`collections.OrderedDict`
-
-    :rtype: :obj:`blade` :term:`object`
-
-    >>> class stooge:
-    ...    name = 'moe'
-    ...    age = 40
-    >>> class stooge2:
-    ...    name = 'larry'
-    ...    age = 50
-    >>> class stooge3:
-    ...    name = 'curly'
-    ...    age = 60
-    ...    class stooge4(object):
-    ...        name = 'beastly'
-    ...        age = 969
-    >>> def test(x):
-    ...    if x[0] == 'name':
-    ...        return True
-    ...    elif x[0].startswith('__'):
-    ...        return True
-    ...    return False
-    >>> # using func while filtering for False values
-    >>> list(xtraverse([stooge, stooge2, stooge3], test, invert=True))
-    ... # doctest: +NORMALIZE_WHITESPACE
-    [ChainMap(OrderedDict([('classname', 'stooge'), ('age', 40)])),
-    ChainMap(OrderedDict([('classname', 'stooge2'), ('age', 50)])),
-    ChainMap(OrderedDict([('classname', 'stooge3'), ('age', 60)]),
-    OrderedDict([('age', 969), ('classname', 'stooge4')]))]
-    '''
-    ifilter = filterfalse if invert else filter
-    def members(iterable):  # @IgnorePep8
-        mro = getmro(iterable)
-        names = iter(dir(iterable))
-        beenthere = set()
-        adder = beenthere.add
-        try:
-            OD = OrderedDict
-            vz = vars
-            cn = chain
-            ga = getattr
-            ic = isclass
-            nx = next
-            while 1:
-                name = nx(names)
-                # yes, it's really supposed to be a tuple
-                for base in cn([iterable], mro):
-                    var = vz(base)
-                    if name in var:
-                        obj = var[name]
-                        break
-                else:
-                    obj = ga(iterable, name)
-                if (name, obj) in beenthere:
-                    continue
-                else:
-                    adder((name, obj))
-                if ic(obj):
-                    yield name, OD((k, v) for k, v in ifilter(
-                        func, members(obj),
-                    ))
-                else:
-                    yield name, obj
-        except StopIteration:
-            pass
-    def traverse(iterable):  # @IgnorePep8
-        try:
-            iterable = iter(iterable)
-            OD = OrderedDict
-            sn = selfname
-            nx = next
-            while 1:
-                iterator = nx(iterable)
-                chaining = ChainMap()
-                chaining['classname'] = sn(iterator)
-                cappend = chaining.maps.append
-                for k, v in ifilter(func, members(iterator)):
-                    if isinstance(v, OD):
-                        v['classname'] = k
-                        cappend(v)
-                    else:
-                        chaining[k] = v
-                yield chaining
-        except StopIteration:
-            pass
-    return traverse(iterable)
-
-def xmembers(iterable, func, inverse=False):
-    '''
-    Collect values from shallowly from classes or objects matched by `func`.
-
-    :argument iterable: iterable object
-    :keyword bool invert: collect items in `iterable` that `func` is
-      :data:`False` rather than :data:`True` for
-
-    :returns: :term:`sequence` of :func:`tuple` of keys and value
-    '''
-    return xfilter(xmerge(xmap(members, iterable)), func, inverse)
-
-def xmro(iterable):
-    return xmerge(xmap(getmro, iterable))
-
-################################################################################
-## BLADE REDUCING OPERATIONS ###################################################
-################################################################################
-
-def xflatten(iterable):
-    '''
-    Reduce nested items in `iterable` to flattened items in `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> list(xflatten([[1, [2], [3, [[4]]]], 'here']))
-    [1, 2, 3, 4, 'here']
-    '''
-    def flatten(iterable, _n=next, _is=isstring):
-        next_ = iter(iterable)
-        try:
-            while 1:
-                item = _n(next_)
-                try:
-                    # don't recur over strings
-                    if _is(item):
-                        yield item
-                    else:
-                        # do recur over other things
-                        for j in flatten(item):
-                            yield j
-                except (AttributeError, TypeError):
-                    # does not recur
-                    yield item
-        except StopIteration:
-            pass
-    return flatten(iterable)
-
-def xreduce(iterable, func, initial=None, reverse=False):
-    '''
-    Reduce `iterable` items in `iterable` down to one items in `iterable` using
-    `func`.
-
-    :argument iterable: iterable object
-
-    :keyword initial: starting value
-
-    :keyword bool reverse: reduce from `the right side <http://www.zvon.org/
-      other/haskell/Outputprelude/foldr_f.html>`_ of items in `iterable`
-
-    >>> # reduce from left side
-    >>> xreduce([1, 2, 3], lambda x, y: x + y)
-    6
-    >>> # reduce from left side with initial value
-    >>> xreduce([1, 2, 3]. lambda x, y: x + y, initial=1)
-    7
-    >>> # reduce from right side
-    >>> xreduce([[0, 1], [2, 3], [4, 5]], lambda x, y: x + y, reverse=True)
-    [4, 5, 2, 3, 0, 1]
-    >>> # reduce from right side with initial value
-    >>>  xreduce(
-    ... [[0, 1], [2, 3], [4, 5]], lambda x, y: x + y, [0, 0], reverse=True
-    ... )
-    [4, 5, 2, 3, 0, 1, 0, 0]
-    '''
-    if reverse:
-        if initial is None:
-            return reduce(partial(lambda f, x, y: f(y, x), func), iterable)
-        return reduce(partial(lambda f, x, y: f(y, x), func), iterable, initial)
-    if initial is None:
-        return reduce(func, iterable)
-    return reduce(func, iterable, initial)
-
-################################################################################
-## BLADE SLICING OPERATIONS ####################################################
-################################################################################
-
-def xat(iterable, n, default=None):
-    '''
-    :term:`Slice` off items in `iterable` found at index `n`.
-
-    :argument iterable: iterable object
-    :argument int n: index of some items in `iterable`
-    :keyword default: default returned if nothing is found at `n`
-
-    >>> # default behavior
-    >>> xat([5, 4, 3, 2, 1], 2)
-    3
-    >>> # return default value if nothing found at index
-    >>> xat([5, 4, 3, 2, 1], 10, 11)
-    11
-    '''
-    return next(islice(iterable, n, None), default)
-
-def xchoice(iterable):
-    '''
-    Randomly :term:`slice` off **one** items in `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> list(xchoice([1, 2, 3, 4, 5, 6])) # doctest: +SKIP
-    3
-    '''
-    i1, i2 = tee(iterable)
-    return xat(i1, randrange(0, count(i2)))
-
-def xdice(iterable, n, fill=None):
-    '''
-    :term:`Slice` one `iterable` items in `iterable` into `n` iterable items in
-    `iterable`.
-
-    :argument iterable: iterable object
-    :argument int n: number of items in `iterable` per slice
-    :keyword fill: value to pad out incomplete iterables
-
-    >>> list(xdice(['moe', 'larry', 'curly', 30, 40, 50, True], 2, 'x'))
-    [('moe', 'larry'), ('curly', 30), (40, 50), (True, 'x')]
-    '''
-    return zip_longest(fillvalue=fill, *[iter(iterable)] * n)
-
-def xfirst(iterable, n=0):
-    '''
-    :term:`Slice`  off `n` things from the **starting** end of `iterable` or
-    just the **first** items in `iterable`.
-
-    :argument iterable: iterable object
-    :keyword int n: number of items in `iterable`
-
-    >>> # default behavior
-    >>> list(xfirst([5, 4, 3, 2, 1]))
-    5
-    >>> # first things from index 0 to 2
-    >>> list(xfirst([5, 4, 3, 2, 1], 2))
-    [5, 4]
-    '''
-    return islice(iterable, n) if n else deferiter(iter(iterable))
-
-def xinitial(iterable):
-    '''
-    :term:`Slice` off every items in `iterable` except the **last** `iterable`.
-
-    :argument iterable: iterable object
-
-    >>> list(xinitial([5, 4, 3, 2, 1]))
-    [5, 4, 3, 2]
-    '''
-    i1, i2 = tee(iterable)
-    return islice(i1, count(i2) - 1)
-
-def xlast(iterable, n=0):
-    '''
-    :term:`Slice` off `n` things from the **tail** end of items in `iterable` or
-    just the **last** items in `iterable`.
-
-    :argument iterable: iterable object
-    :keyword int n: number of items in `iterable` to slice off
-
-    >>> # default behavior
-    >>> list(xlast([5, 4, 3, 2, 1]))[0]
-    1
-    >>> # fetch last two things
-    >>> list(xlast([5, 4, 3, 2, 1], 2))
-    [2, 1]
-    '''
-    if n:
-        i1, i2 = tee(iterable)
-        return islice(i1, count(i2) - n, None)
-    return iter(deferfunc(deque(iterable, maxlen=1).pop))
-
-def xrest(iterable):
-    '''
-    :term:`Slice` off every items in `iterable` except the **first** item.
-
-    :argument iterable: iterable object
-
-    >>> list(xrest([5, 4, 3, 2, 1]))
-    [4, 3, 2, 1]
-    '''
-    return islice(iterable, 1, None)
-
-def xsample(iterable, n):
-    '''
-    Randomly :term:`slice` off `n` items in `iterable`.
-
-    :argument iterable: iterable object
-    :argument int n: sample size
-
-    >>> list(xsample([1, 2, 3, 4, 5, 6], 3)) # doctest: +SKIP
-    [2, 4, 5]
-    '''
-    i1, i2 = tee(iterable)
-    return partmap(
-        lambda i, r, c, x: i(x, r(0, c)),
-        tee(i2, n),
-        islice,
-        randrange,
-        count(i1),
-    )
-
-def xslice(iterable, start, stop=None, step=None):
-    '''
-    Take :term:`slice` out of items in `iterable`.
-
-    :argument iterable: iterable object
-    :argument int start: starting index of slice
-    :keyword int stop: stopping index of slice
-    :keyword int step: size of step in slice
-
-    >>> # slice from index 0 to 3
-    >>> list(xslice([5, 4, 3, 2, 1], 2))
-    [5, 4]
-    >>> # slice from index 2 to 4
-    >>> list(xslice([5, 4, 3, 2, 1], 2, 4))
-    [3, 2]
-    >>> # slice from index 2 to 4 with 2 steps
-    >>> list(xslice([5, 4, 3, 2, 1], 2, 4, 2))
-    3
-    '''
-    if stop is not None and step is not None:
-        return islice(iterable, start, stop, step)
-    elif stop is not None:
-        return islice(iterable, start, stop)
-    return islice(iterable, start)

File blade/xcmp.py

+# -*- coding: utf-8 -*-
+''':class:`blade` comparison operations'''
+
+from functools import reduce, partial
+
+from stuf.base import identity
+from stuf.six import map as xmap, next
+
+
+def xall(iterable, test):
+    '''
+    Discover if `test` is :data:`True` for **all** items in :term:`iterable`.
+
+    :argument iterable: :term:`iterable`
+    :argument test: filtering :func:`callable`
+    :return: :func:`bool`
+
+    >>> from blade.xcmp import xall
+    >>> xall([2, 4, 6, 8], lambda x: x % 2 == 0)
+    True
+    '''
+    return all(xmap(test, iterable))
+
+
+def xany(iterable, test):
+    '''
+    Discover if `test` is :data:`True` for **any** items in :term:`iterable`.
+
+    :argument iterable: :term:`iterable`.
+    :argument test: filtering :func:`callable`
+    :return: :func:`bool`
+
+    >>> from blade.xcmp import xany
+    >>> xany([1, 4, 5, 9], lambda x: x % 2 == 0)
+    True
+    '''
+    return any(xmap(test, iterable))
+
+
+def xdiff(iterable, symmetric=False):
+    '''
+    Discover difference within a series of :term:`iterable` items in
+    :term:`iterable`.
+
+    :argument iterable: :term:`iterable`
+    :keyword bool symmetric: return symmetric difference
+    :return: :term:`iterator` of items
+
+    >>> from blade.xcmp import xdiff
+    >>> # default behavior
+    >>> list(xdiff([[1, 2, 3, 4, 5], [5, 2, 10], [10, 11, 2]]))
+    [1, 3, 4]
+    >>> # symmetric difference
+    >>> list(xdiff([[1, 2, 3, 4, 5], [5, 2, 10], [10, 11, 2]], True))
+    [1, 2, 3, 4, 11]
+    '''
+    if symmetric:
+        test = partial(lambda s, x, y: s(x).symmetric_difference(y), set)
+    else:
+        test = partial(lambda s, x, y: s(x).difference(y), set)
+    return reduce(test, iterable)
+
+
+def xintersect(iterable):
+    '''
+    Discover intersection within a series of :term:`iterable` items in
+    :term:`iterable`.
+
+    :argument iterable: :term:`iterable`
+    :return: :term:`iterator` of items
+
+    >>> from blade.xcmp import xintersect
+    >>> list(xintersect([[1, 2, 3], [101, 2, 1, 10], [2, 1]]))
+    [1, 2]
+    '''
+    return reduce(partial(lambda s, x, y: s(x).intersection(y), set), iterable)
+
+
+def xunion(iterable):
+    '''
+    Discover union within a series of :term:`iterable` items in
+    :term:`iterable`.
+
+    :argument iterable: :term:`iterable`
+    :return: :term:`iterator` of items
+
+    >>> from blade.xcmp import xunion
+    >>> list(xunion([[1, 2, 3], [101, 2, 1, 10], [2, 1]]))
+    [1, 10, 3, 2, 101]
+    '''
+    return reduce(partial(lambda s, x, y: s(x).union(y), set), iterable)
+
+
+def xunique(iterable, test=None):
+    '''
+    Discover unique items in :term:`iterable` that pass `test`.
+
+    :argument iterable: :term:`iterable`
+    :argument test: filtering :func:`callable`
+    :return: :term:`iterator` of items
+
+    >>> from blade.xcmp import xunique
+    >>> # no key function
+    >>> list(xunique([1, 2, 1, 3, 1, 4]))
+    [1, 2, 3, 4]
+    >>> # with key function
+    >>> list(xunique([1, 2, 1, 3, 1, 4], round))
+    [1.0, 2.0, 3.0, 4.0]
+    '''
+    def unique(key, iterable, _n=next):
+        seen = set()
+        seenadd = seen.add
+        try:
+            while 1:
+                element = key(_n(iterable))
+                if element not in seen:
+                    yield element
+                    seenadd(element)
+        except StopIteration:
+            pass
+    return unique(identity if test is None else test, iter(iterable))

File blade/xfilter.py

+# -*- coding: utf-8 -*-
+''':class:`blade` filtering operations'''
+
+from collections import namedtuple
+from inspect import getmro, isclass
+from itertools import chain, tee, groupby
+from operator import attrgetter, itemgetter
+
+from stuf.base import identity
+from stuf.six.moves import filterfalse  # @UnresolvedImport
+from stuf.deep import selfname, members
+from stuf.six import filter, map as xmap, next
+from stuf.collects import OrderedDict, ChainMap
+
+Group = namedtuple('Group', 'keys groups')
+TrueFalse = namedtuple('TrueFalse', 'true false')
+xmerge = chain.from_iterable
+
+
+def xattrs(iterable, *names):
+    '''
+    Collect :term:`attribute` values from items in :term:`iterable` that match
+    an :term:`attribute` name in `names`.
+
+    :argument iterable: :term:`iterable`
+    :argument str names: attribute names
+    :return: :term:`iterator` of values
+
+    >>> from blade.xfilter import xattrs
+    >>> from stuf import stuf
+    >>> stooge = [
+    ...    stuf(name='moe', age=40),
+    ...    stuf(name='larry', age=50),
+    ...    stuf(name='curly', age=60),
+    ... ]
+    >>> list(xattrs(stooge, 'name'))
+    ['moe', 'larry', 'curly']
+    >>> # multiple attribute names
+    >>> list(xattrs(stooge, 'name', 'age'))
+    [('moe', 40), ('larry', 50), ('curly', 60)]
+    >>> # no attrs named 'place'
+    >>> list(xattrs(stooge, 'place'))
+    []
+    '''
+    def attrs(iterable, _n=next):
+        try:
+            get = attrgetter(*names)
+            while 1:
+                try:
+                    yield get(_n(iterable))
+                except AttributeError:
+                    pass
+        except StopIteration:
+            pass
+    return attrs(iter(iterable))
+
+
+def xfilter(iterable, test, invert=False):
+    '''
+    Collect items in :term:`iterable` matched by `test`.
+
+    :argument iterable: :term:`iterable`
+
+    :argument test: filtering :func:`callable`
+
+    :keyword bool invert: collect items in :term:`iterable` that
+      `test``test` is :data:`False` rather than :data:`True` for
+
+    :return: :term:`iterator` of :data:`True` or, alternatively, :data:`False`
+      items
+
+    >>> from blade.xfilter import xfilter
+    >>> # filter for true values
+    >>> list(xfilter([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0))
+    [2, 4, 6]
+    >>> # filter for false values
+    >>> list(xfilter([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0, invert=True))
+    [1, 3, 5]
+    '''
+    return (filterfalse if invert else filter)(test, iterable)
+
+
+def xgroup(iterable, test=None):
+    '''
+    Group items in `iterable` using `test` as the :term:`key function`.
+
+    :argument iterable: :term:`iterable`
+
+    :argument test: filtering :func:`callable`
+
+    :return: :term:`iterator` of :func:`~collections.namedtuple`\s of
+      ``Group(keys=keys, groups=tuple)``
+
+    >>> from blade.xfilter import xgroup
+    >>> # default grouping
+    >>> list(xgroup([1.3, 2.1]))
+    [Group(keys=1.3, groups=(1.3,)), Group(keys=2.1, groups=(2.1,))]
+    >>> from math import floor
+    >>> # use test for key function
+    >>> list(xgroup([1.3, 2.1, 2.4], floor))
+    [Group(keys=1.0, groups=(1.3,)), Group(keys=2.0, groups=(2.1, 2.4))]
+    '''
+    def grouper(test, iterable, _n=next, _g=Group, _t=tuple):
+        try:
+            it = groupby(sorted(iterable, key=test), test)
+            while 1:
+                k, v = _n(it)
+                yield _g(k, _t(v))
+        except StopIteration:
+            pass
+    return grouper(identity if test is None else test, iterable)
+
+def xitems(iterable, *keys):
+    '''
+    Collect values from items in :term:`iterable` (usually a :term:`sequence` or
+    :term:`mapping`) that match a **key** found in `keys`.
+
+    :argument iterable: :term:`iterable`
+    :param str keys: keys or indices
+    :return: :term:`iterator` of items
+
+    >>> from blade.xfilter import xitems
+    >>> stooge = [
+    ...    dict(name='moe', age=40),
+    ...    dict(name='larry', age=50),
+    ...    dict(name='curly', age=60),
+    ... ]
+    >>> # get items from mappings like dictionaries, etc...
+    >>> list(xitems(stooge, 'name'))
+    ['moe', 'larry', 'curly']
+    >>> list(xitems(stooge, 'name', 'age'))
+    [('moe', 40), ('larry', 50), ('curly', 60)]
+    >>> # get items from sequences like lists, tuples, etc...
+    >>> stooge = [['moe', 40], ['larry', 50], ['curly', 60]]
+    >>> list(xitems(stooge, 0))
+    ['moe', 'larry', 'curly']
+    >>> list(xitems(stooge, 1))
+    [40, 50, 60]
+    >>> list(xitems(stooge, 'place'))
+    []
+    '''
+    def itemz(iterable, _n=next):
+        try:
+            get = itemgetter(*keys)
+            while 1:
+                try:
+                    yield get(_n(iterable))
+                except (IndexError, KeyError, TypeError):
+                    pass
+        except StopIteration:
+            pass
+    return itemz(iter(iterable))
+
+
+def xmembers(iterable, test, inverse=False):
+    '''
+    Collect values shallowly from classes or objects in :term:`class`\es in
+    :term:`iterable` matched by `test`.
+
+    :argument iterable: :term:`iterable`
+
+    :argument test: filtering :func:`callable`
+
+    :keyword bool invert: collect items in :term:`iterable` that
+      `test` is :data:`False` rather than :data:`True` for
+
+    :return: :term:`iterator` of values
+
+    >>> from blade.xfilter import xmembers
+    >>> class stooges:
+    ...    name = 'moe'
+    ...    age = 40
+    >>> class stoog2:
+    ...    name = 'larry'
+    ...    age = 50
+    >>> class stoog3:
+    ...    name = 'curly'
+    ...    age = 60
+    ...    class stoog4:
+    ...        name = 'beastly'
+    ...        age = 969
+    >>> list(xmembers([stoog3], lambda x: not x[0].startswith('__'))) # doctest: +SKIP
+    [('age', 60), ('name', 'curly'), ('stoog4', stoog3.stoog4)]
+    '''
+    return xfilter(xmerge(xmap(members, iterable)), test, inverse)
+
+
+def xmro(iterable):
+    '''
+    Extract classes in the :term:`method resolution order` of classes or objects
+    in :term:`class`\es in :term:`iterable`.
+
+    :argument iterable: :term:`iterable`
+    :return: :term:`iterator` of :term:`class`\es
+
+    >>> from blade.xfilter import xmro
+    >>> class stooges:
+    ...    name = 'moe'
+    ...    age = 40
+    >>> class stoog2(stooges):
+    ...    name = 'larry'
+    ...    age = 50
+    >>> class stoog3(stoog2):
+    ...    name = 'curly'
+    ...    age = 60
+    ...    class stoog4:
+    ...        name = 'beastly'
+    ...        age = 969
+    >>> results = list(xmro([stoog3]))
+    >>> stooges in results
+    True
+    >>> stoog3 in results
+    True
+    >>> stoog2 in results
+    True
+    '''
+    return xmerge(xmap(getmro, iterable))
+
+
+def xtraverse(iterable, test, invert=False):
+    '''
+    Collect values from deeply :term:`nested scope`\s from items in
+    :term:`iterable` matched by `test`.
+
+    :argument iterable: :term:`iterable`
+
+    :argument test: filtering :func:`callable`
+
+    :keyword bool invert: collect items in :term:`iterable` that
+      `test` is :data:`False` rather than :data:`True` for
+
+    :return: :term:`iterator` of `ChainMaps <http://docs.python.org/dev/
+      library/collections.html#collections.ChainMap>`_ containing
+      :class:`~collections.OrderedDict`
+
+    >>> from blade.xfilter import xtraverse
+    >>> class stooge:
+    ...    name = 'moe'
+    ...    age = 40
+    >>> class stooge2:
+    ...    name = 'larry'
+    ...    age = 50
+    >>> class stooge3:
+    ...    name = 'curly'
+    ...    age = 60
+    ...    class stooge4(object):
+    ...        name = 'beastly'
+    ...        age = 969
+    >>> def test(x):
+    ...    if x[0] == 'name':
+    ...        return True
+    ...    elif x[0].startswith('__'):
+    ...        return True
+    ...    return False
+    >>> # using test while filtering for False values
+    >>> list(xtraverse([stooge, stooge2, stooge3], test, invert=True)) # doctest: +NORMALIZE_WHITESPACE
+    [ChainMap(OrderedDict([('classname', 'stooge'), ('age', 40)])),
+    ChainMap(OrderedDict([('classname', 'stooge2'), ('age', 50)])),
+    ChainMap(OrderedDict([('classname', 'stooge3'), ('age', 60)]),
+    OrderedDict([('age', 969), ('classname', 'stooge4')]))]
+    '''
+    ifilter = filterfalse if invert else filter
+    def members(iterable):  # @IgnorePep8
+        mro = getmro(iterable)
+        names = iter(dir(iterable))
+        beenthere = set()
+        adder = beenthere.add
+        try:
+            OD = OrderedDict
+            vz = vars
+            cn = chain
+            ga = getattr
+            ic = isclass
+            nx = next
+            while 1:
+                name = nx(names)
+                # yes, it's really supposed to be a tuple
+                for base in cn([iterable], mro):
+                    var = vz(base)
+                    if name in var:
+                        obj = var[name]
+                        break
+                else:
+                    obj = ga(iterable, name)
+                if (name, obj) in beenthere:
+                    continue
+                else:
+                    adder((name, obj))
+                if ic(obj):
+                    yield name, OD((k, v) for k, v in ifilter(
+                        test, members(obj),
+                    ))
+                else:
+                    yield name, obj
+        except StopIteration:
+            pass
+    def traverse(iterable):  # @IgnorePep8
+        try:
+            iterable = iter(iterable)
+            OD = OrderedDict
+            sn = selfname
+            nx = next
+            while 1:
+                iterator = nx(iterable)
+                chaining = ChainMap()
+                chaining['classname'] = sn(iterator)
+                cappend = chaining.maps.append
+                for k, v in ifilter(test, members(iterator)):
+                    if isinstance(v, OD):
+                        v['classname'] = k
+                        cappend(v)
+                    else:
+                        chaining[k] = v
+                yield chaining
+        except StopIteration:
+            pass
+    return traverse(iterable)
+
+
+def xtruefalse(iterable, test):
+    '''
+    Divide items in :term:`iterable` into two :term:`iterable`\s, the first
+    everything `test` is :data:`True` for and the second everything `test` is
+    :data:`False` for.
+
+    :argument iterable: :term:`iterable`
+    :argument test: filtering :func:`callable`
+
+    :return: :func:`~collections.namedtuple` of two :term:`iterator`\s, one of
+      items for which `test` is :data:`True` and one for which `test` is
+      :data:`False`.
+
+    >>> from blade.xfilter import xtruefalse
+    >>> divide = xtruefalse([1, 2, 3, 4, 5, 6], lambda x: x % 2 == 0)
+    >>> tuple(divide.true)
+    (2, 4, 6)
+    >>> tuple(divide.false)
+    (1, 3, 5)
+    '''
+    truth, false = tee(iterable)
+    return TrueFalse(filter(test, truth), filterfalse(test, false))

File blade/xmap.py

+# -*- coding: utf-8 -*-
+''':class:`blade` mapping operations'''
+
+from copy import deepcopy
+from operator import methodcaller
+from itertools import chain, repeat, starmap
+
+from stuf.iterable import partmap, partstar
+from stuf.base import identity, dualidentity
+from stuf.six import items, keys as keyz, map as xmap, values as valuez
+
+xmerge = chain.from_iterable
+
+
+def xargmap(iterable, callable, merge=False, *args):
+    '''
+    Feed each items in `iterable` to `callable` as :term:`positional argument`\s.
+
+    :argument iterable: :term:`iterable`
+    :argument callable: mapped :func:`callable`
+
+    :keyword bool merge: merge global positional :meth:`params` with positional
+      arguments derived from items in `iterable` when passed to `callable`
+
+    >>> from blade.xmap import xargmap
+    >>> # default behavior
+    >>> list(xargmap([(1, 2), (2, 3), (3, 4)], lambda x, y: x * y))
+    [2, 6, 12]
+    >>> # merge global positional arguments with iterable arguments
+    >>> list(xargmap(
+    ...   [(1, 2), (2, 3), (3, 4)],
+    ...   lambda x, y, z, a, b: x * y * z * a * b,
+    ...   True,
+    ...   7, 8, 9,
+    ...  ))
+    [1008, 3024, 6048]
+    '''
+    if merge:
+        return partstar(lambda f, *arg: f(*(arg + args)), iterable, callable)
+    return starmap(callable, iterable)
+
+
+def xcopy(iterable):
+    '''
+    Duplicate each items in `iterable`.
+
+    :argument iterable: :term:`iterable`
+
+    >>> from blade.xmap import xcopy
+    >>> list(xcopy([[1, [2, 3]], [4, [5, 6]]]))
+    [[1, [2, 3]], [4, [5, 6]]]
+    '''
+    return xmap(deepcopy, iterable)
+
+
+def xinvoke(iterable, name, *args, **kw):
+    '''
+    Feed global :term:`positional argument`\s and :term:`keyword argument`\s to
+    each items in `iterable`'s `name` :term:`method`.
+
+    .. note::
+
+      The original thing is returned if the return value of :term:`method`
+      `name` is :data:`None`.
+
+    :argument iterable: :term:`iterable`
+    :argument str name: method name
+
+    >>> from blade.xmap import xinvoke
+    >>> # invoke list.index()
+    >>> list(xinvoke([[5, 1, 7], [3, 2, 1]], 'index', 1))
+    [1, 2]
+    >>> # invoke list.sort() but return sorted list instead of None
+    >>> list(xinvoke([[5, 1, 7], [3, 2, 1]], 'sort'))
+    [[1, 5, 7], [1, 2, 3]]
+    '''
+    def invoke(caller, thing):
+        read = caller(thing)
+        return thing if read is None else read
+    return partmap(invoke, iterable, methodcaller(name, *args, **kw))
+
+
+def xkwargmap(iterable, callable, merge=False, *args, **kw):
+    '''
+    Feed each items in `iterable` as a :func:`tuple` of
+    :term:`positional argument`\s and :term:`keyword argument`\s to `callable`.
+
+    :argument iterable: :term:`iterable`
+
+    :argument callable: mapped :func:`callable`
+
+    :keyword bool merge: merge global positional or keyword :meth:`params`
+      with positional and keyword arguments derived from items in `iterable`
+      into a single :func:`tuple` of wildcard positional and keyword
+      arguments like ``(*iterable_args + global_args, **global_kwargs +
+      iterable_kwargs)`` when passed to `callable`
+
+    >>> from blade.xmap import xkwargmap
+    >>> # default behavior
+    >>> test = [((1, 2), {'a': 2}), ((2, 3), {'a': 2}), ((3, 4), {'a': 2})]
+    >>> def tester(*args, **kw):
+    ...    return sum(args) * sum(kw.values())
+    >>> list(xkwargmap(test, tester))
+    [6, 10, 14]
+    >>> # merging global and iterable derived positional and keyword args
+    >>> list(xkwargmap(test, tester, True, 1, 2, 3, b=5, w=10, y=13))
+    [270, 330, 390]
+    '''
+    if merge:
+        def kwargmap(callable, *params):
+            arg, kwarg = params
+            kwarg.update(kw)
+            return callable(*(arg + args), **kwarg)
+    else:
+        kwargmap = lambda f, x, y: f(*x, **y)
+    return partstar(kwargmap, iterable, callable)
+
+
+def xkeyvalue(iterable, callable=None, keys=False, values=False):
+    '''
+    Run `callable` on incoming :term:`mapping` things.
+
+    :argument iterable: :term:`iterable`
+    :argument callable: mapped :func:`callable`
+    :keyword bool keys: collect mapping keys only
+    :keyword bool values: collect mapping values only
+
+    >>> from blade.xmap import xkeyvalue
+    >>> # filter items
+    >>> list(xkeyvalue(
+    ... [dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])],
+    ... lambda x, y: x * y))
+    [2, 6, 12, 2, 6, 12]
+    >>> # mapping keys only
+    >>> list(xkeyvalue(
+    ... [dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])],
+    ... keys=True
+    ... ))
+    [1, 2, 3, 1, 2, 3]
+    >>> # mapping values only
+    >>> list(xkeyvalue(
+    ... [dict([(1, 2), (2, 3), (3, 4)]), dict([(1, 2), (2, 3), (3, 4)])],
+    ... values=True
+    ... ))
+    [2, 3, 4, 2, 3, 4]
+    '''
+    if keys:
+        callable = identity if callable is None else callable
+        return xmap(callable, xmerge(xmap(keyz, iterable)))
+    elif values:
+        callable = identity if callable is None else callable
+        return xmap(callable, xmerge(xmap(valuez, iterable)))
+    callable = dualidentity if callable is None else callable
+    return starmap(callable, xmerge(xmap(items, iterable)))
+
+def xrepeat(iterable, callable=None, n=None):
+    '''
+    Repeat items in `iterable` `n` times or invoke `callable` `n` times.
+
+    :argument iterable: :term:`iterable`
+    :keyword callable: :func:`callable` to repeatedly invoke
+    :keyword int n: number of times to repeat
+
+    >>> from blade.xmap import xrepeat
+    >>> # repeat iterable
+    >>> list(xrepeat([40, 50, 60], n=3))
+    [(40, 50, 60), (40, 50, 60), (40, 50, 60)]
+    >>> # with callable
+    >>> list(xrepeat([40, 50, 60], lambda *args: list(args), 3))
+    [[40, 50, 60], [40, 50, 60], [40, 50, 60]]
+    '''
+    if not callable:
+        return repeat(tuple(iterable), n)
+    return starmap(callable, repeat(tuple(iterable), n))

File blade/xmath.py

+# -*- coding: utf-8 -*-
+''':class:`blade` mathing operations.'''
+
+from math import fsum
+from itertools import tee
+from operator import truediv
+from collections import deque, namedtuple
+
+from stuf.six import next
+from stuf.iterable import count
+from stuf.collects import Counter
+
+from .xslice import xslicer
+
+Count = namedtuple('Count', 'least most overall')
+MinMax = namedtuple('MinMax', 'min max')
+
+
+def xaverage(iterable):
+    '''
+    Discover average value of numbers in `iterable`.
+
+    :argument iterable: iterable object
+    :return: a number
+
+    >>> from blade.xmath import xaverage
+    >>> xaverage([10, 40, 45])
+    31.666666666666668
+    '''
+    i1, i2 = tee(iterable)
+    return truediv(sum(i1, 0.0), count(i2))
+
+
+def xcount(iterable):
+    '''
+    Discover how common each item in `iterable` is and the overall count of each
+    item in `iterable`.
+
+    :argument iterable: iterable object
+
+    :return: Collects :func:`~collections.namedtuple` ``Count(least=int,
+      most=int, overall=[(thing1, int), (thing2, int), ...])``
+
+    >>> from blade.xmath import xcount
+    >>> common = xcount([11, 3, 5, 11, 7, 3, 5, 11])
+    >>> # least common thing
+    >>> common.least
+    7
+    >>> # most common thing
+    >>> common.most
+    11
+    >>> # total count for every thing
+    >>> common.overall
+    [(11, 3), (3, 2), (5, 2), (7, 1)]
+    '''
+    cnt = Counter(iterable).most_common
+    commonality = cnt()
+    return Count(
+        # least common
+        commonality[:-2:-1][0][0],
+        # most common (mode)
+        cnt(1)[0][0],
+        # overall commonality
+        commonality,
+    )
+
+
+def xmedian(iterable):
+    '''
+    Discover median value of numbers in `iterable`.
+
+    :argument iterable: iterable object
+    :return: a number
+
+    >>> from blade.xmath import xmedian
+    >>> xmedian([4, 5, 7, 2, 1])
+    4
+    >>> xmedian([4, 5, 7, 2, 1, 8])
+    4.5
+    '''
+    i1, i2 = tee(sorted(iterable))
+    result = truediv(count(i1) - 1, 2)
+    pint = int(result)
+    if result % 2 == 0:
+        return xslicer(i2, pint)
+    i3, i4 = tee(i2)
+    return truediv(xslicer(i3, pint) + xslicer(i4, pint + 1), 2)
+
+
+def xminmax(iterable):
+    '''
+    Discover the minimum and maximum values among items in `iterable`.
+
+    :argument iterable: iterable object
+    :return:  :func:`~collections.namedtuple` ``MinMAx(min=value, max=value)``.
+
+    >>> from blade.xmath import xminmax
+    >>> minmax = xminmax([1, 2, 4])
+    >>> minmax.min
+    1
+    >>> minmax.max
+    4
+    '''
+    i1, i2 = tee(iterable)
+    return MinMax(min(i1), max(i2))
+
+
+def xinterval(iterable):
+    '''
+    Discover the length of the smallest interval that can contain the value of
+    every items in `iterable`.
+
+    :argument iterable: iterable object
+
+    :return: a number
+
+    >>> from blade.xmath import xinterval
+    >>> xinterval([3, 5, 7, 3, 11])
+    8
+    '''
+    i1, i2 = tee(sorted(iterable))
+    return deque(i1, maxlen=1).pop() - next(i2)
+
+
+def xsum(iterable, start=0, precision=False):
+    '''
+    Discover the total value of adding `start` and items in `iterable` together.
+
+    :argument iterable: iterable object
+    :keyword start: starting number
+    :type start: :func:`int` or :func:`float`
+    :keyword bool precision: add floats with extended precision
+
+    >>> from blade.xmath import xsum
+    >>> # default behavior
+    >>> xsum([1, 2, 3])
+    6
+    >>> # with a starting mumber
+    >>> xsum([1, 2, 3], start=1)
+    7
+    >>> # add floating points with extended precision
+    >>> xsum([.1, .1, .1, .1, .1, .1, .1, .1], precision=True)
+    0.8
+    '''
+    return fsum(iterable) if precision else sum(iterable, start)

File blade/xreduce.py

+# -*- coding: utf-8 -*-
+''':class:`blade` reduction operations'''
+
+from functools import reduce, partial
+
+from stuf.six import isstring, next
+
+
+def xflatten(iterable):
+    '''
+    Reduce nested items in `iterable` to flattened items in `iterable`.
+
+    :argument iterable: :term:`iterable`
+
+    >>> from blade.xreduce import xflatten
+    >>> list(xflatten([[1, [2], [3, [[4]]]], 'here']))
+    [1, 2, 3, 4, 'here']
+    '''
+    def flatten(iterable, _n=next, _is=isstring):
+        next_ = iter(iterable)
+        try:
+            while 1:
+                item = _n(next_)
+                try:
+                    # don't recur over strings
+                    if _is(item):
+                        yield item
+                    else:
+                        # do recur over other things
+                        for j in flatten(item):
+                            yield j
+                except (AttributeError, TypeError):
+                    # does not recur
+                    yield item
+        except StopIteration:
+            pass
+    return flatten(iterable)
+
+
+def xreduce(iterable, callable, initial=None, invert=False):
+    '''
+    Reduce `iterable` items in `iterable` down to one items in `iterable` using
+    `callable`.
+
+    :argument callable: reducing :func:`callable`
+
+    :argument iterable: :term:`iterable`
+
+    :keyword initial: starting value
+
+    :keyword bool invert: reduce from `the right side <http://www.zvon.org/
+      other/haskell/Outputprelude/foldr_f.html>`_ of items in `iterable`
+
+    >>> from blade.xreduce import xreduce
+    >>> # reduce from left side
+    >>> xreduce([1, 2, 3], lambda x, y: x + y)
+    6
+    >>> # reduce from left side with initial value
+    >>> xreduce([1, 2, 3], lambda x, y: x + y, initial=1)
+    7
+    >>> # reduce from right side
+    >>> xreduce([[0, 1], [2, 3], [4, 5]], lambda x, y: x + y, invert=True)
+    [4, 5, 2, 3, 0, 1]
+    >>> # reduce from right side with initial value
+    >>> xreduce(
+    ...   [[0, 1], [2, 3], [4, 5]], lambda x, y: x + y, [0, 0], invert=True
+    ... )
+    [4, 5, 2, 3, 0, 1, 0, 0]
+    '''
+    if invert:
+        if initial is None:
+            return reduce(partial(lambda f, x, y: f(y, x), callable), iterable)
+        return reduce(partial(
+            lambda f, x, y: f(y, x), callable), iterable, initial,
+        )
+    if initial is None:
+        return reduce(callable, iterable)
+    return reduce(callable, iterable, initial)

File blade/xslice.py

+# -*- coding: utf-8 -*-
+''':class:`blade` slicing operations'''
+
+from random import randrange
+from collections import deque
+from functools import partial
+from itertools import islice, tee
+
+from stuf.six import next
+from stuf.six.moves import zip_longest  # @UnresolvedImport
+from stuf.iterable import deferfunc, deferiter, count, partmap
+
+xslicer = partial(lambda n, i, x, y: n(i(x, y, None)), next, islice)
+
+
+def xat(iterable, n, default=None):
+    '''
+    :term:`Slice` off items in `iterable` found at index `n`.
+
+    :argument iterable: :term:`iterable`
+    :argument int n: index of some items in `iterable`
+    :keyword default: default returned if nothing is found at `n`
+    :return: :term:`iterator` of items
+
+    >>> from blade.xslice import xat
+    >>> # default behavior
+    >>> xat([5, 4, 3, 2, 1], 2)
+    3
+    >>> # return default value if nothing found at index
+    >>> xat([5, 4, 3, 2, 1], 10, 11)
+    11
+    '''
+    return next(islice(iterable, n, None), default)
+
+
+def xchoice(iterable):
+    '''
+    Randomly :term:`slice` off **one** items in `iterable`.
+
+    :argument iterable: :term:`iterable`
+
+    >>> from blade.xslice import xchoice
+    >>> list(xchoice([1, 2, 3, 4, 5, 6])) # doctest: +SKIP
+    3
+    '''
+    i1, i2 = tee(iterable)
+    return xat(i1, randrange(0, count(i2)))
+
+
+def xdice(iterable, n, fill=None):
+    '''
+    :term:`Slice` one `iterable` items in `iterable` into `n` iterable items in
+    `iterable`.
+
+    :argument iterable: :term:`iterable`
+    :argument int n: number of items in `iterable` per slice
+    :keyword fill: value to pad out incomplete iterables
+    :return: :term:`iterator` of items
+
+    >>> from blade.xslice import xdice
+    >>> list(xdice(['moe', 'larry', 'curly', 30, 40, 50, True], 2, 'x'))
+    [('moe', 'larry'), ('curly', 30), (40, 50), (True, 'x')]
+    '''
+    return zip_longest(fillvalue=fill, *[iter(iterable)] * n)
+
+
+def xfirst(iterable, n=0):
+    '''
+    :term:`Slice`  off `n` things from the **starting** end of `iterable` or
+    just the **first** items in `iterable`.
+
+    :argument iterable: :term:`iterable`
+    :keyword int n: number of items in `iterable`
+    :return: :term:`iterator` of items
+
+    >>> from blade.xslice import xfirst
+    >>> # default behavior
+    >>> list(xfirst([5, 4, 3, 2, 1]))
+    [5]
+    >>> # first things from index 0 to 2
+    >>> list(xfirst([5, 4, 3, 2, 1], 2))
+    [5, 4]
+    '''
+    return islice(iterable, n) if n else deferiter(iter(iterable))
+