<?php
/**
    Array functions
    2009--2018 by Sebastian Schleussner
    History:
        2018-04-25    + last
        2015-01-04    refactor
        2012-10-03    + assoc_from_text
        2011-07-26    + sanitize = alias of sanitise
        2010-05-26    + sanitise
        2010-05-20    + implode
        2009-11-04    converted to class
        2009-05-30    created
 */

class Arrays {

/* Get last element.
   end(function()) no longer is valid in PHP 7.0, only end($array) works.
   So... give function() result to end() by an intermediate array.
 */

    
public static function last$a )
    {
        return 
end$a );
    }


/* Exchange first and second dimension of a multidimensional array.
   Or if $selectkey is given, extract the fields of that name from the second dimension.
 */

    
public static function transpose$a$selectkey NULL )
    {
        if ( ! 
is_array($a) || (($s count($a)) == 0) ) return false;
        
$result = array();
        foreach (
$a as $rowkey => $row) {
            if (! 
is_array$row )) return false## invalid data

            
if (empty( $selectkey )) {
                foreach (
$row as $colkey => $value) {
                    
$result[$colkey][$rowkey] = $value;
                }
            } elseif (isset( 
$row[$selectkey] )) {
                
$result[$rowkey] = $row[$selectkey];
            }
        }
        return 
$result;
    }


/* Find the dimensionality of an array (number of dimensions / levels).
   The algorithm assumes that all elements have the same dim'ty,
   which in PHP arrays of course is not guaranteed.
*/

    
public static function dims$a )
    {
        
$result 0;
        while (
is_array($a)) {
            
$result += 1;
            
$a reset($a);
        }
        return 
$result;
    }


/* 1-D "explode": Make an associative array from a text block.
    Arrays::assoc_from_text( $text );
    $text    string
    Input: "key1\tvalue1\n..."
    Output: Array( key1 => value1, ... )
 */

    
public static function assoc_from_text$text )
    {
        
$result = array();
        
$datarows preg_split"/[\r\n]+/"$text );
        foreach (
$datarows as $line) {
            if (empty( 
$line )) continue;

            list(
$key$value) = preg_split"/\s+/"$line);
            
$result[$key] = $value;
        }
        return 
$result;
    }


/* Recursive join/implode.
    Arrays::implode( $glue, $value );
    $glue    string, or array of as many dimensions as $value, where element 0 is the glue for the outermost dimension and so on
*/

    
public static function implode$glue$value$level )
    {
        
$result = array();

        if ( 
is_array($value) && (sizeof($value) > 0) && is_array(current($value)) ) { // Is value an array of sub-arrays?
            
foreach($value as $val) {
                
$result[] = Arrays::implode$glue$val$level );
            }
        } elseif (
is_array($value)) {
            
$result $value;
        } else {
            
$result[] = $value;
        }

        return 
join(
            (
is_array($glue)
                ? (isset(
$glue[$level])
                    ? 
$glue[$level]
                    : 
'')
                : 
$glue),
            
$result
        
);
    }


/* Concatenate the elements of several string arrays.
   All arrays need to be indexed in the same way.
    Arrays::cat ($glue, $array1, $array2, ...);
    $glue    string
*/

    
public static function cat()
    {
        
$args func_get_args();
        
$glue array_shift($args);
        
$array1 array_shift($args);
        foreach (
$args as $array2) {
            foreach (
$array1 as $key => $value) {
                
$array1[$key] = $value $glue $array2[$key];
            }
        }
        return 
$array1;
    }


/* Mathematical functions */

/* Replace non-numeric elements with a custom 'NaN' value.
 */

    
public static function sanitize$x$replacement NULL )
    {
        if (
is_array($x)) {
            foreach (
$x as $key => $value) {
                if ((! 
is_numeric$value )) || is_nan$value )) {
                    
$x[$key] = $replacement;
                } else {
                    
$x[$key] = (float)$value;
                }
            }
        } elseif ((!
is_numeric$x )) || is_nan$x )) {
            
$x $replacement;
        } else {
            
$x = (float)$x;
        }
        return 
$x;
    }

    public static function 
sanitise$x$replacement NULL )
    {
        return 
Arrays::sanitize$x$replacement );
    }


/* Returns the distance from the 1st to 2nd, 2nd to third etc. array element.
   The highest element is set to 0.
 */

    
public static function delta$a )
    {
        if (! 
is_array($a) || (($s count($a)) == 0)) return false;
        
$keys array_keys($a);
        
$result = array();
        for (
$i 0;  $i $s-1;  $i++) {
            
$result[$keys[$i]] = $a[$keys[$i+1]] - $a[$keys[$i]];
        }
        
$result[$keys[$s-1]] = 0;
        return 
$result;
    }


