如何根据排除的另一个多维数组过滤多维数组?

How to filter multidimensional array based on another multidimensional array of exclusions?

我正在尝试过滤包含(>40,000)个产品的多维数组。每个产品 entry/subarray 包含产品 ID 和一些产品属性。

我有一个关联排除数组,其中包含 1 个或多个与特定属性相关的列入黑名单的值。

如果产品具有我的排除数组中指定的任何键值对,则应过滤掉 product/subarray。

排除数组:

$exclusions = [
    'Discontinue Status' => [
        'Discontinued',
        'Run Down Stock',
    ],
    'Hazardous' => [
        'No',
    ],
];

示例产品数组:

$products = [
    [
        'Product ID' => '452',
        'Discontinue Status' => 'Discontinued',
        'Hazardous' => 'No',
    ],
    [
        'Product ID' => '463',
        'Discontinue Status' => 'Normal',
        'Hazardous' => 'No',
    ],
    [
        'Product ID' => '477',
        'Discontinue Status' => 'Run Down Stock',
        'Hazardous' => 'Yes',
    ],
    [
        'Product ID' => '502',
        'Discontinue Status' => 'Discontinued',
        'Hazardous' => 'No',
    ],
    [
        'Product ID' => '520',
        'Discontinue Status' => 'Normal',
        'Hazardous' => 'Yes',
    ],
];

预期输出:

[
    [
        'Product ID' => '520',
        'Discontinue Status' => 'Normal',
        'Hazardous' => 'Yes',
    ],
]

我只能返回正确数量的 products/items,但只有与该项目关联的排除项,而不是使用以下代码的项目本身。

    $exclusions = $this->exclusions;

    $products = [];

    foreach ($array as $product) {

        $filtered = array_filter($product, function ($val, $key) use ($exclusions) { 
                return isset($exclusions[$key]) && !in_array($val, $exclusions[$key]);
            },
            ARRAY_FILTER_USE_BOTH
        ); 

        $products[] = $filtered;

    }  

    $result = array_filter(array_map('array_filter', $products));

    echo '<pre>' . var_export($result, true) . '</pre>';
    echo count($result);

首先我们将 $data$discontinued$hazardous 设置为空白数组,我们将在遍历 $exclusions$products 个数组。

$data = $discontinued = $hazardous = [];

foreach($exclusions as $exclusion) {

    if(isset($exclusion['Discontinue Status'])) $discontinued = $exclusion['Discontinue Status'];

    if(isset($exclusion['Hazardous'])) $hazardous = $exclusion['Hazardous'];

}

现在我们要遍历产品并检查每个产品的值是否与排除数组中的任何内容匹配。

foreach($products as $product) {

    if(in_array($product['Discountinue Status'], $discontinued)) continue;
    if(in_array($product['Hazardous'], $hazardous)) continue;

    $data[] = $product;

}

不建议将排除数组分成单独的变量,因为这会使您的代码在您要修改排除列表时更难/更乏味地维护。

只迭代您的产品数组一次。在该循环内,循环具有与排除数组中的第一级键匹配的键的每个产品中的属性。这是 array_intersect_key() 最擅长的,这可以防止对 Product ID 元素执行不必要的比较。一旦发现满足取消资格的条件,就停止内部循环以获得最佳效率。

代码 #1 (Demo) *我的推荐

$result = [];
foreach ($products as $product) {
    foreach (array_intersect_key($product, $exclusions) as $key => $value) {
        if (in_array($value, $exclusions[$key])) {
            continue 2;
        }
    }
    $result[] = $product;
}
var_export($result);

代码#2:(Demo)

foreach ($products as $index => $product) {
    foreach (array_intersect_key($product, $exclusions) as $key => $value) {
        if (in_array($value, $exclusions[$key])) {
            unset($products[$index]);
            break;
        }
    }
}
var_export(array_values($products));

代码 #3:(Demo)

var_export(
    array_values(
        array_filter(
            $products,
            function($product) use ($exclusions) {
                return !array_filter(
                    array_intersect_key($product, $exclusions),
                    function($value, $key) use ($exclusions) {
                        return in_array($value, $exclusions[$key]);
                    },
                    ARRAY_FILTER_USE_BOTH
                );
            }
        )
    )
);

代码 #4:(Demo)

var_export(
    array_values(
        array_filter(
            $products,
            fn($product) => !array_filter(
                array_intersect_key($product, $exclusions),
                fn($value, $key) => in_array($value, $exclusions[$key]),
                ARRAY_FILTER_USE_BOTH
            )
        )
    )
);

代码#1使用continue 2;停止halt内循环,避免将当前产品存储在输出数组中,然后return到外循环进行下一个产品的处理。

代码 #2 直接修改 $products 数组。通过取消设置产品,数组可能不再是索引数组(键可能在整数之间有间隙)。如果需要,在循环后调用 array_values() 以重新索引输出。

代码 #3 使用的是函数式风格。语言结构(例如 foreach())优于函数式迭代器是很常见的,因此我假设此代码段(和代码 #4)将比前两个代码稍慢。此外,array_filter() 不喜欢 foreach 循环在 breakcontinue 中的早期 return。换句话说,代码 #3 和 #4 将继续检查排除数组,即使给定产品的取消资格条件已经满足。如果这还不够,我只是觉得语法太复杂了。

代码 #4 与代码 #3 相同,但使用的是 PHP7.4 及更高版本中可用的稍微较短的 "arrow function" 语法。这样可以省略 use() 和其他一些字符,但我仍然发现与代码 #1 和 #2 相比,代码片段 intuitive/readable 更少。