Javascript: cái nhìn tiêu cực tương đương?


141

Có cách nào để đạt được tương đương với một cái nhìn tiêu cực trong các biểu thức thông thường javascript không? Tôi cần khớp một chuỗi không bắt đầu bằng một bộ ký tự cụ thể.

Có vẻ như tôi không thể tìm thấy một regex thực hiện điều này mà không thất bại nếu phần phù hợp được tìm thấy ở đầu chuỗi. Cái nhìn tiêu cực dường như là câu trả lời duy nhất, nhưng javascript không có.

EDIT: Đây là regex mà tôi muốn làm việc, nhưng nó không:

(?<!([abcdefg]))m

Vì vậy, nó sẽ khớp với 'm' trong 'jim' hoặc 'm', nhưng không khớp với 'kẹt'


Hãy xem xét việc đăng regex vì nó sẽ có vẻ ngoài tiêu cực; điều đó có thể làm cho nó dễ dàng hơn để đáp ứng.
Daniel LeCheminant

1
Những ai muốn theo dõi giao diện, v.v., xin vui lòng tham khảo bảng tương thích ECMAScript 2016+
Wiktor Stribiżew

@ WiktorStribiżew: Nhìn phía sau đã được thêm vào trong thông số 2018. Chrome hỗ trợ họ, nhưng Firefox vẫn chưa triển khai thông số kỹ thuật .
Lonnie hay nhất

Điều này thậm chí cần một cái nhìn phía sau? Thế còn (?:[^abcdefg]|^)(m)? Như trong"mango".match(/(?:[^abcdefg]|^)(m)/)[1]
slebetman

Câu trả lời:


57

Lookbehind Assertions đã được chấp nhận vào đặc tả ECMAScript năm 2018.

Cách sử dụng giao diện tích cực:

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

Cách sử dụng cái nhìn tiêu cực:

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

Hỗ trợ nền tảng:


2
Có polyfill nào không?
Killy

1
@Killy không có như tôi biết, và tôi nghi ngờ sẽ không bao giờ, vì việc tạo ra một thứ sẽ rất phi thực tế (IE viết một triển khai Regex đầy đủ trong JS)
Okku 21/07/18

Còn việc sử dụng plugin babel, có thể biên dịch xuống ES5 hoặc đã hỗ trợ ES6 không?
Stefan J

1
@IlpoOksanen Tôi nghĩ bạn có nghĩa là mở rộng triển khai RegEx .. đó là những gì polyfill làm .... và không có gì sai khi viết logic trong JavaScript
neaumusic

1
Bạn đang nói về cái gì vậy? Hầu như tất cả các đề xuất đều được truyền cảm hứng từ các ngôn ngữ khác và chúng sẽ luôn thích kết hợp cú pháp và ngữ nghĩa của các ngôn ngữ khác, nơi nó có ý nghĩa trong bối cảnh của thành ngữ JS và khả năng tương thích ngược. Tôi nghĩ rằng tôi khá rõ ràng rằng cả hai cái nhìn tích cực và tiêu cực đã được chấp nhận vào thông số kỹ thuật năm 2018 vào năm 2017 và tôi đã đưa ra các liên kết đến các nguồn. Hơn nữa, tôi đã mô tả chi tiết các nền tảng triển khai thông số kỹ thuật nói trên và trạng thái của các nền tảng khác là gì - và thậm chí đã cập nhật nó kể từ đó. Đương nhiên đó không phải là tính năng
Regapi

83

Kể từ năm 2018, Lookbehind Assertions là một phần của đặc tả ngôn ngữ ECMAScript .

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

Trả lời trước năm 2018

Vì Javascript hỗ trợ giao diện tiêu cực , một cách để làm điều đó là:

  1. đảo ngược chuỗi đầu vào

  2. phù hợp với một regex đảo ngược

  3. đảo ngược và định dạng lại các trận đấu


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]) : 'Ø');
  });

Ví dụ 1:

Sau câu hỏi của @ andrew-oblley:

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

Đầu ra:

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

Ví dụ 2:

Theo bình luận @neaumusic (khớp max-heightnhưng không line-height, mã thông báo height):

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

Đầu ra:

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

