-2

I want to get sub array keys from array by less than values.
This is an example:

$arr_less_than = array(55,60,10,70);

$BlackList = array(10,8,15,20);

$MasterArray = array(
    10 => array(1 => array(50,20,5,40), 2 => array(70,77,58,10), 3 => array(155,95,110,105), 4 => array(250,215,248,188)),
    11 => array(1 => array(5,65,49,100), 2 => array(80,85,60,30), 3 => array(175,85,95,120), 4 => array(235,205,218,284)),
    12 => array(1 => array(82,80,55,80), 2 => array(90,90,74,110), 3 => array(180,122,156,222), 4 => array(255,225,233,263)),
    13 => array(1 => array(350,360,400,375), 2 => array(95,99,111,75), 3 => array(188,112,66,111), 4 => array(66,69,33,110)),
);

Now I need to get sub array keys from $MasterArray by less than $arr_less_than if the sub array key is not in the array $BlackList.

For the above example, the result must return array(12,13).

Note: I don't want to use a foreach loop

samer
  • 1
  • 4
  • 1
    Can you briefly explain why you want to avoid using a foreach loop? – Don't Panic Aug 01 '17 at 19:42
  • Also, can you add some more detail about which array items should be filtered out? It may be clear to others, but I can't determine the criteria. Showing some examples and describing why they shouldn't be included would be helpful. – Don't Panic Aug 01 '17 at 19:46
  • hello Don't Panic thank's for your replay i want to filter all array in $masterarray and return keys if available – samer Aug 01 '17 at 20:01
  • in my example filtered $MasterArray its have 4 array and every array have values if $arr_less_than (less than) any array return main array key in example will return key 12 and 13 because array(55,60,10,70) less than all values or one value in main array [13] also array [12] same the main array[10] was be return because its have less value array(50,20,5,40) but its in blacklist will not filtered – samer Aug 01 '17 at 20:07
  • if sub-arrays contains a value less than any value in $arr_less_than will return sub-arrays key – samer Aug 01 '17 at 20:14
  • Then, why shouldn't 11 be included in the results as well, as some of its sub-arrays contain values less than those in `$arr_less_than`? (Sorry for all the questions, just trying to understand the problem properly.) – Don't Panic Aug 01 '17 at 20:17
  • [11] not include because not have less than $arr_less_than = array(55,60,10,70); all keys in $arr_less_than must less than not only one – samer Aug 01 '17 at 20:24
  • if found lees than array(55,60,10,70); its much but this array(50,60,10,70); not much because first key 50 not less than first key 55 – samer Aug 01 '17 at 20:28
  • So the specific positions in the sub-arrays are compared. I think I see now. – Don't Panic Aug 01 '17 at 20:31
  • yes you are right all sub-arrays will compared – samer Aug 01 '17 at 20:32
  • I am having trouble understanding the example. In 12 all of the values are less then the correspooding values is $arr_less_than, but in 13, not all of them are `array(95,99,111,65)` for example. So 13 all but one meet this criterion. But 11 also has at least on array that meets that criterion and it is excluded. SO is is that all of the $arr_less_than has the be less than all of the sub arrays or just one? – RichGoldMD Aug 01 '17 at 21:45
  • Hi RichGoldMD i want to compered $arr_less_than with all sub-arrays in $masterarray if sub-array have array less than $arr_less_than return sub-array key in this example down no sub-arrays its good but in need to compered in sub-arrays – samer Aug 01 '17 at 22:00
  • $arr_1=array(200, 300, 200, 200); $arr_2=array( 1 => array(70, 90, 70, 20), 2 => array(115, 150, 115, 35), 3 => array(205, 250, 195, 55), 4 => array(325, 420, 325, 95), 5 => array(545, 700, 545, 155) ); $filtered = array_filter($arr_2, function($value) use ($arr_1) { return max($value) < min($arr_1); }); $keys = array_keys($filtered); var_dump($keys); – samer Aug 01 '17 at 22:00
  • So have you solved it? – RichGoldMD Aug 01 '17 at 22:02
  • So then do all of the sub arrays need to meet the criterion where all values are less than the min value of the reference array, or just 1 of the sub array? – RichGoldMD Aug 01 '17 at 22:05
  • not solved it the example work in one array i need all values in sub-arrays – samer Aug 01 '17 at 22:18
  • I'm trying to get clarity on the criteria that cause a row to be included, with the sub arrays. is it that all of the sub arrays have to have a max value less than the min value of $arr_less_than? In that case there would be no matches, because all of the sub arrays have values > 10. So could you more clearly state why one row in MasterArray would meet your criteria? I cant figure it out from your example which seems inconsistent. What is the operation over each value in MasterArray (each array of arrays) which would cause it to match? – RichGoldMD Aug 01 '17 at 22:20
  • $MasterArray maybe have 100 arrays every array in $MasterAarray will have sub-arrays the values in sub like this array(16,20,33,72) i want to compered $arr_less_than with all sub-arrays values 4 keys with 4 keys if any array in sub-arrays much less than return sub-array key – samer Aug 01 '17 at 22:33
  • i want to check this $ar1=array(90,80,90,80) with this $arr2=array(89,200,500,100) the $arr1 not less than The three keys are smaller but the first key 90 > is bigger 89 so they are ignored because they do not match – samer Aug 01 '17 at 22:39
  • So each element of the sub array should be less than the corresponding element in arr_less_than? Ok not understanding how row 13 in your example meet that criteria. Can you help Me there? I think I can help just need to understand fully – RichGoldMD Aug 01 '17 at 22:42
  • 13 => array(1 => array(350,360,400,375), 2 => array(95,99,111,65), 3 => array(188,112,66,111), 4 => array(66,69,33,110)), all arrays in [13] is bigger than $arr_less_than = array(55,60,10,70); – samer Aug 01 '17 at 22:44
  • I see the values, but why does it match? – RichGoldMD Aug 01 '17 at 22:47

