1
\$\begingroup\$

For a website I'm working on, I had to get all the unique entries in an array and count their occurrence. It is possible a certain entry is only found once, but it can also be found 20 times. So I designed the following bit of code:

for ($i = 0; $i < count($nodes); $i++)
{
    for ($j = 0; $j < count($nodes[$i]); $j++)
    {
        if (!array_key_exists($nodes[$i][$j], $uniekenodes))
        {
            $uniekenodes[$nodes[$i][$j]] = 1;
        }
        else
        {
            $uniekenodes[$nodes[$i][$j]] += 1;
        }
    }
}

The $nodes array contains the the entries returned from the database. And the $uniekenodes array contains the unique entries and how many times they occured in the $nodes array.

This is my first php script (on a drupal webpage by the way) and as such I don't know that much about php. I'm pretty confident there is probably a more a efficient way to do this, using php-specific functions. Any and all tips are welcome!

EDIT: I might have to clarify the structure of the arrays:

$nodes has two dimensions. The first dimension is just a key for the second dimension. This one contains an array of drupal nodes for each key.

$uniekenodes uses the nodes from $nodes as a key and the value is how many times the node occured in $nodes

EDIT 2:

I printed the arrays, as requested by Boris Guéry:

Array
(    
[0] => Array //Each of these contains node id's returned by a query
    (
        [0] => 12
        [1] => 11
        [2] => 10
        [3] => 9
        [4] => 8
        [5] => 7
    )

[1] => Array
    (
        [0] => 10
        [1] => 9
        [2] => 8
        [3] => 7
    )

[2] => Array
    (
    )

[3] => Array
    (
        [0] => 11
        [1] => 10
        [2] => 9
        [3] => 8
        [4] => 7
       )    
)

    Array //This one uses the node ids from the previous array as keys, the values are the number of occurences.
    (
        [12] => 1
        [11] => 2
        [10] => 3
        [9] => 3
        [8] => 3
        [7] => 3
    )
\$\endgroup\$
  • \$\begingroup\$ Post an example of your array structure \$\endgroup\$ – Boris Guéry Aug 14 '12 at 9:06
  • \$\begingroup\$ @BorisGuéry Added an example. \$\endgroup\$ – Simon Verbeke Aug 14 '12 at 9:52
1
\$\begingroup\$

First off, foreach loops would make this look much nicer. Also, its always better to use the "not operator" ! only if you have to. For instance, in your situation you do something either way, so switch it around.

foreach( $nodes AS $node ) {
    foreach( $node AS $value ) {
        if( array_key_exists( $value, $uniekenodes ) ) {
            $uniekenodes[ $value ] += 1;
        } else {//the not is now the else
            $uniekenodes[ $value ] = 1;
        }
    }
}

I had the initial idea that you could just use array_count_values() on each interior array then combine them. You can still do this, but combining them wasn't as easy as I thought it would be. Maybe I'm missing something... Anyways, I found another solution. It SHOULD work, but I've not tested it, so no promises. I'm not really sure how I feel about the closure, but it seems to be picking up common use. In the end, both the above method and this one below are fine, its up to you to decide which to use. I'm not even sure where I stand on this one. The bottom one is cleaner, but the top one I'm more familiar with. Its fun to play around with stuff like this and experiment. I'll have to see how this works out in some of my projects. Its always good when you walk away having learned something yourself. Thanks for giving me an excuse to play around with something new :)

array_walk_recursive( $nodes, function( $value, $key ) use( &$uniekenodes ) {
    if( array_key_exists( $value, $uniekenodes ) ) {
        $uniekenodes[ $value ] += 1;
    } else {
        $uniekenodes[ $value ] = 0;
    }
} );

On a final note, I would consider renaming the $uniekenodes array. It is just awkward and I wouldn't have known what was in it if you hadn't told me. $uniqueNodes is good, or $nodeFrequency, or anything else that better describes it. Unless, of course, this is in another language and that is a good description (I don't know, looks like it could be). Hope this helps!

\$\endgroup\$
  • \$\begingroup\$ The first one does the trick! The foreach loop looks more elegant than a for loop. I'm just so used to using for loops, I forgot about foreach loops. Unieke nodes is indeed another language, it's dutch. Thank you! \$\endgroup\$ – Simon Verbeke Aug 14 '12 at 14:52
1
\$\begingroup\$

I think the associative array approach is good. You could probably get away with:

for ($i = 0; $i < count($nodes); $i++)
  for ($j = 0; $j < count($nodes[$i]); $j++)
    $uniekenodes[$nodes[$i][$j]]++;

As incrementing NULL values results in 1.

\$\endgroup\$
  • \$\begingroup\$ It doesn't seem to work properly. $uniekenodes[$nodes[$i][$j]]++; gives me this error: Notice: Undefined index: 12 in include(). If I leave my loop in place, before executing yours it doesn't give the error. \$\endgroup\$ – Simon Verbeke Aug 14 '12 at 11:17
  • \$\begingroup\$ This may work if you use error suppressants. As the OP points out, there are undefined index errors because these keys have not been declared yet. However you should definitely NOT use error suppressants. It is a pretty unique solution though. \$\endgroup\$ – mseancole Aug 14 '12 at 14:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.