text: from __future__ import print_function, division
  import functools, itertools
  from sympy.core.sympify import sympify
  from sympy.core.expr import Expr
  from sympy.core import Basic
  from sympy.core.compatibility import Iterable
  from sympy.tensor.array import MutableDenseNDimArray, ImmutableDenseNDimArray
  from sympy import Symbol
  from sympy.core.sympify import sympify
  from sympy.core.numbers import Integer
  
  
  class ArrayComprehension(Basic):
      """
      Generate a list comprehension
      If there is a symbolic dimension, for example, say [i for i in range(1, N)] where
      N is a Symbol, then the expression will not be expanded to an array. Otherwise,
      calling the doit() function will launch the expansion.
  
      Examples
      ========
  
      >>> from sympy.tensor.array import ArrayComprehension
      >>> from sympy import symbols
      >>> i, j, k = symbols('i j k')
      >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
      >>> a
      ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
      >>> a.doit()
      [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
      >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
      >>> b.doit()
      ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k))
      """
      def __new__(cls, function, *symbols, **assumptions):
          if any(len(l) != 3 or None for l in symbols):
              raise ValueError('ArrayComprehension requires values lower and upper bound'
                                ' for the expression')
          arglist = [sympify(function)]
          arglist.extend(cls._check_limits_validity(function, symbols))
          obj = Basic.__new__(cls, *arglist, **assumptions)
          obj._limits = obj._args[1:]
          obj._shape = cls._calculate_shape_from_limits(obj._limits)
          obj._rank = len(obj._shape)
          obj._loop_size = cls._calculate_loop_size(obj._shape)
          return obj
  
      @property
      def function(self):
          """The function applied across limits
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j = symbols('i j')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.function
          10*i + j
          """
          return self._args[0]
  
      @property
      def limits(self):
          """
          The list of limits that will be applied while expanding the array
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j = symbols('i j')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.limits
          ((i, 1, 4), (j, 1, 3))
          """
          return self._limits
  
      @property
      def free_symbols(self):
          """
          The set of the free_symbols in the array
          Variables appeared in the bounds are supposed to be excluded
          from the free symbol set.
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j, k = symbols('i j k')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.free_symbols
          set()
          >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
          >>> b.free_symbols
          {k}
          """
          expr_free_sym = self.function.free_symbols
          for var, inf, sup in self._limits:
              expr_free_sym.discard(var)
              curr_free_syms = inf.free_symbols.union(sup.free_symbols)
              expr_free_sym = expr_free_sym.union(curr_free_syms)
          return expr_free_sym
  
      @property
      def variables(self):
          """The tuples of the variables in the limits
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j, k = symbols('i j k')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.variables
          [i, j]
          """
          return [l[0] for l in self._limits]
  
      @property
      def bound_symbols(self):
          """The list of dummy variables
  
          Note
          ====
  
          Note that all variables are dummy variables since a limit without
          lower bound or upper bound is not accepted.
          """
          return [l[0] for l in self._limits if len(l) != 1]
  
      @property
      def shape(self):
          """
          The shape of the expanded array, which may have symbols
  
          Note
          ====
  
          Both the lower and the upper bounds are included while
          calculating the shape.
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j, k = symbols('i j k')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.shape
          (4, 3)
          >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
          >>> b.shape
          (4, k + 3)
          """
          return self._shape
  
      @property
      def is_shape_numeric(self):
          """
          Test if the array is shape-numeric which means there is no symbolic
          dimension
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j, k = symbols('i j k')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.is_shape_numeric
          True
          >>> b = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, k+3))
          >>> b.is_shape_numeric
          False
          """
          for _, inf, sup in self._limits:
              if Basic(inf, sup).atoms(Symbol):
                  return False
          return True
  
      def rank(self):
          """The rank of the expanded array
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j, k = symbols('i j k')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.rank()
          2
          """
          return self._rank
  
      def __len__(self):
          """
          The length of the expanded array which means the number
          of elements in the array.
  
          Raises
          ======
  
          ValueError : When the length of the array is symbolic
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j = symbols('i j')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> len(a)
          12
          """
          if self._loop_size.free_symbols:
              raise ValueError('Symbolic length is not supported')
          return self._loop_size
  
      @classmethod
      def _check_limits_validity(cls, function, limits):
          limits = sympify(limits)
          for var, inf, sup in limits:
              if any((not isinstance(i, Expr)) or i.atoms(Symbol, Integer) != i.atoms()
                                                                  for i in [inf, sup]):
                  raise TypeError('Bounds should be an Expression(combination of Integer and Symbol)')
              if (inf > sup) == True:
                  raise ValueError('Lower bound should be inferior to upper bound')
              if var in inf.free_symbols or var in sup.free_symbols:
                  raise ValueError('Variable should not be part of its bounds')
          return limits
  
      @classmethod
      def _calculate_shape_from_limits(cls, limits):
          return tuple([sup - inf + 1 for _, inf, sup in limits])
  
      @classmethod
      def _calculate_loop_size(cls, shape):
          if not shape:
              return 0
          loop_size = 1
          for l in shape:
              loop_size = loop_size * l
  
          return loop_size
  
      def doit(self):
          if not self.is_shape_numeric:
              return self
  
          return self._expand_array()
  
      def _expand_array(self):
          res = []
          for values in itertools.product(*[range(inf, sup+1)
                                          for var, inf, sup
                                          in self._limits]):
              res.append(self._get_element(values))
  
          return ImmutableDenseNDimArray(res, self.shape)
  
      def _get_element(self, values):
          temp = self.function
          for var, val in zip(self.variables, values):
              temp = temp.subs(var, val)
          return temp
  
      def tolist(self):
          """Transform the expanded array to a list
  
          Raises
          ======
  
          ValueError : When there is a symbolic dimension
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j = symbols('i j')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.tolist()
          [[11, 12, 13], [21, 22, 23], [31, 32, 33], [41, 42, 43]]
          """
          if self.is_shape_numeric:
              return self._expand_array().tolist()
  
          raise ValueError("A symbolic array cannot be expanded to a list")
  
      def tomatrix(self):
          """Transform the expanded array to a matrix
  
          Raises
          ======
  
          ValueError : When there is a symbolic dimension
          ValueError : When the rank of the expanded array is not equal to 2
  
          Examples
          ========
  
          >>> from sympy.tensor.array import ArrayComprehension
          >>> from sympy import symbols
          >>> i, j = symbols('i j')
          >>> a = ArrayComprehension(10*i + j, (i, 1, 4), (j, 1, 3))
          >>> a.tomatrix()
          Matrix([
          [11, 12, 13],
          [21, 22, 23],
          [31, 32, 33],
          [41, 42, 43]])
          """
          from sympy.matrices import Matrix
  
          if not self.is_shape_numeric:
              raise ValueError("A symbolic array cannot be expanded to a matrix")
          if self._rank != 2:
              raise ValueError('Dimensions must be of size of 2')
  
          return Matrix(self._expand_array().tomatrix())
  
  
  def isLambda(v):
      LAMBDA = lambda: 0
      return isinstance(v, type(LAMBDA)) and v.__name__ == LAMBDA.__name__
  
  class ArrayComprehensionMap(ArrayComprehension):
      '''
      A subclass of ArrayComprehension dedicated to map external function lambda.
  
      Notes
      =====
  
      Only the lambda function is considered.
      At most one argument in lambda function is accepted in order to avoid ambiguity
      in value assignment.
  
      Examples
      ========
  
      >>> from sympy.tensor.array import ArrayComprehensionMap
      >>> from sympy import symbols
      >>> i, j, k = symbols('i j k')
      >>> a = ArrayComprehensionMap(lambda: 1, (i, 1, 4))
      >>> a.doit()
      [1, 1, 1, 1]
      >>> b = ArrayComprehensionMap(lambda a: a+1, (j, 1, 4))
      >>> b.doit()
      [2, 3, 4, 5]
  
      '''
      def __new__(cls, function, *symbols, **assumptions):
          if any(len(l) != 3 or None for l in symbols):
              raise ValueError('ArrayComprehension requires values lower and upper bound'
                                ' for the expression')
  
          if not isLambda(function):
              raise ValueError('Data type not supported')
  
          arglist = cls._check_limits_validity(function, symbols)
          obj = Basic.__new__(cls, *arglist, **assumptions)
          obj._limits = obj._args
          obj._shape = cls._calculate_shape_from_limits(obj._limits)
          obj._rank = len(obj._shape)
          obj._loop_size = cls._calculate_loop_size(obj._shape)
          obj._lambda = function
          return obj
  
      @property
      def func(self):
          class _(ArrayComprehensionMap):
              def __new__(cls, *args, **kwargs):
                  return ArrayComprehensionMap(self._lambda, *args, **kwargs)
          return _
  
      def _get_element(self, values):
          temp = self._lambda
          if self._lambda.__code__.co_argcount == 0:
              temp = temp()
          elif self._lambda.__code__.co_argcount == 1:
              temp = temp(functools.reduce(lambda a, b: a*b, values))
          return temp
  