36
Vấn đề với cách tiếp cận này là nó không hoạt động khi bạn có cả lookahead và lookbehind
kboom

3
bạn có thể vui lòng đưa ra một ví dụ hoạt động không, nói rằng tôi muốn khớp max-heightnhưng không line-heightvà tôi chỉ muốn trận đấu diễn raheight
neaumusic

Sẽ không có ích gì nếu nhiệm vụ là thay thế hai biểu tượng giống hệt nhau liên tiếp (và không quá 2) không đi trước một số biểu tượng. ''(?!\()sẽ thay thế các dấu nháy đơn ''(''test'''''''testtừ đầu kia, do đó rời đi (''test'NNNtestchứ không phải (''testNNN'test.
Wiktor Stribiżew

60

Giả sử bạn muốn tìm tất cả intkhông có trước unsigned:

Với sự hỗ trợ cho cái nhìn tiêu cực phía sau:

(?<!unsigned )int

Không hỗ trợ cho cái nhìn tiêu cực phía sau:

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

Về cơ bản, ý tưởng là lấy n ký tự trước và loại trừ khớp với nhìn phía trước tiêu cực, nhưng cũng khớp với các trường hợp không có n ký tự đi trước. (trong đó n là chiều dài của nhìn phía sau).

Vì vậy, regex trong câu hỏi:

(?<!([abcdefg]))m

sẽ dịch sang:

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

Bạn có thể cần chơi với các nhóm bắt giữ để tìm vị trí chính xác của chuỗi mà bạn quan tâm hoặc bạn muốn thay thế một phần cụ thể bằng một thứ khác.


2
Đây phải là câu trả lời chính xác. Xem: "So it would match the 'm' in 'jim' or 'm', but not 'jam'".replace(/(j(?!([abcdefg])).|^)m/g, "$1[MATCH]") trả về "So it would match the 'm' in 'ji[MATCH]' or 'm', but not 'jam'" Nó khá đơn giản và nó hoạt động!
Asrail

41

Chiến lược của Mijoja hoạt động cho trường hợp cụ thể của bạn nhưng không nói chung:

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

Đây là một ví dụ trong đó mục tiêu là khớp với hai chữ số nhưng không phải nếu nó đứng trước "ba". Lưu ý từ "balll" - cái nhìn chân thực nên đã loại bỏ 2 l đầu tiên nhưng khớp với cặp thứ 2. Nhưng bằng cách khớp 2 l đầu tiên và sau đó bỏ qua kết quả khớp đó là dương tính giả, công cụ regrec tiến hành từ cuối trận đấu đó và bỏ qua bất kỳ ký tự nào trong dương tính giả.


5
À, bạn đúng rồi. Tuy nhiên, điều này gần hơn rất nhiều so với tôi trước đây. Tôi có thể chấp nhận điều này cho đến khi một cái gì đó tốt hơn xuất hiện (như javascript thực sự triển khai lookbehinds).
Andrew Oblley

33

Sử dụng

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

10
Điều này không làm gì cả: newStringsẽ luôn luôn bằng nhau string. Tại sao rất nhiều upvote?
MikeM

@MikeM: bởi vì điểm đơn giản là để thể hiện một kỹ thuật phù hợp.
lỗi

57
@bọ cánh cứng. Một cuộc biểu tình không làm gì cả là một kiểu biểu tình kỳ lạ. Câu trả lời xuất hiện như thể nó chỉ là bản sao và dán mà không có bất kỳ hiểu biết nào về cách thức hoạt động của nó. Do đó, thiếu giải thích đi kèm và không chứng minh được rằng bất cứ điều gì đã được khớp.
MikeM

2
@MikeM: quy tắc của SO là, nếu nó trả lời câu hỏi như đã viết , thì đúng. OP đã không chỉ định trường hợp sử dụng
lỗi

7
Khái niệm này là chính xác, nhưng vâng, nó không được demo cho lắm. Thử chạy này trong giao diện điều khiển JS ... "Jim Jam Momm m".replace(/([abcdefg])?m/g, function($0, $1){ return $1 ? $0 : '[match]'; });. Nó sẽ trở lại Ji[match] Jam Mo[match][match] [match]. Nhưng cũng lưu ý rằng như Jason đã đề cập dưới đây, nó có thể thất bại trong các trường hợp cạnh nhất định.
Simon East

11

Bạn có thể xác định một nhóm không bắt giữ bằng cách phủ định bộ ký tự của bạn:

(?:[^a-g])m

... sẽ phù hợp với mọi m KHÔNG trước bởi bất kỳ chữ cái nào.


2
Tôi nghĩ rằng trận đấu thực sự cũng sẽ bao gồm các nhân vật trước.
Sam

4
^ điều này là đúng Một lớp nhân vật đại diện cho ... một nhân vật! Tất cả nhóm không bắt giữ của bạn đang làm là không cung cấp giá trị đó trong bối cảnh thay thế. Biểu hiện của bạn không phải là "mỗi m KHÔNG đứng trước bất kỳ chữ cái nào" mà nó đang nói "mỗi m đứng trước một ký tự KHÔNG phải là bất kỳ chữ cái nào"
theflowersoftime 13/03

5
Để câu trả lời cũng giải quyết được vấn đề ban đầu (bắt đầu chuỗi), nó cũng phải bao gồm một tùy chọn, do đó regex kết quả sẽ là (?:[^a-g]|^)m. Xem regex101.com/r/jL1iW6/2 để biết ví dụ chạy.
Johny Skovdal

Sử dụng void logic không phải lúc nào cũng có hiệu quả mong muốn.
GoldBishop

2

Đây là cách tôi đã đạt được str.split(/(?<!^)@/)cho Node.js 8 (không hỗ trợ giao diện):

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

Làm? Có (unicode chưa được kiểm tra). Khó chịu? Đúng.


1

Theo ý tưởng của Mijoja và rút ra từ những vấn đề mà JasonS đã phơi bày, tôi đã có ý tưởng này; tôi đã kiểm tra một chút nhưng tôi không chắc về bản thân mình, vì vậy một xác minh của một người có chuyên môn hơn tôi trong js regex sẽ rất tuyệt :)

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)

đầu ra cá nhân của tôi:

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

nguyên tắc là gọi checker tại mỗi điểm trong chuỗi giữa hai ký tự bất kỳ, bất cứ khi nào vị trí đó là điểm bắt đầu của:

--- bất kỳ chuỗi con nào có kích thước của những gì không muốn (ở đây 'ba', do đó,.. ) (nếu kích thước đó được biết; nếu không thì có lẽ khó thực hiện hơn)

--- --- hoặc nhỏ hơn nếu đó là phần đầu của chuỗi: ^.?

và, theo đó,

--- những gì thực sự được tìm kiếm (ở đây 'll' ).

Tại mỗi cuộc gọi của checker, sẽ có một thử nghiệm để kiểm tra xem giá trị trước đó llcó phải là giá trị chúng tôi không muốn ( !== 'ba'); nếu đó là trường hợp, chúng ta gọi một hàm khác, và nó sẽ phải là hàm này ( doer) sẽ tạo ra các thay đổi trên str, nếu mục đích là cái này, hay nói chung hơn, sẽ nhập dữ liệu cần thiết để xử lý thủ công kết quả quét của str.

Ở đây, chúng tôi thay đổi chuỗi vì vậy chúng tôi cần phải theo dõi sự khác biệt về độ dài để bù lại các vị trí được cung cấp bởi replace, tất cả được tính toán str, mà chính nó không bao giờ thay đổi.

do các chuỗi nguyên thủy là bất biến, chúng ta có thể đã sử dụng biến strđể lưu trữ kết quả của toàn bộ hoạt động, nhưng tôi nghĩ ví dụ, đã phức tạp bởi các thay thế, sẽ rõ ràng hơn với một biến khác (str_done ).

Tôi đoán rằng về mặt biểu diễn thì nó phải khá khắc nghiệt: tất cả những sự thay thế vô nghĩa của '' thành '', this str.length-1lần này, cộng với việc thay thế thủ công bằng cách làm, có nghĩa là rất nhiều lát cắt ... có thể trong trường hợp cụ thể ở trên có thể được nhóm lại, bằng cách cắt chuỗi chỉ một lần thành các phần xung quanh nơi chúng ta muốn chèn [match].join()ing nó với [match]chính nó.

một điều nữa là tôi không biết làm thế nào nó sẽ xử lý các trường hợp phức tạp hơn, đó là các giá trị phức tạp cho giao diện giả mạo ... độ dài có lẽ là dữ liệu có vấn đề nhất.

và, trong checkertrường hợp có nhiều khả năng của các giá trị không mong muốn cho $ phía sau, chúng ta sẽ phải thực hiện một thử nghiệm với nó với một regex khác (được lưu trữ (tạo) bên ngoài checkerlà tốt nhất, để tránh cùng một đối tượng regex được tạo tại mỗi cuộc gọi cho checker) để biết liệu đó có phải là điều chúng ta tìm cách tránh hay không.

hy vọng tôi đã rõ ràng; Nếu không đừng ngần ngại, tôi sẽ cố gắng tốt hơn. :)


