itertools additions: one(), single_valued()

Hi all, I find the following two operations functions useful and general enough that I would like to propose them for addition to itertools: 8< ------------------------------------------------------------------------- def single_valued(iterable): it = iter(iterable) try: first_item = it.next() except StopIteration: raise ValueError, "empty iterable passed to 'single_valued()'" for other_item in it: if other_item != first_item: raise ValueError, "non-single-valued iterable'" return first_item 8< ------------------------------------------------------------------------- This first one embodies the assertion that all values of the iterable are identical. If they are, that value is returned. Otherwise, an exception is thrown. Maybe this should be rephrased such that the assertion part is evaluated via an actual "assert", thereby turning it off in optimized mode. Example use case: You get a list of lists, and expect each to be the same length. Typical treatment: list_len = len(lists[0]) New treatment: list_len = single_valued(len(l) for l in lists) Added benefits: The assumption is verified and clearly visible from the source. "lists" may now be an iterable. 8< ------------------------------------------------------------------------- def one(iterable): it = iter(iterable) try: v = it.next() except StopIteration: raise ValueError, "empty iterable passed to 'one()'" try: v2 = it.next() raise ValueError, "iterable with more than one entry passed to 'one()'" except StopIteration: return v 8< ------------------------------------------------------------------------- This one is a blatant rip-off from SQLAlchemy. It basically allows most searches to be written using generator expressions: what_i_am_looking_for = one(item for item in items if predicate(item)) This also encodes and checks the assumption that the sought item is unique within the list of candidates. Again, the assertion part could be turned off in optimized mode. Opinions? Andreas

On Montag 26 Mai 2008, Boris Borcic wrote:
Opinions?
Andreas
my_target, = iterable my_target, = set(iterable)
quite readably does it, imho, or else one can use
[my_target] = iterable [my_target] = set(iterable)
the error messages seem also OK.
True, thanks for pointing this out. :) I guess the only places where my functions are useful are a) if you don't want the intermediate result in a variable or b) if you can't stomach the storage for the set. Andreas

Andreas Klöckner wrote:
On Montag 26 Mai 2008, Boris Borcic wrote: (...)
[my_target] = iterable [my_target] = set(iterable)
the error messages seem also OK.
True, thanks for pointing this out. :)
I guess the only places where my functions are useful are
a) if you don't want the intermediate result in a variable
On the near side to ugly, one could attempt the expression equivalents: (lambda x:x)(*iterable) (lambda x:x)(*set(iterable))
or b) if you can't stomach the storage for the set.
Yeah, I guess it deserves study. Cheers, BB

[Andreas]
I find the following two operations functions useful and general enough that I would like to propose them for addition to itertools:
No thanks. Variants can already be constructed from existing tools. And, they seem a little to specific to a data model where the first entry has some special significance depending on whether or not it is unique.
it = iter(iterable) try: first_item = it.next() except StopIteration: raise ValueError, "empty iterable passed to 'single_valued()'" for other_item in it: if other_item != first_item: raise ValueError, "non-single-valued iterable'" return first_item
This looks like a set() operation with a couple odd special cases for exceptions. [] --> ValueError If [x] --> x [x x x] --> x [x x y x] --> ValueError The two non-exception cases both run the input iterable to exhaustion and as such do not fit it with the lazy-evaluation theme of the itertools module.
def one(iterable): it = iter(iterable) try: v = it.next() except StopIteration: raise ValueError, "empty iterable passed to 'one()'" try: v2 = it.next() raise ValueError, "iterable with more than one entry passed to 'one()'" except StopIteration: return v
Looks similar to list(islice(iterable,2)) followed by regular list-like handling.
what_i_am_looking_for = one(item for item in items if predicate(item))
Looks similar to: wialf = ifilter(pred, items).next()
This also encodes and checks the assumption that the sought item is unique within the list of candidates. Again, the assertion part could be turned off in optimized mode.
That is an odd assumption given that you're searching for a predicate match and not a single item match. Also, it is often a better design to enforce uniqueness constraints upon insertion, not upon lookup. Raymond

Hi, Andreas Klöckner <lists <at> informa.tiker.net> writes:
list_len = single_valued(len(l) for l in lists) length, = set(map(len, lists))
what_i_am_looking_for = one(item for item in items if predicate(item)) Is that really such a common case? In Python 2.6 you can use next for the same thing however without the check if there is just one item:
what_i_am_looking_for = next(filter(predicate, items)) What I would like to see is a batch function defined like that: def batch(iterable, n): return izip(*repeat(iter(iterable), n)) It's a common pattern I use it very often. Regards, Armin
participants (4)
-
Andreas Klöckner
-
Armin Ronacher
-
Boris Borcic
-
Raymond Hettinger