Commits

Anonymous committed 6f60fd0

iter_util: add DefaultRaise, first, last, offset

Comments (0)

Files changed (1)

python/iter_util.py

   return max_positions(iterable, key, not reverse)
 
 
+class DefaultRaise(object):
+  """Sentinel for raising by default.
+
+  Each module that needs a default raise action can instantiate this class and test for identity against that object.  Instances should not be used as values.
+  """
+  def __repr__(self):
+    return "<raise>"
+
+default_raise = DefaultRaise()
+
+
+def first(iterable, default=default_raise):
+  """Return first item in iterable, or default.
+
+  Raises ValueError if iterable has no items and default not specified.
+
+  >>> first("abc")
+  'a'
+  >>> first("", "x")
+  'x'
+  >>> first("")
+  Traceback (most recent call last):
+  ValueError: first() arg is an empty sequence
+  """
+  for x in iterable:
+    return x
+  if default is not default_raise:
+    return default
+  # exception message copied from max([])
+  raise ValueError("first() arg is an empty sequence")
+
+def last(iterable, default=default_raise):
+  """Return last item in iterable, or default.
+
+  Raises ValueError if iterable has no items and default not specified.
+
+  >>> last("abc")
+  'c'
+  >>> last("", "x")
+  'x'
+  >>> last("")
+  Traceback (most recent call last):
+  ValueError: last() arg is an empty sequence
+  """
+  try:
+    return offset(iterable, -1, default)
+  except ValueError:  # change exception message
+    # exception message copied from max([])
+    raise ValueError("last() arg is an empty sequence")
+
+  # previous implementation, before falling back to offset()
+  #it = iter(iterable)
+  #for x in it:
+  #  break
+  #else:
+  #  if default is not default_raise:
+  #    return default
+  #  # exception message copied from max([])
+  #  raise ValueError("last() arg is an empty sequence")
+  #for x in it:
+  #  pass
+  #return x
+
+def offset(iterable, offset, default=default_raise):
+  """Return item at given offset in iterable, or default.
+
+  Raises ValueError if iterable has no items and default not specified.
+
+  >>> offset("abc", 0)
+  'a'
+  >>> offset("abc", -1)
+  'c'
+  >>> offset("abc", 2)
+  'c'
+  >>> offset("abc", 3, "x")
+  'x'
+  >>> offset("", 0, "x")
+  'x'
+  >>> offset("", 0)
+  Traceback (most recent call last):
+  ValueError: offset() arg is too short
+
+
+  Tests to accommodate micro-optimization:
+  >>> offset("abc", 3)
+  Traceback (most recent call last):
+  ValueError: offset() arg is too short
+  >>> offset("abc", 3, "x")
+  'x'
+
+  >>> offset("abc", -4)
+  Traceback (most recent call last):
+  ValueError: offset() arg is too short
+  >>> offset(iter("abc"), -4, "x")
+  'x'
+
+  >>> offset(iter("abc"), -1)
+  'c'
+  >>> offset(iter("abc"), -2)
+  'b'
+  >>> offset(iter("abc"), -3)
+  'a'
+  >>> offset(iter("abc"), -4)
+  Traceback (most recent call last):
+  ValueError: offset() arg is too short
+  """
+  if isinstance(iterable, (list, tuple, basestring)):  # micro-optimization
+    try:
+      return iterable[offset]
+    except IndexError:
+      if default is not default_raise:
+        return default
+      raise ValueError("offset() arg is too short")
+
+  if offset >= 0:
+    x = next(itertools.islice(iterable, offset, offset+1), default)
+    if x is not default_raise:
+      return x
+    raise ValueError("offset() arg is too short")
+
+  else:
+    it = iter(iterable)
+    saved = list(itertools.islice(it, -offset))
+    if len(saved) != -offset:
+      if default is not default_raise:
+        return default
+      raise ValueError("offset() arg is too short")
+    for x in it:
+      saved.pop(0)
+      saved.append(x)
+    return saved[0]
+
+
 if __name__ == "__main__":
   import doctest
   print doctest.testmod()