Сравнение строк в PHP и удаление одной из них из массива, если они похожи

скажем, у меня есть такой массив:

  • табун лошадей - есть ли призрак
  • группа лошадей - никто не будет любить Вы
  • табун лошадей-похороны
  • Band of Horses-похороны (тексты песен в описании)
  • группа лошадей - Ларедо
  • группа лошадей-Ларедо на Леттерман 5.20.10
  • табун лошадей - " Великая соль Озеро " Sub Pop Records
  • группа лошадей- " никто не собирается Любовь Ты"
  • группа лошадей исполняют песню жениться на Тромсе Свадьба
  • группа лошадей - никто не будет любить Вы
  • "Ларедо" группой лошадей на Q TV
  • табун лошадей, на обратном пути домой
  • группа лошадей - сигареты свадьбы группы
  • Группа Лошадей - " Сигареты Свадьба Группы"
  • Группа Лошадей-Я Иду В Сарай Потому Что Мне Нравится
  • наши мечи-табун лошадей
  • Группа Лошади - "замужняя песня"
  • группа лошадей - монстров
  • группа лошадей - никто не будет любить Вы

новый массив будет иметь:

  • табун лошадей - есть ли призрак
  • группа лошадей - никто не будет любить Вы
  • табун лошадей-похороны
  • группа лошадей - Ларедо
  • табун лошадей - " Великая соль Озеро " Sub Pop Records
  • группа лошадей, на обратном пути Домой
  • группа лошадей - сигареты свадьбы группы
  • Группа Лошадей-Я Иду В Сарай Потому Что Мне Нравится
  • наши мечи-табун лошадей
  • группа лошадей - "жениться песня"
  • группа лошадей - монстров

Как бы вы могли сравнить каждую строку с каждой другой строкой в списке в PHP, и если они похожи, удалите их.

Я считаю эти похожие:

  • группа лошадей - Похороны!--4-->
  • Band of Horses-похороны (текст песни в описании)

еще пример:

  • группа лошадей - Ларедо
  • группа лошадей-Ларедо на Леттерман 5.20.10

4 ответов


у вас есть несколько вариантов.

для каждого варианта вы, вероятно, должны массировать имена альбомов перед выполнением сравнения. Вы можете сделать это путем удаления знаков препинания, сортировка слов в названии альбома в алфавитном порядке (в некоторых случаях) и т. д.

в каждом случае, когда вы делаете сравнение, если вы удаляете одно из имен альбомов из массива, то ваше сравнение чувствительно к порядку, Если вы не сделаете правило о том, какое имя альбома удалить. Поэтому, вероятно, имеет смысл всегда удалять более длинное название альбома, если два названия альбомов сравниваются и оказываются "похожими"."

основные параметры сравнения

  1. простые сравнения подстрок. Проверьте, есть ли название альбома внутри другого. Сначала удалите пунктуацию и сравните регистр нечувствительно (см. мой второй фрагмент кода ниже).

  2. проверить сходство названия альбома с помощью levenshtein(). Этот сравнение строк более эффективно, чем similar_text(). Вы должны убрать знаки препинания и упорядочить слова в алфавитном порядке.

  3. проверить сходство названия альбома с помощью similar_text(). Мне больше всего повезло с этим методом. На самом деле я получил его, чтобы выбрать точные имена альбомов, которые вы хотели (см. первый фрагмент кода ниже).

  4. существуют различные другие функции сравнения строк, с которыми вы можете играть, включая soundex() и metaphone()

в любом случае... вот 2 решения.

первый использует similar_text()... но он вычисляет только сходство после того, как все знаки препинания были разделены и слова помещены в алфавитном порядке и строчными буквами...... недостатком является то, что вы должны играть с пороговым сходством... Второй использует простой тест без учета регистра подстроки после того, как все знаки препинания и пробелы будут удалены.

способ работы обоих фрагментов кода заключается в том, что они используют array_walk() запустить compare() функция на каждом альбоме в массиве. Тогда внутри , Я использую foreach() чтобы сравнить текущий альбом со всеми другими альбомами. Здесь достаточно места, чтобы сделать вещи более эффективными.

обратите внимание, что я должен быть использование 3-го аргумента в качестве ссылки в array_walk кто-нибудь может помочь мне сделать это? Текущая работа вокруг глобальной переменной:


видео (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
)

отметим, что короче версия песни Мэри отсутствует... так что это, должно быть, был ложный позитив против чего-то другого, так как долгое время версия все еще в списке..... но это именно те названия альбомов, которые вы хотели.


метода substring:

Видео


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  
)

вы можете попробовать similar_text, возможно, в сочетании с levenshtein, и с помощью экспериментов установить порог того, что оценка вы считаете достаточно похожим. Также посмотрите на обсуждение пользователей дополнительные подсказки. Затем вы можете перебирать массив, сравнивать каждый элемент с другим элементом и удалять элементы, которые вы считаете слишком похожими.

Я надеюсь, что это послужит началом для вас. Проблема довольно сложная, поскольку есть много вещей, которые можно считать имеющими одинаковое содержание, но имеют совершенно другой синтаксис ("наши мечи - полоса лошадей" против "полоса лошадей - наши мечи"). Это зависит от того, достаточно ли это упрощенное решение для того, что вы пытаетесь сделать.


вот мой (несколько сложный?) решение.

он разбивает входные строки на массив слов (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).)


лучшая реализация будет зависеть от ваших данных. Чем больше вы знаете о своих данных, тем лучшие результаты вы можете получить с наименьшим объемом работы. Во всяком случае, вот пример сценария, который я собрал:

<?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 символа разные), тогда это считается совпадением и добавляется в список.

Это может быть достаточно хорошо для вас, если вы измените параметры. Другие вещи, которые нужно учитывать: игнорирование маленьких слов вообще, похожих фраз и т. д.