如何(快速)发现两个数组是否至少有一个共同项

时间:2014-07-16 21:03:17

标签: php search

我正在编写一个脚本,它将重复搜索一大组数组(40,000)并合并所有至少有一个公共元素的数组。我试过array_intersect(),但我发现这个应用程序太慢了。是否有另一个更快的函数,如果两个数组之间至少共享一个元素,则返回true?

我的假设是array_intersect()由于两个数组都被完全检查并且公共值被组合在一起并返回而减慢了。找到单个匹配后退出会更快。

澄清:所有数组都使用另一个主数组(这是一个2d数组)。如果发现存储在$ master [231]和$ master [353]中的数组都包含元素124352354,应该合并到一个新数组中,结果存储在另一个2d数组中,用于存储合并结果。

当前代码:

$test = array_intersect($entry, $entry2);
if($test){

...

}

更好的方法是:

foreach($entry as $item){
     if(in_array($item, $entry2)){
         $test = true;
         break;
     }
}
if($test){

...

}

另一个改进是使用isset()和array_flip()而不是in_array();

$flipped = array_flip($entry2);
foreach($entry as $item){
     if(isset($flipped[$item]){
         $test = true;
         break;
     }
}
if($test){

...

}

5 个答案:

答案 0 :(得分:2)

假设您只想发现两个数组是否具有公共元素,您可以创建自己的getIntersect函数,这比使用array_intersect更快,因为它会在第一次匹配时立即返回。

function getIntersect($arr1, $arr2)
{
    foreach($arr1 as $val1)
    {
        foreach($arr2 as $val2)
        {
            if($val1 == $val2)
            { return true; }
        }
    }
    return false;
}

假设您真正想要找到的是数组,其中一个元素至少出现一次

然后你可以轻松拥有

function hasCommonElements($arr)
{
    for($i = 0; $i < count($arr); $i++)
    {
        $val = $arr[$i];
        unset($arr[$i]);
        if(in_array($val, $arr))
        {
            return true;
        }
    }
}

您可以使用array_filter轻松获取包含公共元素的所有数组的数组:

array_filter($my40k, "hasCommonElements");

假设你真正想要做的是找到至少有一个共同值的所有数组,你必须做更高级< / em>数组过滤器。

$mybigarray;//your big array

function hasIntersects($arr)
{
    for($i = 0; $i < count($mybigarray); $i++)
    {
        if(getIntersect($arr, $mybigarray[$i]))
        {
            return true;
        }
    }
}

然后调用我们的过滤器怪物

array_filter($mybigarray, "hasIntersects");

免责声明:这些东西都没有经过测试。检查拼写错误

答案 1 :(得分:1)

围绕预先设想的概念

我在一个答案中都证明了我的观点并且破坏了它。 TL; DR =在SQL中执行此操作。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ < / p>

PHP内置函数是代码级清理程序,但效率低得惊人。

如果你是按键,那么你最好的选择就是尝试和真正的for循环:

$array1 = array( ... );
$array2 = array( ... );

$match = FALSE;

foreach($array1 as $key => $value) {
    if(isset($array2[$key]) && $array2[$key] == $value) {
        $match = TRUE;
        break;
    }
}

这可能看似违反直觉,但在执行级别,没有什么,你必须迭代至少一个数组中的每个项目。只需在较短的阵列上进行操作即可缩短它。

array_intersect()继续使用两个数组中的每个键,因此,虽然它看起来很疯狂,但你必须以“脏”的方式进行操作。

如果数据来自数据库,那么让SQL引擎完成提升实际上会更快。具有限制1的简单连接将为您提供一个标志,以便知道是否存在重复项,然后您可以执行另一个查询来获取合并数据(如果您需要在更多表上执行此操作,则可以针对多个表或源查询动态生成查询而不是一对)。

对于这样做,SQL将比任何更高级别的语言(如PHP)更快。我不在乎你是否已经在内存中有数组,执行查询并从数据库加载新数组比尝试进行比较然后合并到应用程序的驻留内存更快...

再次,反直觉的事情......

所以,这很有趣:

我在http://pastebin.com/rzeQnyu2

为此制作了一个测试脚本

当匹配器(电话号码)为5位时,foreach循环始终以另一个选项的1/100时间执行。但是,将其增加到10位数(消除所有碰撞的可能性),foreach跳转到比其他选项慢36倍。

# 5 digit key (phone number)
Match  found
For Each completed in 0.0001
Intersect Key completed in 0.0113

# 10 digit key
Match not found
For Each completed in 0.2342
Intersect Key completed in 0.0064

我想知道为什么第二个选项(可能有更大的数组)对于Intersect来说比较小的那个更快...... WEIRD ......

这是因为,虽然交叉总是迭代所有项目,但foreach循环在它可以提前退出时获胜,但如果它没有获得那个机会则看起来非常慢。我会对更深层次的技术原因感兴趣。

EIther Way - 最后,只需在SQL中执行。

答案 2 :(得分:1)

如果您的数组只包含也是有效键的值(整数,...),那么翻转数组(交换键和值)可能会更好,这在技术上意味着构建和索引 ,并搜索键。例子:

function haveCommonValues1($a1, $a2) {
    $a1_flipped = array_flip($a1);
    foreach ($a2 as $val) {
        if (isset($a1_flipped[$val])) {
            return true;
        }
    }
    return false;
}

或者如果你需要交叉点:

function haveCommonValues2($a1, $a2) {
    $a1_flipped = array_flip($a1);
    $a2_flipped = array_flip($a2);
    return array_intersect_key($a1_flipped, $a2_flipped);
}

在一些测试阵列上我得到了这些结果,但这在很大程度上取决于阵列结构。所以你需要测试它并比较时间。

array_intersect   : 0m1.175s
haveCommonValues1 : 0m0.454s
haveCommonValues2 : 0m0.492s

答案 3 :(得分:0)

array_intersect的运行时为O(n * log(n)),因为它在比较之前使用了排序算法。根据您的输入,您可以通过多种不同的方式对其进行改进(例如,如果您使用小范围的整数,则可能会使用counting sort来阻止算法。)

您可以找到适合该优化的示例herehere。 您不需要排序的可能解决方案发布在this thread中。它也有线性时间,所以我想这就是你要找的东西。

答案 4 :(得分:0)

SELECT *
FROM contacts t1 INNER JOIN contacts t2
  ON t1.phone = t2.phone
  AND t1.AccountID < t2.AccountID

此外,如果您的系统可能会增长到包含国际电话号码,则应将其存储为字符串类型。在欧洲,有些国家/地区使用电话号码中的前导零,而您无法使用数字类型正确存储它们。

修改

以下查询将返回多次使用的所有电话号码实例,无论有多少帐户共享电话号码都没有重复行:

SELECT DISTINCT t1.AccountID, t1.phone
FROM contacts t1 INNER JOIN contacts t2
  ON t1.phone = t2.phone
  AND t1.AccountID != t2.AccountID
ORDER by t1.phone

我会包含一个SQLfiddle,但它似乎被打破了。这是我用作测试的架构/数据:

CREATE TABLE IF NOT EXISTS `contacts` (
  `AccountID` int(11) NOT NULL,
  `phone` varchar(32) NOT NULL,
  KEY `aid` (`AccountID`),
  KEY `phn` (`phone`)
)

INSERT INTO `contacts` (`AccountID`, `phone`) VALUES
    (6, 'b'),
    (1, 'b'),
    (1, 'c'),
    (2, 'd'),
    (2, 'e'),
    (3, 'f'),
    (3, 'a'),
    (4, 'a'),
    (5, 'a'),
    (1, 'a');