Searching in array by key => values

Posted on

Problem

I need to write a function that searches the two-tier board according to certain parameters, which works, but I question whether there is a simpler, lighter way to perform this task.

<?php
function findValue(array $array, array $parameters, $multipleResoult = false){
    $result = array();//used when $multipleResoult == true
    $suspicious = false;
    foreach($array as $childArray){
        foreach($parameters as $k => $p){
            if(array_key_exists($k,$childArray)){
                if($childArray[$k] == $p){
                    $suspicious = $childArray;
                } else {
                    $suspicious = false;
                    continue 2;
                }
            } else {
                $suspicious = false;
                continue 2;
            }
        }
        if(is_array($suspicious)){
            $result[] = $suspicious;
            if($multipleResoult == true){
                $suspicious = false;
            } else {            
                break;
            }
        }

    }
    return $result;
}
$arr =     Array
    ( 
        0 => Array
        (
            "id" => 1,
            "id_shop" => 1,
            "id_lang" => 1,
            "id_product" => 1,
            "id_field" => 3,
            "field_value" => "zxczxc"
        ),

    // find if single and multiple
    1 => Array
    (
            "id" => 2,
            "id_shop" => 1,
            "id_lang" => 2,
            "id_product" => 1,
            "id_field" => 3,
            "field_value" => "sdfsdfsdf"
        ),

    2 => Array
    (
            "id" => 3,
            "id_shop" => 1,
            "id_lang" => 2,
            "id_product" => 2,
            "id_field" => 3,
            "field_value" => "sdfsdfsdf"
        ),
    // find if multiple
    3 => Array
    (
            "id" => 3,
            "id_shop" => 1,
            "id_lang" => 2,
            "id_product" => 1,
            "id_field" => 3,
            "field_value" => "sdfsdfsdf"
        )

);
echo '-----------------SINGLE----------------------';echo "n";echo '<pre>';
print_r(findValue($arr, array(
    'id_shop' => 1,
    'id_field' => 3,
    'id_lang'=> 2,
    'id_product' => 1)
                 ));
echo '-----------------MULTIPLE----------------------';echo "n";echo '<pre>';
print_r(findValue($arr, array(
    'id_shop' => 1,
    'id_field' => 3,
    'id_lang'=> 2,
    'id_product' => 1)
                 ,true));
?>

Solution

Firstly, this could use a better description (in a comment) explaining what it does. I had to figure it out from the code. I think the description should probably be something like

// Searches $array for associative arrays which are supersets of $parameters.
// If $multipleResults it will return all matches in an array;
// otherwise just the first, in an array.

Secondly, names.

  • findValue isn’t very descriptive. How about findSupersets?
  • $array would traditionally be called $haystack in PHP, since you’re searching through it for matches to a predicate.
  • $multipleResoult is a misspelling. Correcting the spelling and adjective-noun agreement we get $multipleResults, which is ok, although I personally would prefer to communicate an action: $returnAll.
  • $suspicious has the wrong connotations for this native English speaker. If something is suspicious, it means that I think there’s something wrong with it. My preferred variable name for something which may or may not pass an acceptance test is $candidate.

Thirdly, since PHP has a dynamic type system I think it would be more idiomatic to return just the element if !$multipleResults, rather than wrapping it in an array.


Now, you specifically ask whether there’s a simpler way to do it. I think that using booleans it can be simplified quite a bit by inverting the assumption. Rather than prove it acceptable, prove it not acceptable. Taking into account various of my suggestions above and refactoring I get:

    foreach($haystack as $childArray){
        $isCandidate = true;
        foreach($parameters as $k => $p){
            if(!array_key_exists($k,$childArray) || $childArray[$k] != $p){
                $isCandidate = false;
                break;
            }
        }
        if($isCandidate){
            if(!$multipleResults){
                return $childArray;
            }
            $result[] = $childArray;
        }
    }

    return $result;

It’s a couple of years now since I used PHP, and I’m not sure offhand whether there are built-in functions to test whether $childArray contains $parameters as a subset, or to filter an array by a predicate. It may be that the whole function can be reduced to two calls to built-ins. If not, you could consider whether you want to factor it out as

function findMatch(array $haystack, $predicate, $returnAll = false) {
    $results = []
    foreach ($haystack as $element) {
        if ($predicate($element)) {
            if ($returnAll) return $element;
            $results[] = $element;
        }
    }
    return $results;
}

function isSuperset(array $candidate, array $subset) {
    foreach ($subset as $k => $v) {
        if (!isset($candidate[$k]) || $candidate[$k] != $v) return false;
    }
    return true;
}

linking the two with a lambda.

You don’t need to call a second foreach loop and temporarily store partial matches (suspicious/candidates) as you go.
Use array_intersect_assoc() and sizeof() (or count()) to instantly identify qualifying subarrays.

Code: (Demo)

function findValue(array $array, array $terms, $get_all=false){
    $term_count=sizeof($terms);  // cache the element count of $terms
    $results=[]; // establish empty array for print_r if no matches are found.
    foreach($array as $subarray){
        if(sizeof(array_intersect_assoc($subarray,$terms))==$term_count){ // qualifying subarray
            if(!$get_all){
                return $subarray;  // end loop and return the single subarray
            }else{
                $results[]=$subarray;
            }
        }
    }
    return $results;
}

Leave a Reply

Your email address will not be published. Required fields are marked *