Изменить простое регулярное выражение

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

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


Ответы (2 шт):

Автор решения: Алексей Р

Если непременно нужно регуляркой, то возможен такой вариант - размещения без повторений, соединенные ИЛИ |:

прогрузить.*статус.*заказ|статус.*прогрузить.*заказ|прогрузить.*заказ.*статус|заказ.*прогрузить.*статус|статус.*заказ.*прогрузить|заказ.*статус.*прогрузить

Демо

Вариант без регулярки путем проверки вхождения подстрок:

const strings = ['Мы должны прогрузить статус заказа, чтобы он был доставлен.',
        'Заказа еще нет в системе, нужно статус заказа прогрузить.',
        'Мы должны прогрузить заказ, чтобы он был доставлен.',
        'Заказа еще нет в системе, нужно статус прогрузить.'],
    words = ['статус', 'заказ', 'грузит']

strings.forEach(s => {
    if (words.every(w => s.toLowerCase().includes(w))) {
        console.log(`${s} - ПОДХОДИТ`)
    } else {
        console.log(`${s} - НЕ ПОДХОДИТ`)
    }
})

→ Ссылка
Автор решения: EzioMercer

Основан на этом ответе: Check if all elements in array are present in a string as words using regular expressions in Javascript

Для удобства, я нужные вам слова запишу в массив:

const targetWords = ['прогрузить', 'статус', 'заказ'];

Регулярное выражение для одного слова будет выглядеть так:

(?=.*?слово)


( - группа
  ?= - посмотреть вперёд
    . - любой символ, кроме переноса строки
      * - в любом количестве
        ? - это оптимизация, которая ограничивает *, чтобы он был настолько мал, насколько это возможно
    слово - искомое слово
)

Т.о. наше регулярное выражение будет выглядеть так:

(?=.*?слово1)(?=.*?слово2)(?=.*?слово3)...(?=.*?словоN)

Небольшое отступление (начало)

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

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

(?=.*?слово)

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

(?=.*?слово1)(?=.*?слово2)

, то я говорю: "Дальше от того места где я сейчас, должно быть всё что угодно и слово1 и от того места где я сейчас, должно быть всё что угодно и слово2"

В чём же хитрости?

  1. Как вы могли заметить я пишу "от того места где я сейчас" и это не с проста. Например если я написал бы что-то типа:

    Мой (?=ангел)(?=демон)
    

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

  2. В кусок "всё что угодно" для слово1 входит и слово2 и то же самое наоборот, в кусок "всё что угодно" для слово2 входит и слово1. А потому в общем случае получается, что в кусок "всё что угодно" для слово1 будут входить слово2, слово3, ..., словоN. А потому порядок слов становится совершенно неважным, что и даёт ту гибкость, о которой вы просили

Небольшое отступление (конец)

Значит нам нужно:

  1. Для всех искомых слов создать такой макет
  2. Создать на основе макета регулярное выражение
  3. Протестировать входной текст на соответствие

const textarea = document.querySelector('textarea');
const resultsContainer = document.querySelector('.results');

const targetWords = ['статус', 'заказ', 'прогрузить'];

const regexp = new RegExp(
  `(?=.*?${targetWords.join(')(?=.*?')})`, 'g'
);


const runTests = () =>
  textarea
    .value
    .split('\n')
    .filter(x => x)
    .forEach(string => console.log(regexp.test(string)));

textarea.addEventListener('input', () => {
  console.clear();
  
  console.log(regexp.toString());
  
  runTests();
})

console.log(regexp.toString());
    
runTests();
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  --fontSize: 1.5rem;
  font-size: var(--fontSize);
}

ul {
  margin-left: var(--fontSize);
}

textarea {
  font-size: var(--fontSize);
  width: 100%;
}
<textarea rows="4">
Мы должны прогрузить статус заказа, чтобы он был доставлен
Заказа еще нет в системе, нужно статус заказа прогрузить
Мы должны прогрузить заказ, чтобы он был доставлен
Заказа еще нет в системе, нужно статус прогрузить</textarea>

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

Дополнительный материал

  1. Как вы можете заметить, в последнем предложении программа выдала false - это потому что там слово "Заказа" с большой буквы начинается, а мы в массив записали мелкими буквами. Если вам нужно искать регистро независимо, то вам нужно к флагу g добавить флаг i (gi) и тогда проблема будет исчерпана

  2. Вы так же можете заметить, что в массив мы написали слово "заказ", но во втором предложении, где мы встречаем слово "заказа" для нас это не вызывает никаких проблем, а всё потому что мы не указали что ничего не должно идти до или после наших слов. Это может вызывать проблему, если вдруг какое-то слово внтри себя имеет один из указанных вами слов, но оно для вас является ненужным

    Если вы начнёте гуглить проблему, то очень быстро наткнётесь на такую вещь \b (Word boundary assertion), что могло бы вас спасти, если бы только не одно НО, это по сути работает только для английского алфавита. И нет никакой особой конструкции для всех языков. Но я придумал (как мне кажется) очень хороший выход из ситуации - это использовать ([^\p{L}]|$|^) вместо \b

    \p{L} - означает любая буква из любого алфавита [^] - означает отрицание всего того что будет внутри [^\p{L}] - вместе означает "НЕ буква" () - означает группировку ^ - означает начало строки $ - означает конец строки | - означает ИЛИ (другими словами: алтернативы) ([^\p{L}]|$|^) - вместе будет "НЕ буква" ИЛИ конец строки ИЛИ начало строки

    Но чтобы им воспользоваться, придётся добавить ещё один флаг u (gui). Но вы так же должны понимать, что если вы решите ограничить слова, то возможные окончания, приставки и суффиксы нужно будет указывать вручную

Ниже будет код, который учитывает эти два замечания:

const textarea = document.querySelector('textarea');
const resultsContainer = document.querySelector('.results');

const targetWords = ['статус', 'заказа?', 'прогрузить'];

const _b = '([^\\p{L}]|$|^)';

const regexp = new RegExp(
  `(?=.*?${_b}${targetWords.join(`${_b})(?=.*?${_b}`)}${_b})`, 'gui'
);


const runTests = () =>
  textarea
    .value
    .split('\n')
    .filter(x => x)
    .forEach(string => console.log(regexp.test(string)));

textarea.addEventListener('input', () => {
  console.clear();
  
  console.log(regexp.toString());
  
  runTests();
})

console.log(regexp.toString());
    
runTests();
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  --fontSize: 1.5rem;
  font-size: var(--fontSize);
}

ul {
  margin-left: var(--fontSize);
}

textarea {
  font-size: var(--fontSize);
  width: 100%;
}
<textarea rows="4">
Мы должны прогрузить статус заказа, чтобы он был доставлен
Заказа еще нет в системе, нужно статус заказа прогрузить
Мы должны прогрузить заказ, чтобы он был доставлен
Заказа еще нет в системе, нужно статус прогрузить</textarea>

Как вы можете заметить, чтобы код заработал мне пришлось в массиве слов вместо заказ написать заказа?, что означает, что в конце может быть, а может не быть буква а

Для лучшего понимания настоятельно рекомендую следующие статьи:

→ Ссылка