Поиск группы строк, где порядок групп не имеет значения - PullRequest
2 голосов
/ 24 июня 2019

У меня есть группы из какой-то строки, и мне нужно найти все группы по регулярному выражению, где порядок групп не имеет значения

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

$string = "banana, strawberry, cherry and chocolate";
$regex = "/(banana)*(strawberry)*(cherry)*(chocolate)/";
if (preg_match($regex, $string)) {
 // do something
}

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

1 Ответ

1 голос
/ 27 июня 2019

О вашем запросе:

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

Заказ ингредиентов не проблема, мы увидим это позже.Но обходиться без разделителей - очень плохая идея!Рассмотрим следующий пример (фруктовый салат):

$ingredients = ['melon', 'orange', 'grape', 'apple'];
$userAnswer = 'watermelonorangegrapeapple';

Проблема очевидна: нет способа отличить «дыню» от «арбуза» с таким типом ограничения, которое приведет к ложным срабатываниям.

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


Ответ пользователя должен включать все 4 ингредиента в любом порядке, и он не может иметь опечаток в названии ингредиентов..

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


Возможности:

Допустим, что все к лучшему в лучшемиз всех возможных миров : слова разделены и нет опечаток.

Как предложено в ранее связанном вопросе , вы можете сделать:

if ( preg_match('~(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b)(?=.*\bword4\b)~Ai', $userAnswer) ) {
    //...
}

Ноэто не компактный, правильный путь вашей мечты:

  1. Он не учитывает разделители.
  2. Вы должны динамически строить шаблон для каждого ингредиентасписок.(Однако это не сложно)
  3. Каждый взгляд должен пройти через всю строку.
  4. Он не гибкий и не масштабируемый вообще.
  5. Если у вас есть сомненияо пунктах со 2 по 5, см. пункт 1.

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

Basic:

$delimiter = '~ \b \s* (?: , \s* | \s and \s+ ) ~uxi';

$parts = preg_split($delimiter, $userAnswer, -1, PREG_SPLIT_NO_EMPTY);

if ( empty(array_diff($ingredients, $parts)) ) {
    // all ingredients are here
}

С очисткой:

$delimiter = '~ \b (?: [ ]? , [ ]? | [ ] and [ ] ) ~ux';

$userAnswer = trim(preg_replace('~[\s\pP]+~u', ' ', mb_strtolower($userAnswer)));

$parts = preg_split($delimiter, $userAnswer);

if ( empty(array_diff($ingredients, $parts)) ) {
    // all ingredients are here
}

С мягким сравнением между строками:

$delimiter = '~ \b (?: [ ]? , [ ]? | [ ] and [ ] ) ~ux';

$userAnswer = trim(preg_replace('~[\s\pP]+~', ' ', mb_strtolower($userAnswer)));

$parts = preg_split($delimiter, $userAnswer);

if ( empty(array_udiff($ingredients, $parts, $callback)) ) {
    // all ingredients are here
}

Пример функции обратного вызова:

Функции обратного вызова для array_udiff - это не что иное, как функции сравнения для сортировки массива, другими словами, сортировка является необходимым шагом для сравнения двух массивов.Вот почему сравнение между двумя элементами должно привести к положительному, отрицательному целому числу или 0 для определения порядка.

PHP имеет две функции для выполнения нечеткого сравнения между строками: similar_text() иlevenshtein().

Пример использования расстояния Левенштейна .Меньше 2 означает, что только один символ может быть заменен, вставлен или удален, чтобы сделать две строки равными (более подробные сведения см. В руководстве по PHP).

$callback = function ($a, $b) {
    return levenshtein($a, $b) < 2 ? 0 
                                   : ( $a < $b ? -1 : 1 ); 
}

Обратите внимание, что эти две функции могут иметь незначительные значениястоимость длинных строк, так как similar_text() - это O (max (m, n) ^ 3), а levenshtein() - это O (m * n) (m и n - это длина строк).Если это становится проблематичным, вы также можете использовать такие функции, как metaphone() или soundex(), чтобы преобразовать строку перед их сравнением или написать собственное преобразование.Это предполагает необходимость предварительного изменения структуры данных, содержащей ингредиенты, чтобы упростить сравнение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...