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

2
  • I'm guessing you want something similar to this: jdl-enterprises.co.uk/sof/25789697.php (in the sense of it having a "count" field) Commented Nov 4, 2014 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. Commented Nov 4, 2014 at 13:00

3 Answers 3

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);
Sign up to request clarification or add additional context in comments.

1 Comment

Although this seems to work perfectly fine with a small array like this, how will it perform if the array contains say 2000 elements?
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)

2 Comments

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 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?
-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;
        }
    ?>

1 Comment

This is potentially really really really really really slow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.