3

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().

Cœur
  • 37,241
  • 25
  • 195
  • 267
anders
  • 457
  • 2
  • 19
  • I'm guessing you want something similar to this: http://jdl-enterprises.co.uk/sof/25789697.php (in the sense of it having a "count" field) – James Lalor Nov 04 '14 at 12:57
  • You are not using in_array correctly as you are checking for a string, but inside your main array you have another level of arrays. You should add the type as the key and compare against that if it is possible. – David Jones Nov 04 '14 at 13:00

3 Answers3

3

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);
OIS
  • 9,833
  • 3
  • 32
  • 41
motanelu
  • 3,945
  • 1
  • 14
  • 21
  • 1
    Although this seems to work perfectly fine with a small array like this, how will it perform if the array contains say 2000 elements? – anders Nov 05 '14 at 09:43
0

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)
Community
  • 1
  • 1
kaiser
  • 21,817
  • 17
  • 90
  • 110
  • How does this save memory or improve speed? It is also incorrect as it ignores the key "madeby". Unlike the accepted answer this with hardcoded "type" key is also limited to the data, while the accepted answer will work with all sorts of arrays which does not have a count key of their own. – OIS Nov 05 '14 at 08:39
  • @OIS Iterators retrieve the data element by element, while an array in a `foreach` loop gets consumed as one. That's why iterators use less memory. About the "incorrect" part: Could you please elaborate? – kaiser Nov 05 '14 at 12:25
-1

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;
        }
    ?>
amit
  • 874
  • 7
  • 16