<?php
/**
* PHP implementation of Ruby's Enumerable interface
*
* The enum class provides arrays with several traversal and searching methods,
* and also a PHP implementation of Ruby blocks using closures (or callbacks).
*
* @author quantumSoup @ StackOverflow
* @version 0.1a
* @link http://ruby-doc.org/core/classes/Enumerable.html Ruby's documentation on enumerable
*/
class enum {
/**
* @var array the internal array container
*/
var $arr;
private $size;
/**
* Constructor
*
* @param array $array to create enum object with
*/
function __construct(array $array=array()) {
$this->arr = $array;
$this->size = count($array);
}
/**
* any in collection?
*
* Passes each element of the collection to the given function. The method
* returns true if the function ever returns a value other than false or null.
*
* If the function is not given, an implicit
* function ($v) { return ($v !== null && $v !== false) is added.<br />
* (that is any() will return true only if one of the collection members is not false or null.)
*
* @param function $lambda takes element and returns a boolean
* @return boolean
*/
function any($lambda=null) {
// these differ from PHP's "falsy" values
if (!is_callable($lambda)) {
foreach ($this->arr as $value)
if ($value !== null && $value !== false)
return true;
} else {
foreach ($this->arr as $value)
if (call_user_func($lambda, $value))
return true;
}
return false;
}
/**
* all in collection?
*
* Passes each element of the collection to the given function. The method
* returns true if the function never returns false or null.
*
* If the function is not given, an implicit
* function ($v) { return ($v !== null && $v !== false) is added<br />
* (that is all() will return true only if none of the collection members are false or null.)
*
* @param function $lambda takes an element, returns a bool
* @return boolean
*/
function all($lambda=null) {
// these differ from PHP's "falsy" values
if (!is_callable($lambda)) {
foreach ($this->arr as $value)
if ($value === false || $value === null)
return false;
} else {
foreach ($this->arr as $value)
if (!call_user_func($lambda, $value))
return false;
}
return true;
}
/**
* map elements
*
* Returns a new array with the results of running function once for every element in enum.
*
* @param function $lambda takes an element and returns an element
* @return array
*/
function collect($lambda) {
return array_map($lambda, $this->arr);
}
/**
* alias for collect()
*
* @see collect()
* @return array
*/
function map($lambda) {
return array_map($lambda, $this->arr);
}
/**
* find an element
*
* Passes each entry in enum to function. Returns the first for which function is not false.
* If no object matches, calls $ifnone and returns its result when it is specified, or returns nil
*
* @param function $lambda takes element, returns boolean
* @param function $ifnone function to call if no elements are found
* @return object|null returns null if nothing is found
*/
function detect($lambda, $ifnone=null) {
foreach ($this->arr as $value)
if (call_user_func($lambda, $value))
return $value;
if (is_callable($ifnone))
return call_user_func($ifnone);
return null;
}
/**
* alias for detect()
*
* @see detect()
* @return object|null
*
*/
function find($lambda, $ifnone=null) {
return $this->detect($lambda, $ifnone);
}
/**
* "sliding window"
*
* Iterates the given function for each array of consecutive <$n> elements.
*
* @param int $n
* @param function $lambda takes an element, returns void
*/
function each_cons($n, $lambda) {
$cons = array();
for ($i=0; $i<$this->size-$n; $i++) {
$inner = array();
for ($j=0; $j<$n; $j++)
$inner[$j] = $this->arr[$i+$j];
$cons[$i] = $inner;
}
array_walk($cons, $lambda);
}
/**
* "sliding slicer"
*
* Iterates the given function for each slice of <$n> elements.
*
* @param int $n
* @param function $lambda takes an element, returns void
*/
function each_slice($n, $lambda) {
$slices = array();
for ($i=0; $i<$this->size; $i+=$n) {
$slice = array_slice($this->arr, $i, $n);
$slices[$i] = $slice;
}
array_walk($slices, $lambda);
}
/**
*
* Calls function with two arguments, the item and its index, for each item in enum.
*
* @param int|string $index
* @param mixed $item
* @param function $lambda takes item and index, returns void
*/
function each_with_index($lambda) {
foreach ($this->arr as $index => $item) {
call_user_func($lambda, $item, $index);
}
}
function each($lambda) {
array_walk($this->arr, $lambda);
}
/**
*
* Returns an array containing all elements of enum for which function is not false
*
* @param function $lambda, takes element and returns a boolean
* @return array
*/
function find_all($lambda) {
return array_values(array_filter($this->arr, $lambda));
}
/**
* find all elements
*
* alias for find_all()
*
* @see find_all()
* @return array
*/
function select($lambda) {
return array_values(array_filter($this->arr, $lambda));
}
/**
* Filter using grep
*
* Returns an array of every element in enum for which element matches the pattern.
* If the optional function is supplied, each matching element is passed to it,
* and the function's result is stored in the output array.
*
* @param function $lambda takes and returns an element
* @return array
*/
function grep($pattern, $lambda=null) {
$match = preg_grep($pattern, $this->arr);
if (!is_callable($lambda))
return $match;
else
return array_map($lambda, $match);
}
/**
*
* Returns true if any member of enum equals obj.
*
* @param mixed $obj the object to look for
* @return bool true if found
*/
function member($obj) {
return in_array($obj, $this->arr);
}
/**
* Also known as fold or reduce
*
* Combines the elements of enum by applying the function to an accumulator
* value ($memo) and each element in turn. At each step, $memo is set to the
* value returned by the function. The optional argument lets you supply an initial
* value for $memo. Otherwise it uses the first element of the collection
* as a the initial value (and skips that element while iterating).
*
*
* @param function $lambda takes $memo and element, returns update to $memo
* @param int $initial
* @return int
*/
function inject($lambda, $initial=null) {
if ($initial == null) {
$first = array_shift($this->arr);
$result = array_reduce($this->arr, $lambda, $first);
array_unshift($this->arr, $first);
return $result;
} else {
return array_reduce($this->arr, $lambda, $initial);
}
}
/**
*
* partition into true and false arrays
*
* Returns two arrays, the first containing the elements of enum for which
* the function evaluates to true, the second containing the rest.
*
* @param function $lambda takes element, returns boolean
* @return array array[0] is thet true part
*/
function partition($lambda) {
$true = array();
$false = array();
foreach ($this->arr as $value) {
if (call_user_func($lambda, $value))
$true[] = $value;
else
$false[] = $value;
}
return array($true, $false);
}
/**
*
* Returns an array for all elements of enum for which function is false .
*
* @see find_all()
* @param function $lambda takes element, returns boolean
* @return array
*/
function reject($lambda) {
$result = array();
foreach ($this->arr as $value) {
if (!call_user_func($lambda, $value))
$result[] = $value;
}
return $result;
}
/**
*
* Returns an array containing the items in enum sorted, either according to
* their comparison method, or by using the results of the supplied function.
*
* The function should return -1, 0, or +1 depending on the comparison between a and b
*
* @param function $lambda takes objects $a and $b, returns -1, 0 or +1
* @return array
*/
function sort($lambda=null) {
$sort = $this->arr;
if (is_callable($lambda))
usort($sort, $lambda);
else
sort($sort);
return $sort;
}
/**
*
* Returns the object in enum with the maximum value.
*
* If no function is supplied, uses default comparison method.
*
* @param function $lambda takes objects $a and $b, returns -1, 0 or +1
* @return array
*/
function max($lambda=null) {
if (!is_callable($lambda))
return max($this->arr);
$sorted = $this->sort($lambda);
return end($sorted);
}
/**
*
* Returns the object in enum with the minimum value.
*
* If no function is supplied, uses default comparison method.
*
* @param function $lambda takes objects $a and $b, returns -1, 0 or +1
* @return array
*/
function min($lambda=null) {
if (!is_callable($lambda))
return min($this->arr);
$sorted = $this->sort($lambda);
return current($sorted);
}
/**
*
* Sorts enum using a set of keys generated by mapping the values in enum through the given function.
*
* @param function $lambda takes and returns element
*/
function sort_by($lambda) {
foreach ($this->arr as $value) {
$sort[$value] = call_user_func($lambda, $value);
}
asort($sort);
return array_values(array_flip($sort));
}
/**
* Ruby's handy zip()
*
* Converts any arguments to arrays, then merges elements of enum with
* corresponding elements from each argument. This generates a sequence of
* $size n-element arrays, where n is one more that the count of arguments.
* If the size of any argument is less than $size, null values are supplied.
*
* @param array $arr1, array $arr2...
* @return array
*/
function zip() {
$args = func_get_args();
array_unshift($args, $this->arr);
$max = max(array_map('count', $args));
$zipped = array();
for ($i=0; $i<$max; $i++) {
for ($j=0; $j<count($args); $j++) {
$val = (isset($args[$j][$i])) ? $args[$j][$i] : null;
$zipped[$i][$j] = $val;
}
}
//if (!is_callable($zipped))
return $zipped;
//else
// array_walk($lambda, $zipped);
}
}
?>
Refactorings
No refactoring yet !
bob
August 10, 2010, August 10, 2010 04:58, permalink
if there are duplicate values in the array, your sort_by() output loses the extra ones. also, if the array contains any arrays or objects as elements, they are lost too, with a warning
Aira
April 23, 2011, April 23, 2011 09:43, permalink
IJWTS wow! Why can't I think of tihngs like that?
IJWTS wow! Why can't I think of tihngs like that?
Hi this is my first submission to refactormycode.com :)
This is basically a PHP implementation of Ruby's enumerable; designed to (sort of) use PHP's anonymous functions as Ruby blocks.
Some of the stuff is there for backwards compatibility with versions of PHP that don't support anonymous functions.
More details and usage examples here:
http://left4churr.com/php/PHPRuby/