/* Generic mathematical operation */

    
public static function mathop()
    {
        
$na func_num_args();
        switch (
$na) {
            case 
:
            case 
:
                return 
false;
            break;

            case 
:
                
$op func_get_arg(0);
                
$a1 func_get_arg(1);
                switch (
$op) {
                    case 
'+' :    case 'add' :
                    case 
'*' :    case 'mult' :
                        return 
false;
                    break;
                    case 
'-' :    case 'sub' :
                        
$op_func create_function('&$x, $key=null''$x = - $x;');
                    break;
                    case 
'/' :    case 'div' :
                        
$op_func create_function('&$x, $key=null''$x = ($x == 0) ? "x" : (1 / $x);');
                    break;
                    default :
                        
$op_func create_function('&$x, $key=null''$x = '.$op.'($x);');
                }
                if (
is_array($a1)) {
                    
array_walk($a1$op_func);
                } else {
                    
$op_func ($a1);
                }
                return 
$a1;
            break;

            case 
:
                
$op func_get_arg(0);
                switch (
$op) {
                    case 
'+' :    case 'add' :
                        
$op_func create_function('&$x, $key, $y''$x = $x + $y;');
                    break;
                    case 
'-' :    case 'sub' :
                        
$op_func create_function('&$x, $key, $y''$x = $x - $y;');
                    break;
                    case 
'*' :    case 'mult' :
                        
$op_func create_function('&$x, $key, $y''$x = $x * $y;');
                    break;
                    case 
'/' :    case 'div' :
                        
$op_func create_function('&$x, $key, $y''$x = ($y == 0) ? "x" : ($x / $y);');
                    break;
                    case 
'^' :    case '**' :
                        
$op_func create_function('&$x, $key, $y''$x = pow($x, $y);');
                    break;
                    default :
                        
$op_func create_function('&$x, $key, $y''$x = '.$op.'($x, $y);');
                }
                
$a1 func_get_arg(1);
                
$a2 func_get_arg(2);
                
$s1 count($a1);
                
$s2 count($a2);
                if (!(
$s1 && $s2)) return false;
                
$result = array();
                if (
is_array($a1)) {
                    if (! 
is_array($a2)) {
                        
array_walk$a1, @$op_func$a2 );
                        return 
$a1;
                    }
                    if (
$s1 != $s2) {
                        return 
false;
                    }
                    
reset($a2);
                    foreach (
$a1 as $key => $value) {
                        
// ($a2[$i] == 0) && error_log("{$value} / {$a2[$i]} = nan", 0);
                        
@$op_func$a1[$key], nullcurrent($a2) );
                        
next($a2);
                    }
                    return 
$a1;
                } else {
                    if (! 
is_array($a2)) {
                        @
$op_func$a1null$a2 );
                        return 
$a1;
                    }
                    switch (
$op) {
                        case 
'+' :    case 'add' :
                        case 
'*' :    case 'mult' :
                        break;
                        case 
'-' :    case 'sub' :
                            
$op_func create_function'&$x, $key, $y''$x = $y - $x;' );
                        break;
                        case 
'/' :    case 'div' :
                            
$op_func create_function'&$x, $key, $y''$x = ($x == 0) ? "x" : ($y / $x);' );
                        break;
                        case 
'^' :    case '**' :
                            
$op_func create_function'&$x, $key, $y''$x = pow($y, $x);' );
                        break;
                        default :
                            
$op_func create_function'&$x, $key, $y''$x = '.$op.'($y, $x);' );
                    }
                    
array_walk($a2, @$op_func$a1);
                    return 
$a2;
                }
            break;

            default :
                
$op func_get_arg(0);
                
$a1 func_get_arg(1);
                for (
$i 2$i $na; ++$i) {
                    
$a1 self::mathop$op$a1func_get_arg($i) );
                }
                return 
$a1;
        }
    }

    public static function 
add()   {  $args func_get_args();  array_unshift($args'+');   return call_user_func_array(array('self','mathop'), $args);  }
    public static function 
sub()   {  $args func_get_args();  array_unshift($args'-');   return call_user_func_array(array('self','mathop'), $args);  }
    public static function 
mult()  {  $args func_get_args();  array_unshift($args'*');   return call_user_func_array(array('self','mathop'), $args);  }
    public static function 
div()   {  $args func_get_args();  array_unshift($args'/');   return call_user_func_array(array('self','mathop'), $args);  }
    public static function 
pow()   {  $args func_get_args();  array_unshift($args'pow'); return call_user_func_array(array('self','mathop'), $args);  }


/* CUMULATIVE SUM
   $result[0] = $a[0];
   $result[1] = $a[0] + $a[1];
   etc. etc.
 */

    
public static function cumsum$a )
    {
        if (!
is_array($a) || (count($a) == ) ) return false;
        
$result = array();
        
$accum 0;
        foreach (
$a as $key => $value) {
            
$accum $result[$key] = $accum $value;
        }
        return 
$result;
    }


/* CUMULATIVE PRODUCT
   $result[0] = $a[0];
   $result[1] = $a[0] * $a[1];
   etc. etc.
 */

    
public static function cumprod$a )
    {
        if (!
is_array($a) || (count($a) == ) ) return false;
        
$result = array();
        
$accum 1;
        foreach (
$a as $key => $value) {
            
$accum $result[$key] = $accum $value;
        }
        return 
$result;
    }


/* GET EVERY NTH ELEMENT
   Arrays::every_nth( $array, $n, $preserve )
 */

    
public static function every_nth$a$n 1$preserve_keys false )
    {
        if (! 
is_array$a )) return false;
        if (! 
is_numeric$n ) || ($n <= 0)) return false;
        if (
$n == 1) return $a;

        
$result = array();
        if (
$preserve_keys) {
            
$keys array_keys$a );
            for (
$i 0$i count$keys ); $i += $n) {
                
$result$keys[$i] ] = $a$keys[$i] ];
            }
        } else {
            
$a array_values$a );
            for (
$i 0$i count$a ); $i += $n) {
                
$result[] = $a[$i];
            }
        }
        return 
$result;
    }

}