diff --git a/.travis.yml b/.travis.yml index bfc5179..c354030 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,9 @@ language: python matrix: include: - - python: 2.6 - python: 2.7 - - python: 3.3 - python: 3.4 + - python: 3.5 - python: "nightly" env: PRE=--pre allow_failures: diff --git a/cycler.py b/cycler.py index 48a2b3f..8fd93ef 100644 --- a/cycler.py +++ b/cycler.py @@ -389,6 +389,74 @@ def simplify(self): trans = self._transpose() return reduce(add, (_cycler(k, v) for k, v in six.iteritems(trans))) + def concat(self, other): + """Concatenate this cycler and an other. + + The keys must match exactly. + + This returns a single Cycler which is equivalent to + `itertools.chain(self, other)` + + Examples + -------- + + >>> num = cycler('a', range(3)) + >>> let = cycler('a', 'abc') + >>> num.concat(let) + cycler('a', [0, 1, 2, 'a', 'b', 'c']) + + Parameters + ---------- + other : `Cycler` + The `Cycler` to concatenate to this one. + + Returns + ------- + ret : `Cycler` + The concatenated `Cycler` + """ + return concat(self, other) + + +def concat(left, right): + """Concatenate two cyclers. + + The keys must match exactly. + + This returns a single Cycler which is equivalent to + `itertools.chain(left, right)` + + Examples + -------- + + >>> num = cycler('a', range(3)) + >>> let = cycler('a', 'abc') + >>> num.concat(let) + cycler('a', [0, 1, 2, 'a', 'b', 'c']) + + Parameters + ---------- + left, right : `Cycler` + The two `Cycler` instances to concatenate + + Returns + ------- + ret : `Cycler` + The concatenated `Cycler` + """ + if left.keys != right.keys: + msg = '\n\t'.join(["Keys do not match:", + "Intersection: {both!r}", + "Disjoint: {just_one!r}" + ]).format( + both=left.keys&right.keys, + just_one=left.keys^right.keys) + + raise ValueError(msg) + + _l = left._transpose() + _r = right._transpose() + return reduce(add, (_cycler(k, _l[k] + _r[k]) for k in left.keys)) def cycler(*args, **kwargs): """ diff --git a/doc/source/index.rst b/doc/source/index.rst index e1a1f6f..ddf21a7 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -180,6 +180,22 @@ Integer Multiplication 2 * color_cycle +Concatenation +~~~~~~~~~~~~~ + +`Cycler` objects can be concatenated either via the :py:meth:`Cycler.concat` method + +.. ipython:: python + + color_cycle.concat(color_cycle) + +or the top-level :py:func:`concat` function + +.. ipython:: python + + from cycler import concat + concat(color_cycle, color_cycle) + Slicing ------- diff --git a/test_cycler.py b/test_cycler.py index 6d93566..d1dc11e 100644 --- a/test_cycler.py +++ b/test_cycler.py @@ -2,10 +2,10 @@ import six from six.moves import zip, range -from cycler import cycler, Cycler +from cycler import cycler, Cycler, concat from nose.tools import (assert_equal, assert_not_equal, assert_raises, assert_true) -from itertools import product, cycle +from itertools import product, cycle, chain from operator import add, iadd, mul, imul @@ -279,3 +279,20 @@ def test_starange_init(): c2 = cycler('lw', range(3)) cy = Cycler(list(c), list(c2), zip) assert_equal(cy, c + c2) + + +def test_concat(): + a = cycler('a', range(3)) + b = cycler('a', 'abc') + for con, chn in zip(a.concat(b), chain(a, b)): + assert_equal(con, chn) + + for con, chn in zip(concat(a, b), chain(a, b)): + assert_equal(con, chn) + + +def test_concat_fail(): + a = cycler('a', range(3)) + b = cycler('b', range(3)) + assert_raises(ValueError, concat, a, b) + assert_raises(ValueError, a.concat, b)