# Tree Functions: Table

By Xah Lee. Date: . Last updated:

Mathematica: Table

Here's the spec for Perl:

Table('exprString', [iMax]) generates a list of iMax copies of value of
eval('exprString'), and returns the reference to the list. i.e.
[eval('exprString'),eval('exprString'),...]

Table('exprString', ['i', iMax]) generates a list of the values by
evaluating 'exprString' when 'i' in the string runs from 1 to iMax.

Table('exprString', ['i', iMin, iMax]) starts with 'i' = iMin.

Table('exprString', ['i', iMin, iMax, iStep]) uses steps iStep. If iStep
is negative, then the role of iMin and iMax are reversed. Inputs such as
[1, -3 , 1] returns bad result.

Table('exprString', ['i', iMin, iMax, iStep], ['j', jMin, jMax, iStep],
... ) gives a array by iterating 'i', 'j' in 'exprString'. For example,
Table('f(i,j)', ['i',1,3], ['j',5,6]) returns [[f(1, 5), f(1, 6)], [f(2,
5), f(2, 6)], [f(3, 5), f(3, 6)]].

In general, Table has the form Table('expressionString', iterator1,
iterator2, ...) where 'expressionString' is a string that will be
evaluated by eval. iterator have one of the following forms [iMax],
['dummyVarString',iMax], ['dummyVarString',iMin, iMax], or
['dummyVarString',iMin, iMax, iStep].

If Table fails, 0 is returned. Table can fail, for example, when the
argument are not appropriate references or the iterator range is bad
such as ['i',5,1].

Example:

Table('q(s)' ,[3]); # returns ['s','s','s']

Table( 'i**2' , ['i', 4]); # returns [1, 4, 9, 16]

Table('[i,j,k]',['i',2],['j',100,200,100],['k',5,6])
# returns [[[[1,100,5],[1,100,6]],[[1,200,5],[1,200,6]]],
#          [[[2,100,5],[2,100,6]],[[2,200,5],[2,200,6]]]]

The first argument of Table function in Mathematica (mma) is a expression. Most other languages cannot have such symbolic expressions. In Perl, a string is chosen instead as the expression, and it is being evaluate later as code. This may not be a practical choice but anyway it's just a exercise. Each other language should choose appropriate design for this emulation...

Solutions:

#! perl

# http://xahlee.org/tree/tree.html
# Xah Lee, 2005-05

#_____ Function _____ _____ _____ _____

=pod

B<Function>

Function(parameterList,'expressionString') returns a function. The
function takes parameters in parameterList and has body
expressionString. parameterList is a reference to a list of strings,
each represents a parameter name. For example: ['i','j']. The return
value of Function is a reference to a function.

Example:

&{Function(['i','j'],'i + j')}(3,4); # returns 7.

=cut

sub Function (\$\$) {
my @parameterList = @{\$_[0]};
my \$expression = \$_[1];

my \$parameterDeclarationString = '(';

foreach my \$parameterString (@parameterList) {
my \$variable = '\$' . \$parameterString;
\$expression =~ s(\$parameterString)(\$variable)g;
\$parameterDeclarationString .= q(\$) . \$parameterString . q(,);
};

chop(\$parameterDeclarationString);
\$parameterDeclarationString = q(my ) . \$parameterDeclarationString . ')' . q(=  @_;);

return eval("sub {\$parameterDeclarationString; return (\$expression);}");
};

#end Function

#_____ Range _____ _____ _____ _____

push (@EXPORT, q(Range));
push (@EXPORT_OK, q(Range));

# this version is written by tilt...@erols.com (Jay Tilton) ,Date: Sun, 15 May 2005 23:33:54 GM
sub Range {
my( \$a1, \$b1, \$dx ) =
@_ == 1 ? (    1, \$_[0], 1) :
@_ == 2 ? (\$_[0], \$_[1], 1) :
@_;
if( \$dx == 0 ) {
warn "Range: increment cannot be zero.";
return;
}
return [map \$a1 + \$dx * \$_, 0..int( (\$b1 - \$a1) / \$dx )];
};

#_____ UniqueString _____ _____ _____ _____

=pod

B<UniqueString>

UniqueString('aString', n) returns a reference to a list of n elements of strings, none of which are in the given string and no two are identical. Each string starts with 'unik' and followed by digits.

Example:

# returns ['unik23946', 'unik14135'].

=cut

# implementation note:
# Dependent functions: (none).

push (@EXPORT, q(UniqueString));
push (@EXPORT_OK, q(UniqueString));

sub UniqueString (\$\$) {
my \$input = \$_[0];
my \$n = \$_[1];

my \$str = 'unik' . int(rand()*10000*\$n);
my @result = ();
for (my \$i = 0; \$i < \$n; \$i++) {
while (\$input =~ m(\$str)) {\$str = 'unik' . int(rand()*10000*\$n);}
\$input .= \$str;
push (@result, \$str);
};
return \@result;
};

