О вашем запросе:
Пользователь может поместить ингредиенты в любом порядке, и он может быть разделен любым символом или строкой (пробел, запятая) или разделитель не требуется.
Заказ ингредиентов не проблема, мы увидим это позже.Но обходиться без разделителей - очень плохая идея!Рассмотрим следующий пример (фруктовый салат):
$ingredients = ['melon', 'orange', 'grape', 'apple'];
$userAnswer = 'watermelonorangegrapeapple';
Проблема очевидна: нет способа отличить «дыню» от «арбуза» с таким типом ограничения, которое приведет к ложным срабатываниям.
Не забывайте, что пользователь несет ответственность за то, что он пишет, и учится на собственных ошибках, когда он не получает желаемого результата.Другой способ состоит в том, чтобы заставить пользователя вводить ингредиенты один за другим, используя поля ввода.
Ответ пользователя должен включать все 4 ингредиента в любом порядке, и он не может иметь опечаток в названии ингредиентов..
Почему бы и нет, но на этот раз вы слишком ограничены, на мой взгляд: что, если пользователь напишет "клубнику", а не "клубнику"?Это не опечатка, я думаю, что это приемлемо.
Возможности:
Допустим, что все к лучшему в лучшемиз всех возможных миров : слова разделены и нет опечаток.
Как предложено в ранее связанном вопросе , вы можете сделать:
if ( preg_match('~(?=.*\bword1\b)(?=.*\bword2\b)(?=.*\bword3\b)(?=.*\bword4\b)~Ai', $userAnswer) ) {
//...
}
Ноэто не компактный, правильный путь вашей мечты:
- Он не учитывает разделители.
- Вы должны динамически строить шаблон для каждого ингредиентасписок.(Однако это не сложно)
- Каждый взгляд должен пройти через всю строку.
- Он не гибкий и не масштабируемый вообще.
- Если у вас есть сомненияо пунктах со 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()
, чтобы преобразовать строку перед их сравнением или написать собственное преобразование.Это предполагает необходимость предварительного изменения структуры данных, содержащей ингредиенты, чтобы упростить сравнение.