Javascript: негативний погляд за еквівалентом?


141

Чи є спосіб досягти еквівалента негативного огляду в регулярних виразах javascript? Мені потрібно відповідати рядок, який не починається з певного набору символів.

Здається, я не в змозі знайти регулярний вираз, який виконує це, не виходячи з ладу, якщо відповідна частина буде знайдена на початку рядка. Негативні погляди, здається, є єдиною відповіддю, але у JavaScript немає.

EDIT: Це те, що я хотів би попрацювати, але це не так:

(?<!([abcdefg]))m

Таким чином, він би відповідав 'm' у 'jim' або 'm', але не 'jam'


Подумайте про розміщення регулярного вираження так, як це виглядатиме з негативним поглядом позаду; що може полегшити реакцію.
Даніель ЛеШемінант

1
Тим, хто хоче відстежувати питання про прийняття тощо, зверніться до таблиці сумісності ECMAScript 2016+
Wiktor Stribiżew

@ WiktorStribiżew: Огляди були додані у специфікації 2018 року. Chrome підтримує їх, але Firefox досі не застосував специфікацію .
Lonnie Best

Це навіть потребує огляду позаду? Про що (?:[^abcdefg]|^)(m)? Як в"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
слебетмен

Відповіді:


57

Зауваження, що стоять за принципом, були прийняті до специфікації ECMAScript у 2018 році.

Позитивний погляд позаду використання:

console.log(
  "$9.99  €8.47".match(/(?<=\$)\d+(\.\d*)?/) // Matches "9.99"
);

Негативний погляд позаду використання:

console.log(
  "$9.99  €8.47".match(/(?<!\$)\d+(?:\.\d*)/) // Matches "8.47"
);

Підтримка платформи:


2
чи є поліфіл?
Кіллі

1
@Killy немає, наскільки я знаю, і я сумніваюся, що коли-небудь буде, оскільки створення одного з них буде потенційно дуже непрактичним (IE пише повну реалізацію Regex в JS)
Okku

А як щодо використання плагіна Babel, чи можливо його скласти до ES5 або вже підтримуваного ES6?
Стефан Дж

1
@IlpoOksanen Я думаю, ви маєте на увазі розширення впровадження RegEx .. це те, що роблять polyfills .... і нічого поганого в написанні логіки в JavaScript
neaumusic

1
Про що ти говориш? Практично всі пропозиції натхненні іншими мовами, і вони завжди віддають перевагу збігу синтаксису та семантики інших мов, де це має сенс у контексті ідіоматичної JS та зворотної сумісності. Я думаю, що я досить чітко заявив, що як позитивні, так і негативні погляди були прийняті в специфікацію 2018 року в 2017 році, і я дав посилання на джерела. Крім того, я детально описав, які платформи реалізовують зазначену специфікацію та який статус інших платформ - і з тих пір навіть її оновлював. Звичайно, це не остання функція Regexp, яку ми побачимо
Okku

83

З 2018 року твердження Lookbehind є частиною специфікації мови ECMAScript .

// positive lookbehind
(?<=...)
// negative lookbehind
(?<!...)

Відповідь до 2018 року

Оскільки Javascript підтримує негативний пошук , один із способів зробити це:

  1. Зворотний рядок введення

  2. збігаються із зворотним регулярним виразом

  3. перевернути та переформатувати сірники


const reverse = s => s.split('').reverse().join('');

const test = (stringToTests, reversedRegexp) => stringToTests
  .map(reverse)
  .forEach((s,i) => {
    const match = reversedRegexp.test(s);
    console.log(stringToTests[i], match, 'token:', match ? reverse(reversedRegexp.exec(s)[0]) : 'Ø');
  });

Приклад 1:

Після запитання @ andrew-ensley:

test(['jim', 'm', 'jam'], /m(?!([abcdefg]))/)

Виходи:

jim true token: m
m true token: m
jam false token: Ø

Приклад 2:

Після коментаря @neaumusic (відповідає, max-heightале ні line-height, маркер height):

test(['max-height', 'line-height'], /thgieh(?!(-enil))/)