#end UniqueString

# _rangeSequence(\$ref_iteratorList) returns a sequence of ranges. For example, _rangeSequence([[-5,10,6],[3],[7,10]]) returns [[-5, 1, 7], [1, 2, 3], [7, 8, 9, 10]].
# Dependent functions: Range.
sub _rangeSequence (\$) {
my \$ref_iteratorList = \$_[0];

my @result;
foreach my \$ref_iterator (@\$ref_iteratorList) {push(@result, Range(@\$ref_iterator))};
return \@result;
};

#_____ Table _____ _____ _____ _____

# implementation note:
# gist: Generate a nested foreach loop, then evaluate this loop to get the result.

# First, get some basic info:

# @parameterList is of the form: ('i','j',...). If non exists, then a unique string is inserted. For example, if input is Table('expr',['i',4],[3]), then @parameterList is ('i','unik293');
# @iteratorList is of the form ([1,3],[2],[-2,7,3],...). It is the iterators without the dummy variable (if exists).
# \$ref_rangeSequence is of the form [[1,...3],[1,...2],...]. It is the iterators expanded.

# Now, generate \$stringToBeEvaluated. It has the following form (sample):

#foreach \$h (0 .. scalar(@{\$ref_rangeSequence->[0]}) -1 ) {
#foreach \$unik5926 (0 .. scalar(@{\$ref_rangeSequence->[1]}) -1 ) {
#foreach \$gg (0 .. scalar(@{\$ref_rangeSequence->[2]}) -1 ) {
#\$resultArray[\$h][\$unik5926][\$gg] = &{Function(\@parameterList,\$exprString)} #(\$ref_rangeSequence->[0]->[\$h],\$ref_rangeSequence->[1]->[\$unik5926],\$ref_rangeSequence->[2]->[\$gg],);
#};};};

# Dependent functions: UniqueString, _rangeSequence, Function.

sub Table (\$;@) {
my \$exprString = shift(@_);
my @iteratorList = @_;

my \$depth = scalar(@iteratorList);
my @parameterList = ();

# set @parameterList and @iteratorList.
foreach my \$ref_iterator (@iteratorList) {
if (scalar(@\$ref_iterator) == 1) { push(@parameterList, \${UniqueString(\$exprString,1)}[0]);}
else { push(@parameterList, shift(@\$ref_iterator));};
};

# Now, @parameterList is of the form ('i','j',...).
# Now, @iteratorList is of the form ([1,3],[2],[-2,7,3],...).

# \$ref_rangeSequence is of the form [[1,...3],[1,...2],...].
my \$ref_rangeSequence = _rangeSequence(\@iteratorList);

my \$stringToBeEvaluated;
# generate a declaration of all the symbols. e.g. 'my (\$i,\$j,...); my @resultArray';
\$stringToBeEvaluated .= 'my (';
foreach my \$variable (@parameterList) {\$stringToBeEvaluated .= '\$' . \$variable . ','};
\$stringToBeEvaluated .= "); \n";
\$stringToBeEvaluated .= 'my @resultArray;' . "\n\n";

#generate the beginning of for loops, \$depth number of times. e.g. 'for \$i (1..10) {'
for my \$i (0 .. \$depth-1) {
\$stringToBeEvaluated .= 'foreach \$' . \$parameterList[\$i] .
' (0 .. scalar(@{\$ref_rangeSequence->[' . \$i . ']}) -1 ) {' . qq(\n);
};

#generate the heart of the loop. e.g. \$array[\$i][\$j]... = f(\$i,\$j,...);
\$stringToBeEvaluated .= '\$resultArray';
foreach my \$variable (@parameterList) {\$stringToBeEvaluated .= '[\$' . \$variable . ']';};
\$stringToBeEvaluated .= ' = &{Function(\@parameterList,\$exprString)} (';
for (my \$i=0; \$i<\$depth; \$i++) {\$stringToBeEvaluated .=  '\$ref_rangeSequence->[' . \$i .
']->[\$' . \$parameterList[\$i] . '],'
};
\$stringToBeEvaluated .= "); \n";

#generate the ending of loops, \$depth number of times. e.g. '};'
\$stringToBeEvaluated .= '};' x \$depth . "\n\n";
\$stringToBeEvaluated .=  'return \@resultArray;';

#       debugging lines:
#       print qq(\\$exprString is: \$exprString\n);
#       print "\@parameterList is: @parameterList\n";
#       foreach my \$ref_iterator (@iteratorList) {print "@\\$ref_iterator is: @\$ref_iterator\n"};
#       dumpValue(\$ref_rangeSequence);
print "\n\n-----------------\n\n\$stringToBeEvaluated------------\n";

#evaluate the stringToBeEvaluated to obtain the array. Return the result.
eval(\$stringToBeEvaluated);
};

#end Table

###########################################
# testing

use Data::Dumper;

print Dumper( Table('"f[i,j,k]"', ['i',3], ['j',3], ['k',3]) );