1

Sử dụng trường hợp của bạn, nếu bạn muốn thay thế m bằng một cái gì đó, ví dụ: chuyển đổi nó thành chữ hoaM , bạn có thể phủ định tập hợp trong nhóm chụp.

phù hợp ([^a-g])m, thay thế bằng$1M

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

([^a-g])sẽ khớp với bất kỳ char nào không ( ^) trong a-gphạm vi và lưu trữ nó trong nhóm chụp đầu tiên, vì vậy bạn có thể truy cập nó $1.

Vì vậy, chúng ta tìm thấy imtrong jimvà thay thế bằng iMmà kết quả trong jiM.


1

Như đã đề cập trước đây, JavaScript cho phép lookbehinds ngay bây giờ. Trong các trình duyệt cũ hơn, bạn vẫn cần một cách giải quyết.

Tôi cá rằng đầu của tôi không có cách nào để tìm một regex mà không có cái nhìn mang lại kết quả chính xác. Tất cả những gì bạn có thể làm là làm việc với các nhóm. Giả sử bạn có một regex (?<!Before)Wanted, đâu Wantedlà regex bạn muốn khớp và Beforelà regex tính ra những gì không nên trước trận đấu. Điều tốt nhất bạn có thể làm là phủ nhận regex Beforevà sử dụng regex NotBefore(Wanted). Kết quả mong muốn là nhóm đầu tiên $1.