1 Answers1

0

There are 2 solutions here - one for all sub-arrays meet the criteria, and one for any sub-array meets the criteria (which it seems is what the OP had in mind). At the end, there are foreach based solutions for the latter case where ANY sub-array meets the criteria.

If I understand the problem correctly, the goal is to identify rows in MasterArray where all of the sub-arrays have values that are greater than the corresponding value in $arr_less_than.

The OP does not want to use foreach (See the end of the answer for foreach based answers which are much simpler) - which may actually produce a more efficient version because it can avoid unnecessary compares and save some cycles, so here is a heavily annotated version using array functions.

I've excluded the data which can be copied from OP's post:

function getMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
    return  array_values(  // When we're done we just want a straight array of the keys from $MasterArray
        array_filter(
            array_keys($MasterArray), // Iterate over $MasterArray's keys (Because we need the keys for the BlackList)
            function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
                // Filter out MasterArray Entries that dont meet the criteria
                echo "Evaluate $MasterArray[$v]" . PHP_EOL;
                // Remove array entries whose key is in the BlackList
                if (in_array($v, $BlackList)) {
                    echo "\tBlacklisted" . PHP_EOL;
                    return false;
                }
                // For each entry in the MasterArray value, add up the number of non-matching entries
                $y = array_reduce(
                    $MasterArray[$v],
                    function ($c1, $sub) use ($arr_less_than) {
                        // For each subarray entry in a MasterArray value, reduce the array to a count
                        // of elements whose value is less than the corresponding value in the $arr_less_than
                        $s1 = array_reduce(
                            array_keys($sub),
                            function ($carry, $key) use ($sub, $arr_less_than) {
                                if ($sub[$key] <= $arr_less_than[$key]) {
                                    return ++$carry;
                                }
                            },
                            0 // Initial value for the array_reduce method
                        );
                        // $s1 will be a count of non-matching values
                        return $c1 + $s1;
                    },
                    0 //Initial value for the array_reduce method
                );
                echo "\t$y" . PHP_EOL;
                // Include the array value in the filter only if there are no non-matching values ($y == 0)
                return !$y;
            }
        )
    );
}
print_r(getMatchingRows($arr_less_than, $MasterArray, $BlackList));