Виходи:

max-height true token: height
line-height false token: Ø

36
Проблема такого підходу полягає в тому, що він не працює, коли у вас є і вигляд, і
дихання позаду

3
ви можете, будь ласка, показати робочий приклад, скажіть, я хочу відповідати, max-heightале ні, line-heightі я хочу лише, щоб матч бувheight
неаумузичний

Не допомагає, якщо завдання полягає в заміні двох послідовних однакових символів (і не більше 2), яким не передує якийсь символ. ''(?!\()замінить апострофи ''(''test'''''''testз іншого кінця, тим самим залишаючи, (''test'NNNtestа не (''testNNN'test.
Wiktor Stribiżew

61

Припустимо, ви хочете знайти все, що intне передує unsigned:

З підтримкою негативного огляду:

(?<!unsigned )int

Без підтримки негативного огляду:

((?!unsigned ).{9}|^.{0,8})int

В основному ідея полягає в тому, щоб схопити n попередніх символів і виключити відповідність з негативним поглядом вперед, але також зіставити випадки, коли немає попередніх n символів. (де n - довжина огляду).

Отже, питання, про яке йдеться:

(?<!([abcdefg]))m

перекладається на:

((?!([abcdefg])).|^)m

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


2
Це має бути правильна відповідь. Дивіться: "So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") повертається "So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" Це досить просто і це працює!
Асрейль

41

Стратегія Mijoja працює для вашого конкретного випадку, але не загалом:

js>newString = "Fall ball bill balll llama".replace(/(ba)?ll/g,
   function($0,$1){ return $1?$0:"[match]";});
Fa[match] ball bi[match] balll [match]ama

Ось приклад, коли метою є збіг подвійного l, але не, якщо йому передує "ba". Зауважте, що слово "balll" - справжній вигляд позаду повинен був придушити перші 2 л, але відповідати 2-й парі. Але, порівнюючи перші 2 л, а потім ігноруючи цю відповідність як хибну позитивну, двигун regexp починається з кінця цього матчу і ігнорує будь-які символи в межах помилкового додатного.


5
Ах, ти прав. Однак це набагато ближче, ніж я був раніше. Я можу прийняти це до тих пір, поки не відбудеться щось краще (наприклад, JavaScript, реально реалізуючи погляд).
Ендрю Енслі

33

Використовуйте

newString = string.replace(/([abcdefg])?m/, function($0,$1){ return $1?$0:'m';});

10
Це нічого не робить: newStringзавжди буде рівним string. Чому так багато оновлень?
MikeM

@MikeM: справа в тому, щоб просто продемонструвати техніку відповідності.
помилка

57
@bug. Демонстрація, яка нічого не робить - це дивна демонстрація. Відповідь трапляється так, ніби вона просто копіюється та вставляється, не розуміючи, як це працює. Таким чином, відсутність супровідних пояснень та неспроможність продемонструвати відповідність будь-чого.
MikeM

2
@MikeM: правило ТА є, якщо воно відповідає на запитання як написано , це правильне. OP не вказав випадок використання
помилка

7
Концепція правильна, але так, це не дуже добре. Спробуйте запустити це в консолі JS ... "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });. Це має повернутися Ji[match] Jam Mo[match][match] [match]. Але також зауважте, що, як згадувалося нижче, Джейсон може призвести до невдач у певних випадках.
Саймон Іст

11

Ви можете визначити групу, яка не захоплює, відкинувши набір символів:

(?:[^a-g])m

... який би відповідав кожному m НЕ, якому передував жоден із цих листів.


2
Я думаю, що матч насправді також висвітлює попередній персонаж.
Сем,

4
^ це правда. Клас символів представляє ... персонаж! Вся ваша група, яка не захоплює, не робить це значення доступним у контексті заміни. У вашому вираженні не йдеться про те, що "кожен m НЕ передує жодній з цих літер", він говорить "кожен m передує символу, який НЕ є жодною з цих букв"
the Flowersполовина

5
Щоб відповідь також вирішила оригінальну задачу (початок рядка), вона також повинна містити варіант, так що результуюче вираження буде (?:[^a-g]|^)m. Див. Regex101.com/r/jL1iW6/2 для прикладу запуску.
Джонні Сковдал

Використання порожнечої логіки не завжди дає бажаний ефект.
GoldBishop

2

Ось як я домігся str.split(/(?<!^)@/)для Node.js 8 (який не підтримує відставання):

str.split('').reverse().join('').split(/@(?!$)/).map(s => s.split('').reverse().join('')).reverse()

Працює? Так (unicode не перевірено). Неприємно? Так.


1

слідуючи ідеї Мійоха та виходячи з проблем, які викривав Джейсонс, у мене була така ідея; Я перевірив трохи, але не впевнений у собі, тому перевірка з боку когось більш експертного, ніж я, у js regex була б чудовою :)

var re = /(?=(..|^.?)(ll))/g
         // matches empty string position
         // whenever this position is followed by
         // a string of length equal or inferior (in case of "^")
         // to "lookbehind" value
         // + actual value we would want to match

,   str = "Fall ball bill balll llama"

,   str_done = str
,   len_difference = 0
,   doer = function (where_in_str, to_replace)
    {
        str_done = str_done.slice(0, where_in_str + len_difference)
        +   "[match]"
        +   str_done.slice(where_in_str + len_difference + to_replace.length)

        len_difference = str_done.length - str.length
            /*  if str smaller:
                    len_difference will be positive
                else will be negative
            */

    }   /*  the actual function that would do whatever we want to do
            with the matches;
            this above is only an example from Jason's */



        /*  function input of .replace(),
            only there to test the value of $behind
            and if negative, call doer() with interesting parameters */
,   checker = function ($match, $behind, $after, $where, $str)
    {
        if ($behind !== "ba")
            doer
            (
                $where + $behind.length
            ,   $after
                /*  one will choose the interesting arguments
                    to give to the doer, it's only an example */
            )
        return $match // empty string anyhow, but well
    }
str.replace(re, checker)
console.log(str_done)

мій особистий результат:

Fa[match] ball bi[match] bal[match] [match]ama

принцип полягає у виклику checkerв кожній точці рядка між будь-якими двома символами, коли ця позиція є початковою точкою:

--- будь-яка підрядка розміру того, що не потрібно (тут 'ba', таким чином ..) (якщо цей розмір відомий; інакше це може бути важче зробити)

--- --- або менше, ніж це, якщо це початок рядка: ^.?

і, слідуючи за цим,

--- що потрібно насправді шукати (тут 'll').

При кожному виклику checker, буде тест, щоб перевірити, чи не було раніше значення ll, яке ми не хочемо ( !== 'ba'); якщо це так, ми називаємо іншу функцію, і вона повинна бути саме цією ( doer), яка внесе зміни на str, якщо мета ця, або, більш загально, буде отримувати необхідні дані для ручної обробки результати сканування str.

тут ми змінюємо рядок, тому нам потрібно було слідкувати за різницею довжини, щоб компенсувати розташування, задані replace, усі розраховані на str, яка сама ніколи не змінюється.

Оскільки примітивні рядки незмінні, ми могли б використати змінну strдля зберігання результату всієї операції, але я вважав, що приклад, уже ускладнений заміною, буде зрозумілішим з іншою змінною ( str_done).

я здогадуюсь, що з точки зору виступів він повинен бути досить суворим: усі ці безглузді заміни '' на '', this str.length-1часи, плюс тут ручна заміна на doer, що означає багато нарізки ... напевно, у цьому конкретному вище випадку, що могло б будемо групуватися, розрізаючи струну лише один раз на шматочки навколо, куди ми хочемо вставити, [match]і .join()розгорнути її [match].

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

і, у checkerвипадку, якщо у кількох можливостях небажаних значень за $ позаду, нам доведеться зробити тест на ньому з ще одним регексом (щоб бути кешованим (створеним) назовні checkerнайкраще, щоб уникнути того самого об'єкта, який буде створений регулярним виразом). під час кожного заклику checker) знати, чи варто цього уникати чи ні.

сподіваюся, що я зрозумів; якщо не вагайтеся, я спробую краще. :)


1

Використовуючи ваш випадок, якщо ви хочете m щось замінити , наприклад, перетворити його в великі регістри M, ви можете зняти звідки набір у групі захоплення.

матч ([^a-g])m, замінити на$1M

"jim jam".replace(/([^a-g])m/g, "$1M")
\\jiM jam

([^a-g])відповідатиме будь-якому знаку, що не ( ^), в a-gдіапазоні, і зберігатиме його в групі першого захоплення, щоб ви могли отримати доступ до нього $1.

Таким чином , ми знаходимо imв jimі замінити його , iMщо призводить до jiM.


1

Як вже згадувалося раніше, JavaScript дозволяє виглядати відсталими зараз. У старих веб-переглядачах вам все-таки потрібне вирішення.

Б'юсь об заклад, що немає способу знайти регулярний вираз, не дивлячись позаду, який би точно приніс результат. Все, що ви можете зробити, це робота з групами. Припустимо, у вас є регулярний вираз (?<!Before)Wanted, де Wantedзнаходиться регулярний вираз, який ви хочете зіставити, і Beforeце регулярний вираз, який підраховує те, що не повинно передувати матчу. Найкраще, що ви можете зробити, це заперечувати регулярний вираз Beforeі використовувати регулярний вираз NotBefore(Wanted). Бажаний результат - перша група $1.

У вашому випадку Before=[abcdefg]це легко заперечувати NotBefore=[^abcdefg]. Тож би був регулярний вираз [^abcdefg](m). Якщо вам потрібна позиція Wanted, ви також повинні згрупуватися NotBefore, щоб бажаний результат був другою групою.

Якщо збіги Beforeшаблону мають фіксовану довжину n, тобто якщо шаблон не містить повторюваних лексем, ви можете уникнути заперечення Beforeшаблону та використання регулярного виразу (?!Before).{n}(Wanted), але все ж доведеться використовувати першу групу або використовувати регулярний вираз (?!Before)(.{n})(Wanted)і використовувати другу групи. У цьому прикладі візерунок Beforeфактично має фіксовану довжину, а саме 1, тому використовуйте регулярний вираз (?![abcdefg]).(m)або (?![abcdefg])(.)(m). Якщо вас цікавлять усі матчі, додайте gпрапор, перегляньте мій фрагмент коду:

function TestSORegEx() {
  var s = "Donald Trump doesn't like jam, but Homer Simpson does.";
  var reg = /(?![abcdefg])(.{1})(m)/gm;
  var out = "Matches and groups of the regex " + 
            "/(?![abcdefg])(.{1})(m)/gm in \ns = \"" + s + "\"";
  var match = reg.exec(s);
  while(match) {
    var start = match.index + match[1].length;
    out += "\nWhole match: " + match[0] + ", starts at: " + match.index
        +  ". Desired match: " + match[2] + ", starts at: " + start + ".";   
    match = reg.exec(s);
  }
  out += "\nResulting string after statement s.replace(reg, \"$1*$2*\")\n"
         + s.replace(reg, "$1*$2*");
  alert(out);
}

0

Це ефективно це робить

"jim".match(/[^a-g]m/)
> ["im"]
"jam".match(/[^a-g]m/)
> null

Шукати та замінювати приклад

"jim jam".replace(/([^a-g])m/g, "$1M")
> "jiM jam"

Зауважте, що негативний рядок, що переглядає, має бути довжиною 1 символ, щоб це працювало.


1
Не зовсім. У "джимі" я не хочу "я"; просто "м". І "m".match(/[^a-g]m/)кричить nullтакож. Я хочу також "м" і в цьому випадку.
Ендрю Енслі

-1

/(?![abcdefg])[^abcdefg]m/gi так, це хитрість.


5
Перевірка (?![abcdefg])є абсолютно зайвою, оскільки вона [^abcdefg]вже робить свою роботу, щоб не допустити відповідності цих персонажів.
nhahtdh

2
Це не збігається з 'm' без попередніх символів.
Ендрю Енслі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.