【问题标题】:Comparing strings in PHP and removing one of them from the array if they are similar比较 PHP 中的字符串,如果它们相似,则从数组中删除其中一个
【发布时间】:2010-09-10 01:06:50
【问题描述】:

假设我有一个这样的数组:

  • 马群 - 有鬼吗
  • 马群 - 没有人会爱 你
  • 马群 - 葬礼
  • 马的乐队 - 葬礼(歌词 在描述中)
  • 马群 - 拉雷多
  • 马带 - 莱特曼的拉雷多 5.20.10
  • 马群 - “大盐 Lake”子流行唱片
  • 马的乐队 - “没有人会爱 你”
  • Band of Horses 表演 Marry Song 在 特罗姆瑟婚礼
  • 马群 - 没有人会爱 你
  • Q TV 上 Band of Horses 的“Laredo”
  • 马群,在我回家的路上
  • 马群 - 香烟婚礼 乐队
  • 马的乐队 - “香烟婚礼 乐队”
  • 马群 - 我去谷仓 因为我喜欢
  • 我们的剑 - 马带
  • Band Of Horses - “结婚之歌”
  • 马群 - 怪物
  • 马群 - 没有人会爱 你

新数组将具有:

  • 马群 - 有鬼吗
  • 马群 - 没有人会爱 你
  • 马群 - 葬礼
  • 马群 - 拉雷多
  • 马群 - “大盐 Lake”子流行唱片
  • 马群,在我回家的路上
  • 马群 - 香烟婚礼 乐队
  • 马群 - 我去谷仓 因为我喜欢
  • 我们的剑 - 马带
  • Band Of Horses - “结婚之歌”
  • 马群 - 怪物

在 PHP 中,您将如何将每个字符串与列表中的每个其他字符串进行比较,如果它们相似,请删除它们。

我认为这些类似:

  • 马群 - 葬礼
  • Band of Horse - The Funeral(歌词在描述中)

另一个例子:

  • 马群 - 拉雷多
  • 马带 - 莱特曼的拉雷多 5.20.10

【问题讨论】:

  • “相似”究竟是什么意思?.... 那么,在示例列表输入中,您希望输出是什么?
  • 好的,我用我想要的结果更新了我的问题。
  • 只是因为我不想比较你的两个数组中的每一个项目来弄清楚你的意思,你能给我们一些你认为相似的示例字符串吗?

标签: php function


【解决方案1】:

您有多种选择。

对于每个选项,您可能应该在进行比较之前按摩专辑名称。您可以通过去除标点符号、按字母顺序(在某些情况下)对专辑名称中的单词进行排序等来做到这一点。

在每种情况下,当您进行比较时,如果您从数组中删除其中一个专辑名称,那么您的比较是顺序敏感的,除非您制定了要删除哪个专辑名称的规则。 因此,如果比较两个专辑名称并发现“相似”,则始终删除较长的专辑名称可能是有意义的。

主要的比较选项是

  1. 简单的子字符串比较。检查专辑名称是否在另一个专辑中。先去掉标点符号,不区分大小写比较(见下面我的第二个代码 sn-p)。

  2. 使用 levenshtein() 检查专辑名称的相似性。此字符串比较比similar_text() 更有效。您应该去掉标点符号并按字母顺序排列单词。

  3. 使用 similar_text() 检查专辑名称的相似性。我对这种方法最幸运。事实上,我可以选择你想要的确切专辑名称(参见下面的第一个代码 sn-p)。

  4. 您还可以使用各种其他字符串比较函数,包括 soundex()metaphone()

无论如何...这里有 2 个解决方案。

第一个使用similar_text()...但它仅在去除所有标点符号并将单词按字母顺序排列并小写后才计算相似度.... .. 缺点是你必须玩弄阈值相似性...第二个使用简单的不区分大小写的子字符串测试,在所有标点符号和空格都被剥离之后。

两个代码 sn-ps 的工作方式是它们使用 array_walk() 在数组中的每个专辑上运行 compare() 函数。然后在compare() 函数中,我使用foreach() 将当前专辑与所有其他专辑进行比较。有足够的空间让事情变得更有效率。

请注意,我应该在array_walk 中使用第三个参数作为参考,有人可以帮我这样做吗?当前的解决方法是一个全局变量:


Live example(69% 相似度阈值)