The basic idea is to generate a list of keys from the outermost array - so we iterate over them with array_filter. Then we exclude those with a key in the blacklist. Rows that arent in the blacklist, are reduced into an integer by iterating over each sub=arrays values and comparing them positon-wise against $arr_less_than and adding 1 for each value that fails to be greater than the corresponding member in $arr_less_than. Then those values are summed for all of the members in the MasterArray row. If the result is zero, then the row passes. Finally, the ultimate result is passed to array_values to normalize the resulting array.

Note that this requires that all values be compared, even if the first sub-value in the first sub-array fails. For that reason a foreach approach that can escape may be more efficient.

This is essentially the same method without comments and couple of shortcuts:

function getMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
    return  array_values(  // When we're done we just want a straight array of the keys from $MasterArray
        array_filter(
            array_keys($MasterArray),
            function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
                return !in_array($v, $BlackList) && !array_reduce(
                    $MasterArray[$v],
                    function ($c1, $sub) use ($arr_less_than) {
                        return $c1  ?: array_reduce(
                            array_keys($sub),
                            function ($carry, $key) use ($sub, $arr_less_than) {
                                return $carry ?: ($sub[$key] <= $arr_less_than[$key] ? 1 : 0);
                            },
                            0
                        );
                    },
                    0
                );
            }
        )
    );
}

Some of the methods in array_reduce are short-circuited using ?: operator since the actual count is irrelevant. Once the count exceeds zero, the row fails, regardless.

Here is similar code if the criterion is that AT LEAST ONE sub-array has all members greater than the reference array.

function getMatchingRowsAny($arr_less_than, $MasterArray, $BlackList)
{
    return  array_values(  // When we're done we just want a straight array of the keys from $MasterArray
        array_filter(
            array_keys($MasterArray), // Iterate over $MastrArray's keys (Because we need the keys for theBlackList)
            function ($v) use ($BlackList, $MasterArray, $arr_less_than) {
                // Filter out MasterArray Entries that dont meet the criteria
                echo "Evaluate \MasterArray[$v]" . PHP_EOL;
                // Remove array entries whose key is in the BlackList
                if (in_array($v, $BlackList)) {
                    echo "\tBlacklisted" . PHP_EOL;
                    return false;
                }
                // For each entry in the MasterArray value, add up the number of non-matching entries
                $y = array_reduce(
                    $MasterArray[$v],
                    function ($c1, $sub) use ($arr_less_than) {
                        // For each subarray entry in a MasterArray value, reduce the array to a flag
                        // indicating if it has whose value is <= the corresponding value in the $arr_less_than
                        $s1 = array_reduce(
                            array_keys($sub),
                            function ($fail, $key) use ($sub, $arr_less_than) {
                                return $fail || $sub[$key] <= $arr_less_than[$key];
                            },
                            false
                        );
                        // This could be short-circuited above to avoid an unnecessary array_reduce call
                        return $c1 || !$s1;
                    },
                    false
                );
                echo "\t$y" . PHP_EOL;
                // Include the array value in the filter if there are any matching values
                return $y;
            }
        )
    );
}

print_r(getMatchingRowsAny($arr_less_than, $MasterArray, $BlackList));

