jamesmoey
10/16/2013 - 11:43 PM

Extend array collection from Doctrine with operation to perform on all the item in the collection.

Extend array collection from Doctrine with operation to perform on all the item in the collection.

<?php
namespace Collections;

use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\PropertyAccess\PropertyAccess;

class ExtendedArrayCollection extends ArrayCollection
{

    /**
     * Reduce the collection into a single value.
     *
     * @param \Closure $func
     * @param null $initialValue
     * @return mixed
     */
    public function reduce(\Closure $func, $initialValue = null)
    {
        return array_reduce($this->toArray(), $func, $initialValue);
    }

    /**
     * Apply filter chain
     *
     * @var \Closure[] $chain
     * @return ExtendedArrayCollection
     */
    public function applyFilterChain($chain)
    {
        $collection = $this;
        foreach ($chain as $filter) {
            $collection = $collection->filter($filter);
        }
        return $collection;
    }

    /**
     * Get average
     * @param $field
     * @return float
     */
    public function getAverage($field)
    {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $avg = 0;
        if ($this->count() === 0) return 0;
        $this->forAll(function ($index, $element) use (&$avg, $accessor, $field) {
            $avg += $accessor->getValue($element, $field);
            return true;
        });
        return $avg / $this->count();
    }

    /**
     * Get median.
     *
     * @param $field
     * @return float
     */
    public function getMedian($field)
    {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $iterator = $this->getSortedIteratorOn($field);
        if ($iterator->count() === 0) return 0;
        if ($iterator->count() % 2 === 0) {
            return (
                $accessor->getValue($iterator[ceil($iterator->count() / 2) - 1], $field) +
                $accessor->getValue($iterator[ceil($iterator->count() / 2)], $field)
            ) / 2;
        } else {
            return $accessor->getValue($iterator[floor($iterator->count() / 2)], $field);
        }
    }

    /**
     * Get absolute median. Make negative into position
     *
     * @param $field
     * @return float
     */
    public function getAbsoluteMean($field) {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $iterator = $this->getSortedAbsoluteIteratorOn($field);
        if ($iterator->count() === 0) return 0;
        if ($iterator->count() % 2 === 0) {
            return (
                abs($accessor->getValue($iterator[ceil($iterator->count() / 2) - 1], $field)) +
                abs($accessor->getValue($iterator[ceil($iterator->count() / 2)], $field))
            ) / 2;
        } else {
            return abs($accessor->getValue($iterator[floor($iterator->count() / 2)], $field));
        }
    }

    public function getSortedAbsoluteIteratorOn($field) {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $iterator = $this->getIterator();
        $iterator->uasort(function ($first, $second) use ($accessor, $field) {
            $firstPrice = abs($accessor->getValue($first, $field));
            $secondPrice = abs($accessor->getValue($second, $field));
            if ($firstPrice == $secondPrice) return 0;
            else return ($firstPrice < $secondPrice ? -1 : 1);
        });
        $list = iterator_to_array($iterator, false);
        return new \ArrayIterator($list);
    }

    /**
     * Get a sorted iterator on a field.
     *
     * @param string $field
     * @return \ArrayIterator
     */
    public function getSortedIteratorOn($field)
    {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $iterator = $this->getIterator();
        $iterator->uasort(function ($first, $second) use ($accessor, $field) {
            $firstPrice = $accessor->getValue($first, $field);
            $secondPrice = $accessor->getValue($second, $field);
            if ($firstPrice == $secondPrice) return 0;
            else return ($firstPrice < $secondPrice ? -1 : 1);
        });
        $list = iterator_to_array($iterator, false);
        return new \ArrayIterator($list);
    }

    /**
     * Return a new collection with sorted result.
     *
     * @param string $field
     * @return PropertyCollection
     */
    public function getSortedCollection($field)
    {
        return new self($this->getSortedIteratorOn($field)->getArrayCopy());
    }

    /**
     * Get standard deviation.
     *
     * @param $field
     * @return float
     */
    public function getStandardDeviation($field)
    {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $average = $this->getAverage($field);
        $variance = 0;
        if ($this->count() === 0) return 0;
        $this->forAll(function ($index, $element) use (&$variance, $average, $accessor, $field) {
            $variance += pow($accessor->getValue($element, $field) - $average, 2);
            return true;
        });
        $variance /= $this->count();
        return sqrt($variance);
    }

    /**
     * Return a new collection excluding the outlier.
     *
     * @param $median
     * @param float $sdRange The Standard Deviation range.
     * @param string $field
     * @return self
     */
    public function excludeOutlier($median, $sdRange, $field)
    {
        $accessor = PropertyAccess::createPropertyAccessorBuilder()->enableMagicCall()->getPropertyAccessor();
        $from = $median / 2;
        $to = $median + $sdRange;
        return $this->filter(function ($e) use ($from, $to, $accessor, $field) {
            $price = 0;
            if ($accessor->getValue($e, $field) !== null) {
                $price = $accessor->getValue($e, $field);
            }
            if ($price >= $from && $price <= $to) return true;
            else return false;
        });
    }
}