function compare($value, $key)
{
    global $array; // Should use 3rd argument of compare instead

    $value = strtolower(preg_replace("/[^a-zA-Z0-9 ]/", "", $value));
    $value = explode(" ", $value);
    sort($value);
    $value = implode($value);
    $value = preg_replace("/[\s]/", "", $value); // Remove any leftover \s

    foreach($array as $key2 => $value2)
    {
        if ($key != $key2)
        {
            // collapse, and lower case the string            
            $value2 = strtolower(preg_replace("/[^a-zA-Z0-9 ]/", "", $value2));
            $value2 = explode(" ", $value2);
            sort($value2);
            $value2 = implode($value2);            
            $value2 = preg_replace("/[\s]/", "", $value2);

              // Set up the similarity
            similar_text($value, $value2, $sim);
            if ($sim > 69)
            {     // Remove the longer album name
                unset($array[ ((strlen($value) > strlen($value2))?$key:$key2) ]);
            }
        }
    }
}
array_walk($array, 'compare');
$array = array_values($array);
print_r($array);

上面的输出是:

Array
(
    [0] => Band of Horses - Is There a Ghost
    [1] => Band Of Horses - No One's Gonna Love You
    [2] => Band of Horses - The Funeral
    [3] => Band of Horses - Laredo
    [4] => Band of Horses - "The Great Salt Lake" Sub Pop Records
    [5] => Band of Horses perform Marry Song at Tromso Wedding
    [6] => Band of Horses, On My Way Back Home
    [7] => Band of Horses - cigarettes wedding bands
    [8] => Band Of Horses - I Go To The Barn Because I Like The
    [9] => Our Swords - Band of Horses
    [10] => Band of Horses - Monsters
)

请注意,玛丽的歌曲的 短版 版本丢失了......所以它一定是对其他东西的误报,因为长版本仍在列表中......但是它们正是您想要的专辑名称。


子串方法:

Live Example


function compare($value, $key)
{
      // I should be using &$array as a 3rd variable.
      // For some reason couldn't get that to work, so I do this instead.
    global $array;   
      // Take the current album name and remove all punctuation and white space
    $value = preg_replace("/[^a-zA-Z0-9]/", "", $value);        
      // Compare current album to all othes
    foreach($array as $key2 => $value2)
    {
        if ($key != $key2)
        {

              // collapse the album being compared to
            $value2 = preg_replace("/[^a-zA-Z0-9]/", "", $value2);

            $subject = $value2;
            $pattern = '/' . $value . '/i';

              // If there's a much remove the album being compared to
            if (preg_match($pattern, $subject))
            {
                unset($array[$key2]);
            }
        }
    }
}
array_walk($array, 'compare');
$array = array_values($array);
echo "<pre>";
print_r($array);
echo "</pre>";

对于您的示例字符串,上面的输出(它显示了您不想显示的 2 个):

Array  
(  
    [0] => Band of Horses - Is There a Ghost  
    [1] => Band Of Horses - No One's Gonna Love You  
    [2] => Band of Horses - The Funeral  
    [3] => Band of Horses - Laredo  
    [4] => Band of Horses - "The Great Salt Lake" Sub Pop Records  
    [5] => Band of Horses perform Marry Song at Tromso Wedding      // <== Oops
    [6] => 'Laredo' by Band of Horses on Q TV                       // <== Oops  
    [7] => Band of Horses, On My Way Back Home  
    [8] => Band of Horses - cigarettes wedding bands  
    [9] => Band Of Horses - I Go To The Barn Because I Like The  
    [10] => Our Swords - Band of Horses  
    [11] => Band Of Horses - "Marry song"  
    [12] => Band of Horses - Monsters  
)

