6.12 Arrays ===========
· Slices Python-style array slices Appending '[]' to a built-in or user-defined type yields an array. The array element 'i' of an array 'A' can be accessed as 'A[i]'. By default, attempts to access or assign to an array element using a negative index generates an error. Reading an array element with an index beyond the length of the array also generates an error; however, assignment to an element beyond the length of the array causes the array to be resized to accommodate the new element. One can also index an array 'A' with an integer array 'B': the array 'A[B]' is formed by indexing array 'A' with successive elements of array 'B'. A convenient Java-style shorthand exists for iterating over all elements of an array; see array iteration. The declaration real[] A; initializes 'A' to be an empty (zero-length) array. Empty arrays should be distinguished from null arrays. If we say real[] A=null; then 'A' cannot be dereferenced at all (null arrays have no length and cannot be read from or assigned to). Arrays can be explicitly initialized like this: real[] A={0,1,2}; Array assignment in 'Asymptote' does a shallow copy: only the pointer is copied (if one copy if modified, the other will be too). The 'copy' function listed below provides a deep copy of an array. Every array 'A' of type 'T[]' has the virtual members * 'int length', * 'int cyclic', * 'int[] keys', * 'T push(T x)', * 'void append(T[] a)', * 'T pop()', * 'void insert(int i ... T[] x)', * 'void delete(int i, int j=i)', * 'void delete()', and * 'bool initialized(int n)'. The member 'A.length' evaluates to the length of the array. Setting 'A.cyclic=true' signifies that array indices should be reduced modulo the current array length. Reading from or writing to a nonempty cyclic array never leads to out-of-bounds errors or array resizing. The member 'A.keys' evaluates to an array of integers containing the indices of initialized entries in the array in ascending order. Hence, for an array of length 'n' with all entries initialized, 'A.keys' evaluates to '{0,1,...,n-1}'. A new keys array is produced each time 'A.keys' is evaluated. The functions 'A.push' and 'A.append' append their arguments onto the end of the array, while 'A.insert(int i ... T[] x)' inserts 'x' into the array at index 'i'. For convenience 'A.push' returns the pushed item. The function 'A.pop()' pops and returns the last element, while 'A.delete(int i, int j=i)' deletes elements with indices in the range ['i','j'], shifting the position of all higher-indexed elements down. If no arguments are given, 'A.delete()' provides a convenient way of deleting all elements of 'A'. The routine 'A.initialized(int n)' can be used to examine whether the element at index 'n' is initialized. Like all 'Asymptote' functions, 'push', 'append', 'pop', 'insert', 'delete', and 'initialized' can be "pulled off" of the array and used on their own. For example, int[] A={1}; A.push(2); // A now contains {1,2}. A.append(A); // A now contains {1,2,1,2}. int f(int)=A.push; f(3); // A now contains {1,2,1,2,3}. int g()=A.pop; write(g()); // Outputs 3. A.delete(0); // A now contains {2,1,2}. A.delete(0,1); // A now contains {2}. A.insert(1,3); // A now contains {2,3}. A.insert(1 ... A); // A now contains {2,2,3,3} A.insert(2,4,5); // A now contains {2,2,4,5,3,3}. The '[]' suffix can also appear after the variable name; this is sometimes convenient for declaring a list of variables and arrays of the same type: real a,A[]; This declares 'a' to be 'real' and implicitly declares 'A' to be of type 'real[]'. In the following list of built-in array functions, 'T' represents a generic type. Note that the internal functions 'alias', 'array', 'copy', 'concat', 'sequence', 'map', and 'transpose', which depend on type 'T[]', are defined only after the first declaration of a variable of type 'T[]'. 'new T[]' returns a new empty array of type 'T[]'; 'new T[] {list}' returns a new array of type 'T[]' initialized with 'list' (a comma delimited list of elements). 'new T[n]' returns a new array of 'n' elements of type 'T[]'. These 'n' array elements are not initialized unless they are arrays themselves (in which case they are each initialized to empty arrays). 'T[] array(int n, T value, int depth=intMax)' returns an array consisting of 'n' copies of 'value'. If 'value' is itself an array, a deep copy of 'value' is made for each entry. If 'depth' is specified, this deep copying only recurses to the specified number of levels. 'int[] sequence(int n)' if 'n >= 1' returns the array '{0,1,...,n-1}' (otherwise returns a null array); 'int[] sequence(int n, int m)' if 'm >= n' returns an array '{n,n+1,...,m}' (otherwise returns a null array); 'T[] sequence(T f(int), int n)' if 'n >= 1' returns the sequence '{f_i :i=0,1,...n-1}' given a function 'T f(int)' and integer 'int n' (otherwise returns a null array); 'T[] map(T f(T), T[] a)' returns the array obtained by applying the function 'f' to each element of the array 'a'. This is equivalent to 'sequence(new T(int i) {return f(a[i]);},a.length)'. 'int[] reverse(int n)' if 'n >= 1' returns the array '{n-1,n-2,...,0}' (otherwise returns a null array); 'int[] complement(int[] a, int n)' returns the complement of the integer array 'a' in '{0,1,2,...,n-1}', so that 'b[complement(a,b.length)]' yields the complement of 'b[a]'. 'real[] uniform(real a, real b, int n)' if 'n >= 1' returns a uniform partition of '[a,b]' into 'n' subintervals (otherwise returns a null array); 'int find(bool[], int n=1)' returns the index of the 'n'th 'true' value or -1 if not found. If 'n' is negative, search backwards from the end of the array for the '-n'th value; 'int search(T[] a, T key)' For built-in ordered types 'T', searches a sorted array 'a' of 'n' elements for k, returning the index 'i' if 'a[i] <= key < a[i+1]', '-1' if 'key' is less than all elements of 'a', or 'n-1' if 'key' is greater than or equal to the last element of 'a'. 'int search(T[] a, T key, bool less(T i, T j))' searches an array 'a' sorted in ascending order such that element 'i' precedes element 'j' if 'less(i,j)' is true; 'T[] copy(T[] a)' returns a deep copy of the array 'a'; 'T[] concat(... T[][] a)' returns a new array formed by concatenating the given one-dimensional arrays given as arguments; 'bool alias(T[] a, T[] b)' returns 'true' if the arrays 'a' and 'b' are identical; 'T[] sort(T[] a)' For built-in ordered types 'T', returns a copy of 'a' sorted in ascending order; 'T[][] sort(T[][] a)' For built-in ordered types 'T', returns a copy of 'a' with the rows sorted by the first column, breaking ties with successively higher columns. For example: string[][] a={{"bob","9"},{"alice","5"},{"pete","7"}, {"alice","4"}}; // Row sort (by column 0, using column 1 to break ties): write(sort(a)); produces alice 4 alice 5 bob 9 pete 7 'T[] sort(T[] a, bool less(T i, T j))' returns a copy of 'a' stably sorted in ascending order such that element 'i' precedes element 'j' if 'less(i,j)' is true. 'T[][] transpose(T[][] a)' returns the transpose of 'a'. 'T[][][] transpose(T[][][] a, int[] perm)' returns the 3D transpose of 'a' obtained by applying the permutation 'perm' of 'new int[]{0,1,2}' to the indices of each entry. 'T sum(T[] a)' For arithmetic types 'T', returns the sum of 'a'. In the case where 'T' is 'bool', the number of true elements in 'a' is returned. 'T min(T[] a)' 'T min(T[][] a)' 'T min(T[][][] a)' For built-in ordered types 'T', returns the minimum element of 'a'. 'T max(T[] a)' 'T max(T[][] a)' 'T max(T[][][] a)' For built-in ordered types 'T', returns the maximum element of 'a'. 'T[] min(T[] a, T[] b)' For built-in ordered types 'T', and arrays 'a' and 'b' of the same length, returns an array composed of the minimum of the corresponding elements of 'a' and 'b'. 'T[] max(T[] a, T[] b)' For built-in ordered types 'T', and arrays 'a' and 'b' of the same length, returns an array composed of the maximum of the corresponding elements of 'a' and 'b'. 'pair[] pairs(real[] x, real[] y);' For arrays 'x' and 'y' of the same length, returns the pair array 'sequence(new pair(int i) {return (x[i],y[i]);},x.length)'. 'pair[] fft(pair[] a, int sign=1)' returns the unnormalized Fast Fourier Transform of 'a' (if the optional 'FFTW' package is installed), using the given 'sign'. Here is a simple example: int n=4; pair[] f=sequence(n); write(f); pair[] g=fft(f,-1); write(); write(g); f=fft(g,1); write(); write(f/n); 'real dot(real[] a, real[] b)' returns the dot product of the vectors 'a' and 'b'. 'pair dot(pair[] a, pair[] b)' returns the complex dot product 'sum(a*conj(b))' of the vectors 'a' and 'b'. 'real[] tridiagonal(real[] a, real[] b, real[] c, real[] f);' Solve the periodic tridiagonal problem L'x'='f' and return the solution 'x', where 'f' is an n vector and L is the n \times n matrix [ b[0] c[0] a[0] ] [ a[1] b[1] c[1] ] [ a[2] b[2] c[2] ] [ ... ] [ c[n-1] a[n-1] b[n-1] ] For Dirichlet boundary conditions (denoted here by 'u[-1]' and 'u[n]'), replace 'f[0]' by 'f[0]-a[0]u[-1]' and 'f[n-1]-c[n-1]u[n]'; then set 'a[0]=c[n-1]=0'. 'real[] solve(real[][] a, real[] b, bool warn=true)' Solve the linear equation 'a'x='b' by LU decomposition and return the solution x, where 'a' is an n \times n matrix and 'b' is an array of length n. For example: import math; real[][] a={{1,-2,3,0},{4,-5,6,2},{-7,-8,10,5},{1,50,1,-2}}; real[] b={7,19,33,3}; real[] x=solve(a,b); write(a); write(); write(b); write(); write(x); write(); write(a*x); If 'a' is a singular matrix and 'warn' is 'false', return an empty array. If the matrix 'a' is tridiagonal, the routine 'tridiagonal' provides a more efficient algorithm (tridiagonal). 'real[][] solve(real[][] a, real[][] b, bool warn=true)' Solve the linear equation 'a'x='b' and return the solution x, where 'a' is an n \times n matrix and 'b' is an n \times m matrix. If 'a' is a singular matrix and 'warn' is 'false', return an empty matrix. 'real[][] identity(int n);' returns the n \times n identity matrix. 'real[][] diagonal(... real[] a)' returns the diagonal matrix with diagonal entries given by a. 'real[][] inverse(real[][] a)' returns the inverse of a square matrix 'a'. 'real[] quadraticroots(real a, real b, real c);' This numerically robust solver returns the real roots of the quadratic equation ax^2+bx+c=0, in ascending order. Multiple roots are listed separately. 'pair[] quadraticroots(explicit pair a, explicit pair b, explicit pair c);' This numerically robust solver returns the complex roots of the quadratic equation ax^2+bx+c=0. 'real[] cubicroots(real a, real b, real c, real d);' This numerically robust solver returns the real roots of the cubic equation ax^3+bx^2+cx+d=0. Multiple roots are listed separately. 'Asymptote' includes a full set of vectorized array instructions for arithmetic (including self) and logical operations. These element-by-element instructions are implemented in C++ code for speed. Given real[] a={1,2}; real[] b={3,2}; then 'a == b' and 'a >= 2' both evaluate to the vector '{false, true}'. To test whether all components of 'a' and 'b' agree, use the boolean function 'all(a == b)'. One can also use conditionals like '(a >= 2) ? a : b', which returns the array '{3,2}', or 'write((a >= 2) ? a : null', which returns the array '{2}'. All of the standard built-in 'libm' functions of signature 'real(real)' also take a real array as an argument, effectively like an implicit call to 'map'. As with other built-in types, arrays of the basic data types can be read in by assignment. In this example, the code file fin=input("test.txt"); real[] A=fin; reads real values into 'A' until the end-of-file is reached (or an I/O error occurs). The virtual members 'dimension', 'line', 'csv', 'word', and 'read' of a file are useful for reading arrays. For example, if line mode is set with 'file line(bool b=true)', then reading will stop once the end of the line is reached instead: file fin=input("test.txt"); real[] A=fin.line(); Since string reads by default read up to the end of line anyway, line mode normally has no effect on string array reads. However, there is a white-space delimiter mode for reading strings, 'file word(bool b=true)', which causes string reads to respect white-space delimiters, instead of the default end-of-line delimiter: file fin=input("test.txt").line().word(); real[] A=fin; Another useful mode is comma-separated-value mode, 'file csv(bool b=true)', which causes reads to respect comma delimiters: file fin=csv(input("test.txt")); real[] A=fin; To restrict the number of values read, use the 'file dimension(int)' function: file fin=input("test.txt"); real[] A=dimension(fin,10); This reads 10 values into A, unless end-of-file (or end-of-line in line mode) occurs first. Attempting to read beyond the end of the file will produce a runtime error message. Specifying a value of 0 for the integer limit is equivalent to the previous example of reading until end-of-file (or end-of-line in line mode) is encountered. Two- and three-dimensional arrays of the basic data types can be read in like this: file fin=input("test.txt"); real[][] A=fin.dimension(2,3); real[][][] B=fin.dimension(2,3,4); Sometimes the array dimensions are stored with the data as integer fields at the beginning of an array. Such 1, 2, or 3 dimensional arrays can be read in with the virtual member functions 'read(1)', 'read(2)', or 'read(3)', respectively: file fin=input("test.txt"); real[] A=fin.read(1); real[][] B=fin.read(2); real[][][] C=fin.read(3); One, two, and three-dimensional arrays of the basic data types can be output with the functions 'write(file,T[])', 'write(file,T[][])', 'write(file,T[][][])', respectively.