Bachir Messaouri
Bachir Messaouri

Reputation: 784

Filter a 2d array to keep rows with a value in any column which case-insensitively matches a search substring

I have a script which allows me to filter an entire multidimensional array according to a string. But it will only work if a value of the array matches the string exactly while I would like it to work when the string is at least contained in the value.

So, in other words, if the string is "nana", I would like the filtering to let the values "Banana" and "Ananas" be a match. So I would like the search to be done on "%nana%" in order to allow any combination of letters before and after the "nana" string.

Here is my code so far:

$dataset = [
    [3, "Yellow", "Banana"],
    [2, "Brown", "Ananas"],
    [1, "Green", "brown"]
];

$dataset = array_filter($dataset, function ($v){
    return filterArray('anana', $v);
});

function filterArray($needle,$haystack){
    $needle = strtolower($needle);

    foreach($haystack as $k => $v){
        $haystack[$k] = strtolower($v);
    };
   
    return in_array($needle,$haystack);
}

echo '<pre>'; print_r($dataset); echo '</pre>';

This won't work.

I made my homework and found that "fnmatch" or "preg_match" are referenced rather often for this case. The problem is I don't see where they should be in my script. The cases I've browsed are different enough for me not to know how to use them properly here. So it's not about looking the proper function per se but rather about understanding how and where to use it.

I tried this but it didn't work:

....
foreach($haystack as $k => $v){
    if(preg_match('/^'.$needle.'$/i', $v)) {    
        $haystack[$k] = strtolower($v);
    } 
};
....

And this didn't work neither:

....
foreach($haystack as $k => $v){
    if(preg_match("/\b$needle\b/i", $v)) {
        $haystack[$k] = strtolower($v); 
    } 
};
....

I also tried this :

....
$dataset = array_filter(dataset, function ($v){
    return filter((bool)preg_match("/$needle/i",$v);
});
....

None of those changes did any good and it feels to me like I exhausted the solutions found online and in here about this.

I also tried to use "fnmatch", even if I wasn't sure how and where to use it, but with no success.

Upvotes: 2

Views: 2440

Answers (3)

mickmackusa
mickmackusa

Reputation: 47904

From PHP8.4, you can cleanly and efficiently use nested calls of array_any() inside of array_filter() calls to retain qualifying rows.

Code: (Demo)

$dataset = [
    [3, "Yellow", "Banana"],
    [2, "Brown", "Ananas"],
    [1, "Green", "brown"]
];

$needle = 'anana';

var_export(
    array_filter(
        $dataset,
        fn($row) => array_any(
            $row,
            fn($v) => stripos($v, $needle) !== false
        )
    )
);

Output:

array (
  0 => 
  array (
    0 => 3,
    1 => 'Yellow',
    2 => 'Banana',
  ),
  1 => 
  array (
    0 => 2,
    1 => 'Brown',
    2 => 'Ananas',
  ),
)

Not only is this script functional-style and elegant, array_any() is specifically designed (under the hood) to cease iterating as soon as a qualifying evaluation is encountered -- no wasted looping!

Upvotes: 0

Enrico Dias
Enrico Dias

Reputation: 1487

You may use stripos to check if a string contains another string.

function filterArray($needle,$haystack){

    foreach($haystack as $k => $v){

        if (stripos($v, $needle) !== false) return true;

    }

}

Upvotes: 2

Nick
Nick

Reputation: 147186

You can use stripos to do a case-insensitive search for the $needle anywhere inside the $haystack value:

$dataset=[[3,"Yellow","Banana"], [2,"Brown","Ananas"], [1,"Green","brown"]];

$dataset = array_filter($dataset, function ($v){
    return filterArray('anana', $v);
});

function filterArray($needle,$haystack){
    foreach($haystack as $v){
        if (stripos($v, $needle) !== false) return true;
    };
    return false;
}

echo '<pre>'; print_r($dataset); echo '</pre>';

Output:

<pre>Array
(
    [0] => Array
        (
            [0] => 3
            [1] => Yellow
            [2] => Banana
        )
    [1] => Array
        (
            [0] => 2
            [1] => Brown
            [2] => Ananas
        )
)
</pre>

Demo on 3v4l.org

Upvotes: 3

Related Questions