【讨论】:

    【解决方案2】:

    您可能想尝试similar_text,可能与levenshtein 结合使用,并通过实验确定您认为足够相似的分数的阈值。另请查看user discussions 以获得更多提示。然后,您可以遍历数组,将每个元素与其他元素进行比较,并删除您认为过于相似的元素。

    我希望这对您来说是一个开始。这个问题相当复杂,因为有很多东西可能被认为具有相同的内容,但具有完全不同的语法(“我们的剑 - 马群”与“马群 - 我们的剑”)。这取决于这个相当简单的解决方案是否足以满足您的要求。

    【讨论】:

    【解决方案3】:

    这是我的(有点复杂?)解决方案。

    它将输入字符串拆分为单词数组 (getWords)。然后,它将它们相互比较,按“平等”(titlesMatch)对它们进行分组,这不关心词序。它存储匹配组的数组,以便您查看相似的标题。

    这是脚本(假设$array 是输入):

    function getWords($str) {
        // Remove non-alpha characters and split by spaces
        $normalized = preg_replace('/[^a-z0-9\s]/', '', strtolower($str));
        $words = preg_split('/\s+/', $normalized, -1, PREG_SPLIT_NO_EMPTY);
    
        return $words;
    }
    
    function titlesMatch($words1, $words2) {
        $intersection = array_intersect($words1, $words2);
    
        sort($words1);
        sort($words2);
        sort($intersection);
    
        return $intersection === $words1 || $intersection === $words2;
    }
    
    $wordedArray = array_map('getWords', $array);
    
    $uniqueItems = array();
    
    foreach ($wordedArray as $words1Index => $words1) {
        $isUnique = true;
    
        foreach ($uniqueItems as &$words2Indices) {
            foreach ($words2Indices as $words2Index) {
                if (titlesMatch($words1, $wordedArray[$words2Index])) {
                    $words2Indices[] = $words1Index;
                    $isUnique = false;
    
                    break;
                }
            }
        }
    
        if ($isUnique) {
            $uniqueItems[] = array($words1Index);
        }
    }
    
    // Show the first matches as an example
    foreach ($uniqueItems as $indices) {
        echo $array[$indices[0]] . "\n";
    }
    

    输入数据的输出:

    Band of Horses - Is There a Ghost
    Band Of Horses - No One's Gonna Love You
    Band of Horses - The Funeral
    Band of Horses - Laredo
    Band of Horses - "The Great Salt Lake" Sub Pop Records
    Band of Horses perform Marry Song at Tromso Wedding
    Band of Horses, On My Way Back Home
    Band of Horses - cigarettes wedding bands
    Band Of Horses - I Go To The Barn Because I Like The
    Our Swords - Band of Horses
    Band of Horses - Monsters
    

    (注意:这看起来 O(n3) 但实际上是 O(n2)。)

    【讨论】:

    • 这个取决于订单。首先按最短短语排序可以帮助缓解这种情况。它也会在拼写错误或略有不同的单词上失败,但是,当然,这也使得它不太容易出现误报......
    【解决方案4】:

    最佳实施很大程度上取决于您的数据。您对数据了解得越多,就能以最少的工作量获得更好的结果。无论如何,这是我放在一起的示例脚本:

    <?php
        $list = array(); # source data
    
        $groups = array();
    
        foreach ($list as $item)
        {
            $words = array_unique(explode(' ', trim(preg_replace('/[^a-z]+/', ' ', strtolower($item)))));
    
            $matches = array();
    
            foreach ($groups as $i => $group)
            {
                foreach ($group as $g)
                {
                    if (count($words) < count($g['words']))
                    {
                        $a = $words;
                        $b = $g['words'];
                    }
                    else
                    {
                        $a = $g['words'];
                        $b = $words;
                    }
    
                    $c = 0;
                    foreach ($a as $word1)
                    {
                        foreach ($b as $word2)
                        {
                            if (levenshtein($word1, $word2) < 2)
                            {
                                ++$c;
                                break;
                            }
                        }
                    }
    
                    if ($c / count($a) > 0.85)
                    {
                        $matches[] = $i;
                        continue 2;
                    }
                }           
            }
    
            $me = array('item' => $item, 'words' => $words);
    
            if (!$matches)
                $groups[] = array($me);
            else
            {
                for ($i = 1; $i < count($matches); ++$i)
                {
                    $groups[$matches[0]] = array_merge($groups[$matches[0]], $groups[$matches[$i]]);
                    unset($groups[$matches[$i]]);
                }
    
                $groups[$matches[0]][] = $me;
            }
        }
    
        foreach ($groups as $group)
        {
            echo $group[0]['item']."\n";
            for ($i = 1; $i < count($group); ++$i)
                echo "\t".$group[$i]['item']."\n";
        }
    ?>
    

    您的列表的输出:

    Band of Horses - Is There a Ghost
    Band Of Horses - No One's Gonna Love You
        Band Of Horses - "No One's Gonna Love You"
        Band Of Horses - No One's Gonna Love You
        Band Of Horses - No One's Gonna Love You
    Band of Horses - The Funeral
        Band of Horses - The Funeral (lyrics in description)
    Band of Horses - Laredo
        Band Of Horses - Laredo on Letterman 5.20.10
        'Laredo' by Band of Horses on Q TV
    Band of Horses - "The Great Salt Lake" Sub Pop Records
    Band of Horses perform Marry Song at Tromso Wedding
        Band Of Horses - "Marry song"
    Band of Horses, On My Way Back Home
    Band of Horses - cigarettes wedding bands
        Band Of Horses - "Cigarettes Wedding Bands"
    Band Of Horses - I Go To The Barn Because I Like The
    Our Swords - Band of Horses
    Band of Horses - Monsters
    

    这里的基本原则是将类似的列表项组合在一起。任何进入的新项目都会与现有组进行比较。较短的项目与较大的项目进行检查。如果足够多的单词 (85%) 足够接近(2 个字符不同),则将其视为匹配,并将其添加到列表中。

    如果您调整参数,这对您来说可能已经足够了。其他需要考虑的事项:完全忽略小词、相似的短语等。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-23
      • 2014-06-22
      • 2011-05-19
      • 1970-01-01
      • 1970-01-01
      • 2016-11-14
      相关资源
      最近更新 更多