As an exercise (and because I'm a glutton for punishment) I rendered the same methods using foreach as both a generator and a function returning an array - mostly to illustrate that foreach may be the better choice, and is definitely simpler:

// Implemented as a generator - The associated foreach that uses it follows
function generateMatchingRows($arr_less_than, $MasterArray, $BlackList)
{
    foreach ($MasterArray as $k => $v) {
        if (in_array($k, $BlackList)) {
            continue;
        }
        foreach ($v as $sub_array) {
            $match = true;
            foreach ($sub_array as $k1 => $v1) {
                if ($v1 <= $arr_less_than[$k1]) {
                    $match = false;
                    break;
                }
            }
            if ($match) {
                yield $k;
                break;
            }
        }
    }
}

foreach (generateMatchingRows($arr_less_than, $MasterArray, $BlackList) as $k) {
    echo $k . PHP_EOL; // Or push them onto an array
}

// Implemented as a function returning an array - classical approach - just return an array
function getMatchingRowsForEach($arr_less_than, $MasterArray, $BlackList)
{
    $rv = [];
    foreach ($MasterArray as $k => $v) {
        if (in_array($k, $BlackList)) {
            continue;
        }
        foreach ($v as $sub_array) {
            $match = true;
            foreach ($sub_array as $k1 => $v1) {
                if ($v1 <= $arr_less_than[$k1]) {
                    $match = false;
                    break;
                }
            }
            if ($match) {
                $rv[] = $k;
                break;
            }
        }
    }
    return $rv;
}

print_r(getMatchingRowsForEach($arr_less_than, $MasterArray, $BlackList));
RichGoldMD
  • 1,252
  • 1
  • 10
  • 18
  • not work because if first sub-array array(1,1,1,1) its escape all sub-array after that in row – samer Aug 02 '17 at 00:22
  • Then I misunderstand your criteria. 1 is less than all of the values in $arr_less_than, and you indicated you wanted rows in which ALL sub-arrays have values only greater than the corresponding values in $arr_less_than. 1 is not greater than any of the numbers in $arr_less_than - this is why #13 failed before you changed the code, because one value was less than the corresponding value in arr_less_than. What is the expectdd behavior for a row if the first subarray is 1,1,1,1? Does it depend on the other values? Or has it failed already? – RichGoldMD Aug 02 '17 at 00:28
  • You wrote: " all arrays in [13] is bigger than $arr_less_than = array(55,60,10,70); " – RichGoldMD Aug 02 '17 at 00:29
  • mybye $arr_less_than = array(2500,2000,1500,1700); and 12 => array(1 => array(82,80,55,80), 2 => array(90,90,74,110), 3 => array(180,122,156,222), 4 => array(5000,3000,2500,2700)) its will fail – samer Aug 02 '17 at 00:33
  • Can you tell me WHY it should not fail with those values? Is it not ALL subarrays must pass, but AT LEAST ONE must pass? In that case, in your original post, 11 would pass (the first sub-array is greater than $arr_less_than), but you indicated it does not. And you wrote: "all keys in $arr_less_than must less than not only one" regarding #11 – RichGoldMD Aug 02 '17 at 00:38
  • its should not fail because 4 => array(5000,3000,2500,2700) is bigger $arr_less_than – samer Aug 02 '17 at 00:47
  • Then #11 in your post should not fail. But you said it would. You are contradicting your own answer to Don't Panic in your original posts comments. I don't know if you are confused about what you are trying to accomplish, but it's not the same now as it was a couple of hours ago. Good luck with your project. – RichGoldMD Aug 02 '17 at 00:54
  • [11] now fail I am very sorry I took much of your time but for many numbers I did not notice if you could help me with the best regards – samer Aug 02 '17 at 01:01
  • I added a new function at the end which looks for rows with ANY matching sub-array - so now you have both cases. – RichGoldMD Aug 02 '17 at 01:18
  • `foreach` version added as well - its much simpler. – RichGoldMD Aug 02 '17 at 01:41
  • Thank you Mr – RichGoldMD can you post result for your function – samer Aug 02 '17 at 02:28
  • I get Array ( [0] => 11 [1] => 12 [2] => 13 ) but you should now be able to _copy and paste and test yourself_ because your test case is incomplete. It needs a row which would be excluded and is not in the blacklist. – RichGoldMD Aug 02 '17 at 09:01
  • if all sub-arrays in row bigger than $arr_less_than result will be fail else result pass and return row key – samer Aug 02 '17 at 12:44
  • No. Row 13 meets that condition and passes. – RichGoldMD Aug 02 '17 at 12:59