text: # -*- coding: utf-8 -*-
  """Functions that operate on lists and dicts.
  
  .. versionadded:: 1.0.0
  """
  
  from __future__ import absolute_import
  
  from functools import partial
  import random
  
  import pydash as pyd
  
  from .helpers import iteriteratee, iterator, callit, getargcount, NoValue
  from ._compat import cmp_to_key, _cmp
  
  
  __all__ = (
      'at',
      'count_by',
      'every',
      'filter_',
      'find',
      'find_last',
      'flat_map',
      'flat_map_deep',
      'flat_map_depth',
      'for_each',
      'for_each_right',
      'group_by',
      'includes',
      'invoke_map',
      'key_by',
      'map_',
      'nest',
      'order_by',
      'partition',
      'pluck',
      'reduce_',
      'reduce_right',
      'reductions',
      'reductions_right',
      'reject',
      'sample',
      'sample_size',
      'shuffle',
      'size',
      'some',
      'sort_by'
  )
  
  
  def at(collection, *paths):
      """Creates a list of elements from the specified indexes, or keys, of the
      collection. Indexes may be specified as individual arguments or as arrays
      of indexes.
  
      Args:
          collection (list|dict): Collection to iterate over.
          *paths (mixed): The indexes of `collection` to retrieve, specified as
              individual indexes or arrays of indexes.
  
      Returns:
          list: filtered list
  
      Example:
  
          >>> at([1, 2, 3, 4], 0, 2)
          [1, 3]
          >>> at({'a': 1, 'b': 2, 'c': 3, 'd': 4}, 'a', 'c')
          [1, 3]
          >>> at({'a': 1, 'b': 2, 'c': {'d': {'e': 3}}}, 'a', ['c', 'd', 'e'])
          [1, 3]
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.1.0
          Support deep path access.
      """
      return pyd.properties(*paths)(collection)
  
  
  def count_by(collection, iteratee=None):
      """Creates an object composed of keys generated from the results of running
      each element of `collection` through the iteratee.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          dict: Dict containing counts by key.
  
      Example:
  
          >>> results = count_by([1, 2, 1, 2, 3, 4])
          >>> assert results == {1: 2, 2: 2, 3: 1, 4: 1}
          >>> results = count_by(['a', 'A', 'B', 'b'], lambda x: x.lower())
          >>> assert results == {'a': 2, 'b': 2}
          >>> results = count_by({'a': 1, 'b': 1, 'c': 3, 'd': 3})
          >>> assert results == {1: 2, 3: 2}
  
      .. versionadded:: 1.0.0
      """
      ret = {}
  
      for result in iteriteratee(collection, iteratee):
          ret.setdefault(result[0], 0)
          ret[result[0]] += 1
  
      return ret
  
  
  def every(collection, predicate=None):
      """Checks if the predicate returns a truthy value for all elements of a
      collection. The predicate is invoked with three arguments:
      ``(value, index|key, collection)``. If a property name is passed for
      predicate, the created :func:`pluck` style predicate will return the
      property value of the given element. If an object is passed for predicate,
      the created :func:`where` style predicate will return ``True`` for elements
      that have the properties of the given object, else ``False``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          bool: Whether all elements are truthy.
  
      Example:
  
          >>> every([1, True, 'hello'])
          True
          >>> every([1, False, 'hello'])
          False
          >>> every([{'a': 1}, {'a': True}, {'a': 'hello'}], 'a')
          True
          >>> every([{'a': 1}, {'a': False}, {'a': 'hello'}], 'a')
          False
          >>> every([{'a': 1}, {'a': 1}], {'a': 1})
          True
          >>> every([{'a': 1}, {'a': 2}], {'a': 1})
          False
  
      .. versionadded:: 1.0.0
  
      .. versionchanged: TOOD
          Removed alias ``all_``.
      """
      if predicate:
          cbk = pyd.iteratee(predicate)
          collection = [cbk(item) for item in collection]
  
      return all(collection)
  
  
  def filter_(collection, predicate=None):
      """Iterates over elements of a collection, returning a list of all elements
      the predicate returns truthy for.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          list: Filtered list.
  
      Example:
  
          >>> results = filter_([{'a': 1}, {'b': 2}, {'a': 1, 'b': 3}], {'a': 1})
          >>> assert results == [{'a': 1}, {'a': 1, 'b': 3}]
          >>> filter_([1, 2, 3, 4], lambda x: x >= 3)
          [3, 4]
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed alias ``select``.
      """
      return [value
              for is_true, value, _, _ in iteriteratee(collection, predicate)
              if is_true]
  
  
  def find(collection, predicate=None):
      """Iterates over elements of a collection, returning the first element that
      the predicate returns truthy for.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          mixed: First element found or ``None``.
  
      Example:
  
          >>> find([1, 2, 3, 4], lambda x: x >= 3)
          3
          >>> find([{'a': 1}, {'b': 2}, {'a': 1, 'b': 2}], {'a': 1})
          {'a': 1}
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed aliases ``detect`` and ``find_where``.
      """
      search = (collection[key]
                for is_true, _, key, _ in iteriteratee(collection, predicate)
                if is_true)
      return next(search, None)
  
  
  def find_last(collection, predicate=None):
      """This method is like :func:`find` except that it iterates over elements
      of a `collection` from right to left.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          mixed: Last element found or ``None``.
  
      Example:
  
          >>> find_last([1, 2, 3, 4], lambda x: x >= 3)
          4
          >>> results = find_last([{'a': 1}, {'b': 2}, {'a': 1, 'b': 2}],\
                                   {'a': 1})
          >>> assert results == {'a': 1, 'b': 2}
  
      .. versionadded:: 1.0.0
      """
      search = (collection[key]
                for is_true, _, key, _ in iteriteratee(collection,
                                                       predicate,
                                                       reverse=True)
                if is_true)
      return next(search, None)
  
  
  def flat_map(collection, iteratee=None):
      """Creates a flattened list of values by running each element in
      collection thru `iteratee` and flattening the mapped results. The
      `iteratee` is invoked with three arguments:
      ``(value, index|key, collection)``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list: Flattened mapped list.
  
      Example:
  
          >>> duplicate = lambda n: [[n, n]]
          >>> flat_map([1, 2], duplicate)
          [[1, 1], [2, 2]]
  
      .. versionadded:: 4.0.0
      """
      return pyd.flatten(itermap(collection, iteratee=iteratee))
  
  
  def flat_map_deep(collection, iteratee=None):
      """This method is like :func:`flat_map` except that it recursively flattens
      the mapped results.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list: Flattened mapped list.
  
      Example:
  
          >>> duplicate = lambda n: [[n, n]]
          >>> flat_map_deep([1, 2], duplicate)
          [1, 1, 2, 2]
  
      .. versionadded:: 4.0.0
      """
      return pyd.flatten_deep(itermap(collection, iteratee=iteratee))
  
  
  def flat_map_depth(collection, iteratee=None, depth=1):
      """This method is like :func:`flat_map` except that it recursively flattens
      the mapped results up to `depth` times.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list: Flattened mapped list.
  
      Example:
  
          >>> duplicate = lambda n: [[n, n]]
          >>> flat_map_depth([1, 2], duplicate, 1)
          [[1, 1], [2, 2]]
          >>> flat_map_depth([1, 2], duplicate, 2)
          [1, 1, 2, 2]
  
      .. versionadded:: 4.0.0
      """
      return pyd.flatten_depth(itermap(collection, iteratee=iteratee),
                               depth=depth)
  
  
  def for_each(collection, iteratee=None):
      """Iterates over elements of a collection, executing the iteratee for each
      element.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list|dict: `collection`
  
      Example:
  
          >>> results = {}
          >>> def cb(x): results[x] = x ** 2
          >>> for_each([1, 2, 3, 4], cb)
          [1, 2, 3, 4]
          >>> assert results == {1: 1, 2: 4, 3: 9, 4: 16}
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed alias ``each``.
      """
      next((None for ret, _, _, _ in iteriteratee(collection, iteratee)
            if ret is False),
           None)
      return collection
  
  
  def for_each_right(collection, iteratee):
      """This method is like :func:`for_each` except that it iterates over
      elements of a `collection` from right to left.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list|dict: `collection`
  
      Example:
  
          >>> results = {'total': 1}
          >>> def cb(x): results['total'] = x * results['total']
          >>> for_each_right([1, 2, 3, 4], cb)
          [1, 2, 3, 4]
          >>> assert results == {'total': 24}
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed alias ``each_right``.
      """
      next((None for ret, _, _, _ in iteriteratee(collection,
                                                  iteratee,
                                                  reverse=True)
            if ret is False),
           None)
      return collection
  
  
  def group_by(collection, iteratee=None):
      """Creates an object composed of keys generated from the results of running
      each element of a `collection` through the iteratee.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          dict: Results of grouping by `iteratee`.
  
      Example:
  
          >>> results = group_by([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}], 'a')
          >>> assert results == {1: [{'a': 1, 'b': 2}], 3: [{'a': 3, 'b': 4}]}
          >>> results = group_by([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}], {'a': 1})
          >>> assert results == {False: [{'a': 3, 'b': 4}],\
                                 True: [{'a': 1, 'b': 2}]}
  
      .. versionadded:: 1.0.0
      """
      ret = {}
      cbk = pyd.iteratee(iteratee)
  
      for value in collection:
          key = cbk(value)
          ret.setdefault(key, [])
          ret[key].append(value)
  
      return ret
  
  
  def includes(collection, target, from_index=0):
      """Checks if a given value is present in a collection. If `from_index` is
      negative, it is used as the offset from the end of the collection.
  
      Args:
          collection (list|dict): Collection to iterate over.
          target (mixed): Target value to compare to.
          from_index (int, optional): Offset to start search from.
  
      Returns:
          bool: Whether `target` is in `collection`.
  
      Example:
  
          >>> includes([1, 2, 3, 4], 2)
          True
          >>> includes([1, 2, 3, 4], 2, from_index=2)
          False
          >>> includes({'a': 1, 'b': 2, 'c': 3, 'd': 4}, 2)
          True
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Renamed from ``contains`` to ``includes`` and removed alias
          ``include``.
      """
      if isinstance(collection, dict):
          collection = collection.values()
      else:
          # only makes sense to do this if `collection` is not a dict
          collection = collection[from_index:]
  
      return target in collection
  
  
  def invoke_map(collection, path, *args, **kargs):
      """Invokes the method at `path` of each element in `collection`, returning
      a list of the results of each invoked method. Any additional arguments
      are provided to each invoked method. If `path` is a function, it's invoked
      for each element in `collection`.
  
      Args:
          collection (list|dict): Collection to iterate over.
          path (str|func): String path to method to invoke or callable to invoke
              for each element in `collection`.
          args (optional): Arguments to pass to method call.
          kargs (optional): Keyword arguments to pass to method call.
  
      Returns:
          list: List of results of invoking method of each item.
  
      Example:
  
          >>> items = [{'a': [{'b': 1}]}, {'a': [{'c': 2}]}]
          >>> expected = [{'b': 1}.items(), {'c': 2}.items()]
          >>> invoke_map(items, 'a[0].items') == expected
          True
  
      .. versionadded:: 4.0.0
      """
      return map_(collection,
                  lambda item: pyd.invoke(item, path, *args, **kargs))
  
  
  def key_by(collection, iteratee=None):
      """Creates an object composed of keys generated from the results of running
      each element of the collection through the given iteratee.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          dict: Results of indexing by `iteratee`.
  
      Example:
  
          >>> results = key_by([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}], 'a')
          >>> assert results == {1: {'a': 1, 'b': 2}, 3: {'a': 3, 'b': 4}}
  
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Renamed from ``index_by`` to ``key_by``.
      """
      ret = {}
      cbk = pyd.iteratee(iteratee)
  
      for value in collection:
          ret[cbk(value)] = value
  
      return ret
  
  
  def map_(collection, iteratee=None):
      """Creates an array of values by running each element in the collection
      through the iteratee. The iteratee is invoked with three arguments:
      ``(value, index|key, collection)``. If a property name is passed for
      iteratee, the created :func:`pluck` style iteratee will return the property
      value of the given element. If an object is passed for iteratee, the
      created :func:`where` style iteratee will return ``True`` for elements that
      have the properties of the given object, else ``False``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
  
      Returns:
          list: Mapped list.
  
      Example:
  
          >>> map_([1, 2, 3, 4], str)
          ['1', '2', '3', '4']
          >>> map_([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, {'a': 5, 'b': 6}], 'a')
          [1, 3, 5]
          >>> map_([[[0, 1]], [[2, 3]], [[4, 5]]], '0.1')
          [1, 3, 5]
          >>> map_([{'a': {'b': 1}}, {'a': {'b': 2}}], 'a.b')
          [1, 2]
          >>> map_([{'a': {'b': [0, 1]}}, {'a': {'b': [2, 3]}}], 'a.b[1]')
          [1, 3]
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed alias ``collect``.
      """
      return list(itermap(collection, iteratee))
  
  
  def nest(collection, *properties):
      """This method is like :func:`group_by` except that it supports nested
      grouping by multiple string `properties`. If only a single key is given, it
      is like calling ``group_by(collection, prop)``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          *properties (str): Properties to nest by.
  
      Returns:
          dict: Results of nested grouping by `properties`.
  
      Example:
  
          >>> results = nest([{'shape': 'square', 'color': 'red', 'qty': 5},\
                              {'shape': 'square', 'color': 'blue', 'qty': 10},\
                              {'shape': 'square', 'color': 'orange', 'qty': 5},\
                              {'shape': 'circle', 'color': 'yellow', 'qty': 5},\
                              {'shape': 'circle', 'color': 'pink', 'qty': 10},\
                              {'shape': 'oval', 'color': 'purple', 'qty': 5}],\
                             'shape', 'qty')
          >>> expected = {\
              'square': {5: [{'shape': 'square', 'color': 'red', 'qty': 5},\
                             {'shape': 'square', 'color': 'orange', 'qty': 5}],\
                         10: [{'shape': 'square', 'color': 'blue', 'qty': 10}]},\
              'circle': {5: [{'shape': 'circle', 'color': 'yellow', 'qty': 5}],\
                         10: [{'shape': 'circle', 'color': 'pink', 'qty': 10}]},\
              'oval': {5: [{'shape': 'oval', 'color': 'purple', 'qty': 5}]}}
          >>> results == expected
          True
  
      .. versionadded:: 4.3.0
      """
      if not properties:
          return collection
  
      properties = pyd.flatten(properties)
      first, rest = properties[0], properties[1:]
  
      return pyd.map_values(group_by(collection, first),
                            lambda value: nest(value, *rest))
  
  
  def order_by(collection, keys, orders=None, reverse=False):
      """This method is like :func:`sort_by` except that it sorts by key names
      instead of an iteratee function. Keys can be sorted in descending order by
      prepending a ``"-"`` to the key name (e.g. ``"name"`` would become
      ``"-name"``) or by passing a list of boolean sort options via `orders`
      where ``True`` is ascending and ``False`` is descending.
  
      Args:
          collection (list|dict): Collection to iterate over.
          keys (list): List of keys to sort by. By default, keys will be sorted
              in ascending order. To sort a key in descending order, prepend a
              ``"-"`` to the key name. For example, to sort the key value for
              ``"name"`` in descending order, use ``"-name"``.
          orders (list, optional): List of boolean sort orders to apply for each
              key. ``True`` corresponds to ascending order while ``False`` is
              descending. Defaults to ``None``.
          reverse (bool, optional): Whether to reverse the sort. Defaults to
              ``False``.
  
      Returns:
          list: Sorted list.
  
      Example:
  
          >>> items = [{'a': 2, 'b': 1}, {'a': 3, 'b': 2}, {'a': 1, 'b': 3}]
          >>> results = order_by(items, ['b', 'a'])
          >>> assert results == [{'a': 2, 'b': 1},\
                                 {'a': 3, 'b': 2},\
                                 {'a': 1, 'b': 3}]
          >>> results = order_by(items, ['a', 'b'])
          >>> assert results == [{'a': 1, 'b': 3},\
                                 {'a': 2, 'b': 1},\
                                 {'a': 3, 'b': 2}]
          >>> results = order_by(items, ['-a', 'b'])
          >>> assert results == [{'a': 3, 'b': 2},\
                                 {'a': 2, 'b': 1},\
                                 {'a': 1, 'b': 3}]
          >>> results = order_by(items, ['a', 'b'], [False, True])
          >>> assert results == [{'a': 3, 'b': 2},\
                                 {'a': 2, 'b': 1},\
                                 {'a': 1, 'b': 3}]
  
      .. versionadded:: 3.0.0
  
      .. versionchanged:: 3.2.0
          Added `orders` argument.
  
      .. versionchanged:: 3.2.0
          Added :func:`sort_by_order` as alias.
  
      .. versionchanged:: 4.0.0
          Renamed from ``order_by`` to ``order_by`` and removed alias
          ``sort_by_order``.
      """
      if isinstance(collection, dict):
          collection = collection.values()
  
      # Maintain backwards compatibility.
      if pyd.is_boolean(orders):
          reverse = orders
          orders = None
  
      comparers = []
  
      if orders:
          for i, key in enumerate(keys):
              if pyd.has(orders, i):
                  order = 1 if orders[i] else -1
              else:
                  order = 1
  
              comparers.append((pyd.property_(key), order))
      else:
          for key in keys:
              if key.startswith('-'):
                  order = -1
                  key = key[1:]
              else:
                  order = 1
  
              comparers.append((pyd.property_(key), order))
  
      def comparison(left, right):
          # pylint: disable=useless-else-on-loop,missing-docstring
          for func, mult in comparers:
              result = _cmp(func(left), func(right))
              if result:
                  return mult * result
          else:
              return 0
  
      return sorted(collection, key=cmp_to_key(comparison), reverse=reverse)
  
  
  def partition(collection, predicate=None):
      """Creates an array of elements split into two groups, the first of which
      contains elements the `predicate` returns truthy for, while the second of
      which contains elements the `predicate` returns falsey for. The `predicate`
      is invoked with three arguments: ``(value, index|key, collection)``.
  
      If a property name is provided for `predicate` the created :func:`pluck`
      style predicate returns the property value of the given element.
  
      If an object is provided for `predicate` the created :func:`where` style
      predicate returns ``True`` for elements that have the properties of the
      given object, else ``False``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          list: List of grouped elements.
  
      Example:
  
          >>> partition([1, 2, 3, 4], lambda x: x >= 3)
          [[3, 4], [1, 2]]
  
      .. versionadded:: 1.1.0
      """
      trues = []
      falses = []
  
      for is_true, value, _, _ in iteriteratee(collection, predicate):
          if is_true:
              trues.append(value)
          else:
              falses.append(value)
  
      return [trues, falses]
  
  
  def pluck(collection, path):
      """Retrieves the value of a specified property from all elements in the
      collection.
  
      Args:
          collection (list): List of dicts.
          path (str|list): Collection's path to pluck
  
      Returns:
          list: Plucked list.
  
      Example:
  
          >>> pluck([{'a': 1, 'b': 2}, {'a': 3, 'b': 4}, {'a': 5, 'b': 6}], 'a')
          [1, 3, 5]
          >>> pluck([[[0, 1]], [[2, 3]], [[4, 5]]], '0.1')
          [1, 3, 5]
          >>> pluck([{'a': {'b': 1}}, {'a': {'b': 2}}], 'a.b')
          [1, 2]
          >>> pluck([{'a': {'b': [0, 1]}}, {'a': {'b': [2, 3]}}], 'a.b.1')
          [1, 3]
          >>> pluck([{'a': {'b': [0, 1]}}, {'a': {'b': [2, 3]}}], ['a', 'b', 1])
          [1, 3]
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Function removed.
  
      .. versionchanged:: 4.0.1
          Made property access deep.
      """
      return map_(collection, pyd.property_(path))
  
  
  def reduce_(collection, iteratee=None, accumulator=None):
      """Reduces a collection to a value which is the accumulated result of
      running each element in the collection through the iteratee, where each
      successive iteratee execution consumes the return value of the previous
      execution.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed): Iteratee applied per iteration.
          accumulator (mixed, optional): Initial value of aggregator. Default is
              to use the result of the first iteration.
  
      Returns:
          mixed: Accumulator object containing results of reduction.
  
      Example:
  
          >>> reduce_([1, 2, 3, 4], lambda total, x: total * x)
          24
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed aliases ``foldl`` and ``inject``.
      """
      iterable = iterator(collection)
  
      if accumulator is None:
          try:
              _, accumulator = next(iterable)
          except StopIteration:
              raise TypeError(
                  'reduce_() of empty sequence with no initial value')
  
      result = accumulator
  
      if iteratee is None:
          iteratee = pyd.identity
  
      argcount = getargcount(iteratee, maxargs=3)
  
      for index, item in iterable:
          result = callit(iteratee, result, item, index, argcount=argcount)
  
      return result
  
  
  def reduce_right(collection, iteratee=None, accumulator=None):
      """This method is like :func:`reduce_` except that it iterates over
      elements of a `collection` from right to left.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed): Iteratee applied per iteration.
          accumulator (mixed, optional): Initial value of aggregator. Default is
              to use the result of the first iteration.
  
      Returns:
          mixed: Accumulator object containing results of reduction.
  
      Example:
  
          >>> reduce_right([1, 2, 3, 4], lambda total, x: total ** x)
          4096
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 3.2.1
          Fix bug where collection was not reversed correctly.
  
      .. versionchanged:: 4.0.0
          Removed alias ``foldr``.
      """
      if not isinstance(collection, dict):
          collection = list(collection)[::-1]
  
      return reduce_(collection, iteratee, accumulator)
  
  
  def reductions(collection, iteratee=None, accumulator=None, from_right=False):
      """This function is like :func:`reduce_` except that it returns a list of
      every intermediate value in the reduction operation.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed): Iteratee applied per iteration.
          accumulator (mixed, optional): Initial value of aggregator. Default is
              to use the result of the first iteration.
  
      Returns:
          list: Results of each reduction operation.
  
      Example:
  
          >>> reductions([1, 2, 3, 4], lambda total, x: total * x)
          [2, 6, 24]
  
      Note:
          The last element of the returned list would be the result of using
          :func:`reduce_`.
  
      .. versionadded:: 2.0.0
      """
      if iteratee is None:
          iteratee = pyd.identity
  
      results = []
  
      argcount = getargcount(iteratee, maxargs=3)
  
      def interceptor(result, item, index):
          result = callit(iteratee, result, item, index, argcount=argcount)
          results.append(result)
          return result
  
      reducer = reduce_right if from_right else reduce_
      reducer(collection, interceptor, accumulator)
  
      return results
  
  
  def reductions_right(collection, iteratee=None, accumulator=None):
      """This method is like :func:`reductions` except that it iterates over
      elements of a `collection` from right to left.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed): Iteratee applied per iteration.
          accumulator (mixed, optional): Initial value of aggregator. Default is
              to use the result of the first iteration.
  
      Returns:
          list: Results of each reduction operation.
  
      Example:
  
          >>> reductions_right([1, 2, 3, 4], lambda total, x: total ** x)
          [64, 4096, 4096]
  
      Note:
          The last element of the returned list would be the result of using
          :func:`reduce_`.
  
      .. versionadded:: 2.0.0
      """
      return reductions(collection, iteratee, accumulator, from_right=True)
  
  
  def reject(collection, predicate=None):
      """The opposite of :func:`filter_` this method returns the elements of a
      collection that the predicate does **not** return truthy for.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicate (mixed, optional): Predicate applied per iteration.
  
      Returns:
          list: Rejected elements of `collection`.
  
      Example:
  
          >>> reject([1, 2, 3, 4], lambda x: x >= 3)
          [1, 2]
          >>> reject([{'a': 0}, {'a': 1}, {'a': 2}], 'a')
          [{'a': 0}]
          >>> reject([{'a': 0}, {'a': 1}, {'a': 2}], {'a': 1})
          [{'a': 0}, {'a': 2}]
  
      .. versionadded:: 1.0.0
      """
      return [value
              for is_true, value, _, _ in iteriteratee(collection, predicate)
              if not is_true]
  
  
  def sample(collection):
      """Retrieves a random element from a given `collection`.
  
      Args:
          collection (list|dict): Collection to iterate over.
  
      Returns:
          mixed: Random element from the given collection.
  
      Example:
  
          >>> items = [1, 2, 3, 4, 5]
          >>> results = sample(items)
          >>> assert results in items
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Moved multiple samples functionality to :func:`sample_size`. This
          function now only returns a single random sample.
      """
      return random.choice(collection)
  
  
  def sample_size(collection, n=None):
      """Retrieves list of `n` random elements from a collection.
  
      Args:
          collection (list|dict): Collection to iterate over.
          n (int, optional): Number of random samples to return.
  
      Returns:
          list: List of `n` sampled collection values.
  
      Examples:
  
          >>> items = [1, 2, 3, 4, 5]
          >>> results = sample_size(items, 2)
          >>> assert len(results) == 2
          >>> assert set(items).intersection(results) == set(results)
  
      .. versionadded:: 4.0.0
      """
      num = min(n or 1, len(collection))
      return random.sample(collection, num)
  
  
  def shuffle(collection):
      """Creates a list of shuffled values, using a version of the Fisher-Yates
      shuffle.
  
      Args:
          collection (list|dict): Collection to iterate over.
  
      Returns:
          list: Shuffled list of values.
  
      Example:
  
          >>> items = [1, 2, 3, 4]
          >>> results = shuffle(items)
          >>> assert len(results) == len(items)
          >>> assert set(results) == set(items)
  
      .. versionadded:: 1.0.0
      """
      if isinstance(collection, dict):
          collection = collection.values()
  
      # Make copy of collection since random.shuffle works on list in-place.
      collection = list(collection)
  
      # NOTE: random.shuffle uses Fisher-Yates.
      random.shuffle(collection)
  
      return collection
  
  
  def size(collection):
      """Gets the size of the `collection` by returning `len(collection)` for
      iterable objects.
  
      Args:
          collection (list|dict): Collection to iterate over.
  
      Returns:
          int: Collection length.
  
      Example:
  
          >>> size([1, 2, 3, 4])
          4
  
      .. versionadded:: 1.0.0
      """
      return len(collection)
  
  
  def some(collection, predicate=None):
      """Checks if the predicate returns a truthy value for any element of a
      collection. The predicate is invoked with three arguments:
      ``(value, index|key, collection)``. If a property name is passed for
      predicate, the created :func:`map_` style predicate will return the
      property value of the given element. If an object is passed for predicate,
      the created :func:`where` style predicate will return ``True`` for elements
      that have the properties of the given object, else ``False``.
  
      Args:
          collection (list|dict): Collection to iterate over.
          predicateed (mixed, optional): Predicate applied per iteration.
  
      Returns:
          bool: Whether any of the elements are truthy.
  
      Example:
  
          >>> some([False, True, 0])
          True
          >>> some([False, 0, None])
          False
          >>> some([1, 2, 3, 4], lambda x: x >= 3)
          True
          >>> some([1, 2, 3, 4], lambda x: x == 0)
          False
  
      .. versionadded:: 1.0.0
  
      .. versionchanged:: 4.0.0
          Removed alias ``any_``.
      """
  
      if predicate:
          cbk = pyd.iteratee(predicate)
          collection = [cbk(item) for item in collection]
  
      return any(collection)
  
  
  def sort_by(collection, iteratee=None, reverse=False):
      """Creates a list of elements, sorted in ascending order by the results of
      running each element in a `collection` through the iteratee.
  
      Args:
          collection (list|dict): Collection to iterate over.
          iteratee (mixed, optional): Iteratee applied per iteration.
          reverse (bool, optional): Whether to reverse the sort. Defaults to
              ``False``.
  
      Returns:
          list: Sorted list.
  
      Example:
  
          >>> sort_by({'a': 2, 'b': 3, 'c': 1})
          [1, 2, 3]
          >>> sort_by({'a': 2, 'b': 3, 'c': 1}, reverse=True)
          [3, 2, 1]
          >>> sort_by([{'a': 2}, {'a': 3}, {'a': 1}], 'a')
          [{'a': 1}, {'a': 2}, {'a': 3}]
  
      .. versionadded:: 1.0.0
      """
      if isinstance(collection, dict):
          collection = collection.values()
  
      return sorted(collection, key=pyd.iteratee(iteratee), reverse=reverse)
  
  
  #
  # Utility methods not a part of the main API
  #
  
  
  def itermap(collection, iteratee=None):
      """Generative mapper."""
      for result in iteriteratee(collection, iteratee):
          yield result[0]
  