anders
anders

Reputation: 467

PHP: Counting similar occurrences in multidimensional array

I have a multidimensional array, in which I want to count similar occurrences.

So basically I want this:

[
    [
        'type' => 'frosties',
        'madeby' => 'kelloggs'
    ],
    [
        'type' => 'frosties',
        'madeby' => 'kelloggs'
    ],        
    [
        'type' => 'cornflakes',
        'madeby' => 'kelloggs'
    ]
];

To end out as this:

[
    [
        'type' => 'frosties',
        'madeby' => 'kelloggs',
        'count' => 2
    ],
    [
        'type' => 'cornflakes',
        'madeby' => 'kelloggs',
        'count' => 1
    ]
]

This is what I've come up with so far:

    public function count($array) {
    $newArr = [];

    foreach ($array as $breakfast) {
        if (in_array($breakfast['type'], $newArr) && in_array($breakfast['madeby'], $newArr)) {
            //what goes here?
            //dosomething['count']++;
        } else {
            $newArr[] = [
                'type'   => $breakfast['type'],
                'madeby' => $breakfast['madeby'],
                'count'  => 0
            ];
        }
    }
    return $newArr;
}

I might have been staring at this for too long, but I just can't seem to come up with what goes inside the if().

Upvotes: 3

Views: 1277

Answers (3)

kaiser
kaiser

Reputation: 22353

To save some memory and make the look up faster, I'd suggest to use an implementation that makes use of the \ArrayAccess interface. The CerialStack keeps two internal arrays: One to hold the stack where we append and another smaller one that just serves a purpose as look up map.

class CerialStack implements \ArrayAccess
{
    /** @var array Container */
    private $stack = [];

    /** @var array Flat map of `type` for look ups */
    private $map = [];

    // Only look up the map
    public function offsetExists( $type )
    {
        return in_array( $type, $this->map );
    }

    // Only looks up the map
    public function offsetGet( $type )
    {
        return $this->offsetExists( $type )
            ? $this->stack[ array_search( $type, $this->map ) ]
            : false;
    }

    // Sets both the map as well as the stack (if the map look up return false)
    // increases the counter if the value exists
    public function offsetSet( $index, $value )
    {
        $type = $value['type'];
        if ( ! $this->offsetGet( $type ) )
        {
            $this->map[] = $type;
            $this->stack[] = array_merge( [ 'count' => 1, ], $value );
        }
        else
        {
            $key = $this->getKey( $type );
            $key and $this->stack[ $key ]['count']++;
        }
    }

    // reduces both the map and the stack
    public function offsetUnset( $type )
    {
        $key = $this->getKey( $type );
        if ( $key )
            unset(
                $this->stack[ $key ],
                $this->map[ $key ]
            );
    }

    private function getKey( $type )
    {
        return array_search( $type, $this->map );
    }

    public function getStack()
    {
        return $this->stack;
    }
}

The Stack class allows pretty fast look ups as well as handling the array as you are used to. No magic or special function calls involved.

Sort by [...]

To sort the stack, I'd suggest to use a \SplMaxHeap implementation as I've written in another answer. Just alter the class slightly and replace ->rating with ['count'] in the custom heap.

Test

Let's test that:

// Test data
$cerials = [
    [
        'type'   => 'frosties',
        'madeby' => 'kelloggs',
    ],
    [
        'type'   => 'frosties',
        'madeby' => 'kelloggs',
    ],
    [
        'madeby' => 'kelloggs',
        'type'   => 'cornflakes',
    ]
];

$it = new \CerialStack;
// Push into stack
foreach ( $cerials as $cerial )
    $it[] = $cerial;
// Dump the stack
var_dump( $it->getStack() );

// Output
array (size=2)
  0 => 
    array (size=3)
      'count' => int 2
      'type' => string 'frosties' (length=8)
      'madeby' => string 'kelloggs' (length=8)
  1 => 
    array (size=3)
      'count' => int 1
      'madeby' => string 'kelloggs' (length=8)
      'type' => string 'cornflakes' (length=10)

Upvotes: 0

motanelu
motanelu

Reputation: 4025

Here you go:

$array = [
    [
        'type' => 'frosties',
        'madeby' => 'kelloggs'
    ],
    [
        'type' => 'frosties',
        'madeby' => 'kelloggs'
    ],
    [
        'type' => 'cornflakes',
        'madeby' => 'kelloggs'
    ]
];

$results = [];

foreach ($array as $pair) {
    //ksort($pair); <- might need ksort here if type and madeby are not in the same order. 
    $key = serialize($pair);
    if (isset($results[$key])) {
        $results[$key]['count']++;
    } else {
        $results[$key] = array_merge($pair, ['count' => 1]);
    }
}

$results = array_values($results);

print_r($results);

Upvotes: 3

amit
amit

Reputation: 874

Try this and change function name to something else:

        <?php
        $array = array(
            array (
            'type' => 'frosties',
            'madeby' => 'kelloggs',
            ),
       array (
            'type' => 'frosties',
            'madeby' => 'kelloggs'
        ),        
        array(
            'type' => 'cornflakes',
            'madeby' => 'kelloggs'
        )
    );
    print_r(counta($array));

    function counta($array) {
        $newArr = [];

        foreach ($array as $breakfast) {
            if(empty($newArr))
            {
                $newArr[] = array (
                  'type' => $breakfast['type'],
                    'madeby' => $breakfast['madeby'],
                    'count' => 1
                );
            }
            else
            {
                foreach($newArr as $k=>$tmp)
                {
                    if($tmp['type']==$breakfast['type'] && $tmp['madeby']==$breakfast['madeby'])
                    {
                        $newArr[$k]['count']=$newArr[$k]['count']+1;
                    }
                    else{
                        $newArr[] = array (
                  'type' => $breakfast['type'],
                    'madeby' => $breakfast['madeby'],
                    'count' => 1
                );
                    }
                }
            }
        }
        return $newArr;
        }
    ?>

Upvotes: -1

Related Questions