print 5;

### Python

# -*- coding: utf-8 -*-
# Python

# http://xahlee.org/tree/tree.html
# Xah Lee, 2005-06

# Note the solution here is incomplete/incorrect.
# see http://xahlee.org/tree/Table_notes.html

import math;

def Range(iMin, iMax=None, iStep=None):
if (iMax==None and iStep==None):
return Range(1,iMin)
if iStep==None:
return Range(iMin,iMax,1)
if iMin <= iMax and iStep > 0:
if (isinstance(iStep,int) or isinstance(iStep,long)):
return range( iMin, iMax+1, iStep)
else:
return [ iMin+i*iStep for i in range(int(math.floor((iMax-iMin)/iStep))+1) ]
if iMin >= iMax and iStep < 0:
if (isinstance(iStep,int) or isinstance(iStep,long)):
return range( iMin, iMax-1, iStep)
else:
return [ iMin+i*iStep for i in range(int(math.floor((iMin-iMax)/-iStep))+1) ]
return []

##############

def parti(l,j):
'''parti(l,j) returns l partitioned with j elements per group. If j is not a factor of length of l, then the reminder elements are dropped.
Example: parti([1,2,3,4,5,6],2) returns [[1,2],[3,4],[5,6]]
Example: parti([1,2,3,4,5,6,7],3) returns [[1,2,3],[4,5,6]]'''
n=len(l)/j
r=[] # result list
h=range(j) # temp holder for sublist
for n1 in range(n):
for j1 in range(j):
h[j1]=l[n1*j+j1]
r.append( h[:] )
return r

# Terry Hancock <hanc...@anansispaceworks.com>, 2005-06
def parti2(L, j):
return [L[k*j:(k+1)*j] for k in range(len(L)/j)]

##################

def Table(fun, *itrs):
'''Table(f,[iStart,iEnd,iStep]) returns a list of f applied to the range range(iStart,iEnd,iStep).
Example: Table(f,[3,10,2]) returns [f(3),f(5),f(7),f(9)]
Table(f,[iStart,iEnd,iStep], [jStart,jEnd,jStep], ...) returns a nested list of f(i,j,...) applied thru the iterators.
Example: Table(f,[1,2,1],[2,6,2]) returns [[f(1,2),f(1,4),f(1,6)],[f(2,2),f(2,4),f(2,6)]]'''
dim=len (itrs)
dummies = ['i'+repr(i) for i in Range(0,dim-1)]
i0=[0,1]
i1=[2,3]
i2=[4,'a']

h0=[]
for j0 in i0:
h1=[]
for j1 in i1:
h2=[]
for j2 in i2:
h2.append(f(j0,j1,j2))
h1.append( h2[:] )
h0.append( h1[:] )
return h0

def outer(fun, *lists):
'''outer(f,list1, list2,...) is like Table(f,iterator1,iterator2,...)
except that in Table each iterator is generated by Range(), but in outer
the explicit list is given. For example, Table(f,[1,4,1],[2,6,2]) is
equivalent to outer(f,[1,2,3,4],[2,4,6])'''
numLists=len (lists)
dummies = ['j'+repr(i) for i in Range(0,numLists-1)]
code=''
print dummies
obj=compile(code,'<string>','exec')
result=0
m='''
i0=[0,1]
i1=[2,3]
i2=[4,'a']

h0=[]
for j0 in i0:
h1=[]
for j1 in i1:
h2=[]
for j2 in i2:
h2.append(f(j0,j1,j2))
h1.append( h2[:] )
h0.append( h1[:] )
return h0'''
return result

def f(*a):
return 'f'+repr(a)

print outer(f,[1,3,1],[1,3,1],[1,3,1])

# Table[f[i,j,k],{i,2},{j,3},{k,2}]
#{
#  {
#    {f[1,1,1],f[1,1,2]},
#    {f[1,2,1],f[1,2,2]},
#    {f[1,3,1],f[1,3,2]}
#  },
#  {
#    {f[2,1,1],f[2,1,2]},
#    {f[2,2,1],f[2,2,2]},
#    {f[2,3,1],f[2,3,2]}
#  }
#}

#my (\$i,\$j,\$k,);
#my @resultArray;
#
#foreach \$i (0 .. scalar(@{\$ref_rangeSequence->[0]}) -1 ) {
#foreach \$j (0 .. scalar(@{\$ref_rangeSequence->[1]}) -1 ) {
#foreach \$k (0 .. scalar(@{\$ref_rangeSequence->[2]}) -1 ) {
#\$resultArray[\$i][\$j][\$k] = &{Function(\@parameterList,\$exprString)} (\$ref_rangeSequence->[0]->[\$i],\$ref_rangeSequence->[1]->[\$j],\$ref_rangeSequence->[2]->[\$k],);
#};};};
#
#return \@resultArray;

Some notes on writing this function in Python: Table_notes.html

Scheme version Table_scm.html

If you have a question, put \$5 at patreon and message me.