Trong trường hợp của bạn Before=[abcdefg]đó là dễ dàng để phủ nhận NotBefore=[^abcdefg]. Vì vậy, regex sẽ là [^abcdefg](m). Nếu bạn cần vị trí của Wanted, bạn cũng phải nhóm NotBefore, để kết quả mong muốn là nhóm thứ hai.

Nếu các kết quả khớp của Beforemẫu có độ dài cố định n, nghĩa là, nếu mẫu không chứa mã thông báo lặp lại, bạn có thể tránh phủ định Beforemẫu và sử dụng biểu thức chính quy (?!Before).{n}(Wanted), nhưng vẫn phải sử dụng nhóm đầu tiên hoặc sử dụng biểu thức chính quy (?!Before)(.{n})(Wanted)và sử dụng biểu thức thứ hai nhóm. Trong ví dụ này, mẫu Beforethực sự có độ dài cố định, cụ thể là 1, vì vậy hãy sử dụng biểu thức chính quy (?![abcdefg]).(m)hoặc (?![abcdefg])(.)(m). Nếu bạn quan tâm đến tất cả các trận đấu, hãy thêm gcờ, xem đoạn mã của tôi:

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

Điều này thực sự hiệu quả

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

Tìm kiếm và thay thế ví dụ

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

Lưu ý rằng chuỗi nhìn phía sau tiêu cực phải dài 1 ký tự để hoạt động này.


1
Không hẳn. Trong "jim", tôi không muốn "i"; chỉ cho họ". Và "m".match(/[^a-g]m/)yeilds nulllà tốt. Tôi cũng muốn "m" trong trường hợp đó.
Andrew Oblley

-1

/(?![abcdefg])[^abcdefg]m/gi vâng, đây là một mẹo.


5
Việc kiểm tra (?![abcdefg])là hoàn toàn dư thừa, vì [^abcdefg]đã thực hiện công việc của mình để ngăn những nhân vật đó khớp với nhau.
nhahtdh

2
Điều này sẽ không khớp với 'm' không có ký tự trước.
Andrew Oblley
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.