【问题标题】:Most efficient way to compare/match two large arrays?比较/匹配两个大数组的最有效方法?
【发布时间】:2010-10-06 16:37:04
【问题描述】:

我正在用 PHP 编写一个进程密集型函数,它需要尽可能优化以提高速度,因为在极端情况下可能需要长达 60 秒才能完成。这是我的情况:

我正在尝试将一组人员与工作的 XML 列表进行匹配。人群中有我已经分析过的关键字,由空格分隔。这些作业来自一个大型 XML 文件。

目前是这样设置的:

$matches = new array();
foreach($people as $person){
    foreach($jobs as $job){
        foreach($person['keywords'] as $keyword){
            $count = substr_count($job->title, $keyword);
            if($count > 0) $matches[$job->title] = $count;
        }
    }
}

我用不同的类别循环了几次关键字。它完成了我需要它做的事情,但感觉非常草率,并且该过程可能需要非常非常长的时间,具体取决于人员/工作的数量。

有没有更有效或更快的方法来做到这一点?

【问题讨论】:

  • 您是否考虑过使用关系数据库(例如:MySQL)而不是“大型 XML 文件”?因为它们,你知道的,就是为此而生的。
  • 你为什么一次匹配一个人关键词?如果要使用 substr_count 函数,为什么不传递一个更大的字符串作为第二个参数,即所有关键字的集合?
  • 用xpath搜索XML文件不是更方便吗?
  • @NullUserException,这些作业是从动态的提要中获取的,不幸的是,我们无法控制。 @Sagar V,我不确定你的意思。但是阻止我改变的是关键字实际上是数组中的索引,它们的值是该关键字出现的次数。我没有在我的示例中展示这一点,但它可以让我计算出比赛最终的好坏。
  • 关键字是实际的关键字还是也可以是关键短语?

标签: php algorithm compare


【解决方案1】:
$matches = new array();
foreach($people as $person){
    foreach($jobs as $job){
        foreach($person['keywords'] as $keyword){
            $count = substr_count($job->title, $keyword);
            if($count > 0) $matches[$job->title] = $count;
        }
    }
}

说实话,你的方法有点草率,但我认为这是因为你有一些特殊格式的数据需要解决?尽管除了马虎之外,我还看到您在处理我认为不是故意的事情的方式中丢失了一些数据。

我看到您不只是检查“是职位名称中的关键字”,而是“职位名称中的关键字是多少次”,然后您将其存储。这意味着对于职位friendly friend of the friend company,“关键字”朋友出现了3 次,因此$matches["friendly friend of the friend company"] = 3。但是,由于您在成为 $people foreach 循环之前声明了 $matches,这意味着您在新人使用该关键字的任何时候都会覆盖此值。换句话说,如果第一个人有关键字“friend”,则$matches["friendly friend of the friend company"] 设置为 3。然后如果第二个人有关键字“friendly”,则该值被覆盖,$matches["friendly friend of the friend company"] 现在等于 1。

我认为您想要做的是计算有多少人拥有包含在职位名称中的关键字。在这种情况下,与其计算$keyword$job->title 中出现的次数,您应该只看到如果它出现,并做出相应的回应。

$matches = new array();
foreach($people as $person){
    foreach($jobs as $job){
        foreach($person['keywords'] as $keyword){
            if(strpos($job->title, $keyword) !== FALSE) /* "If $keyword exists in $job->title" */
                $matches[$job->title]++; /* Increment "number of people who match" */
        }
    }
}

另一种可能性是您想知道有多少关键字给定的人与给定的职位相匹配。在这种情况下,您需要每人一个单独的数组。只需稍作修改即可完成此操作。

$matches = new array();
foreach($people as $person){
    $matches[$person] = new array();
    foreach($jobs as $job){
        foreach($person['keywords'] as $keyword){
            if(strpos($job->title, $keyword) !== FALSE) /* "If $keyword exists in $job->title" */
                $matches[$person][$job->title]++; /* Increment "number of keywords which match" */
        }
    }
}

或者,或者,您现在可以返回计算关键字匹配的次数,因为对于每个人来说,这实际上是一个有意义的值(“以及工作如何匹配”)

$matches = new array();
foreach($people as $person){
    $matches[$person] = new array();
    foreach($jobs as $job){
        foreach($person['keywords'] as $keyword){
            if($count = substr_count($job->title, $keyword)) /* if(0) = false */
                $matches[$person][$job->title] += $count; /* Increase "number of keywords which match" by $count */
        }
    }
}

基本上,在解决使循环高效的问题之前,您需要弄清楚您的循环真正想要完成的是什么。弄清楚这一点,然后提高效率的最佳选择就是将循环的迭代次数减少到最少,并使用尽可能多的内置函数,因为这些是用 C 实现的(非解释的,因此更快-运行语言)。

【讨论】:

  • 是的,为了保持简单,我在尝试完成的工作中遗漏了很多细节,而是尝试专注于整体结构。不过,您几乎一针见血。目前,关键字是数组中的索引,值是它们在该人身上出现的次数。然后在匹配一个工作时,它会计算该关键字在工作中出现的次数,并在不同的“匹配”数组中给它一个“分数”(关键字值 * 匹配数),最终让我对相关性进行排名。
  • 在这种情况下,需要计算出现次数(并因此单独解析每个字符串)确实会给您带来算法复杂性的独特情况。我绝对会建议初学者将人员和工作列表保持在必要的范围内(如果您没有输出结果,则不要计算人员匹配项)
  • @Nick 另外,考虑给每个作业一个包含所有词汇的数组。计算机比较两个数组要快得多/容易得多,因为您确切地知道如果单词确实存在,它将从哪里开始。本质上,您只检查与单词一样多的位置,而不是字母。因此,$job->wordArray = array('friendly','friend','of','the','friend','company');。在此之后,查看 array_count_values()array_key_exists() 以查找匹配数。
  • @Nick 请注意,新方法将区分“友好”和“朋友”,但这可能很好,因为它可以区分“猫”和“餐饮”
  • 谢谢,我会试一试。现在我正在使用正则表达式和关键字周围的单词边界来区分像“cat”和“餐饮”这样的词,这可能对我的速度问题没有帮助。不幸的是,必须对每个人进行分析,而且没有办法避免这种情况,因为我需要显示所有人并能够对它们进行排序。但我正在考虑将 XML 转换为数组,因为它被每个人一遍又一遍地重用,这肯定有助于确认这样做的必要性。谢谢!
【解决方案2】:

您可以使用职位名称中的单词索引来提高查找效率:

$jobsByWords = array();
foreach ($jobs as &$job) {
    preg_match_all('/\w+/', strtolower($jobs->title), $words);
    foreach ($words[0] as $word) {
        if (!isset($jobsByWords[$word])) $jobsByWords[$word] = array();
        $jobsByWords[$word][] = &$job;
    }
}

然后您只需迭代人员并检查关键字是否在索引中:

foreach ($people as $person) {
    foreach ($person['keywords'] as $keyword) {
        $keyword = strtolower($keyword);
        if (isset($jobsByWords[$keyword])) {
            foreach ($jobsByWords[$keyword] as &$job) {
                $matches[$job->title] = true;
            }
        }
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-05-15
    • 1970-01-01
    • 2021-04-29
    • 2016-12-02
    • 1970-01-01
    • 1970-01-01
    • 2011-01-19
    • 1970-01-01
    相关资源
    最近更新 更多