"Asdf" क्यों है .replace (/.* / g, "x") == "xx"?


129

मैं एक आश्चर्यजनक (मेरे लिए) तथ्य को ठोकर खा गया।

console.log("asdf".replace(/.*/g, "x"));

दो प्रतिस्थापन क्यों ? ऐसा लगता है कि न्यूलाइन के बिना कोई भी गैर-रिक्त स्ट्रिंग इस पैटर्न के लिए बिल्कुल दो प्रतिस्थापन का उत्पादन करेगी। एक प्रतिस्थापन फ़ंक्शन का उपयोग करते हुए, मैं देख सकता हूं कि पहला प्रतिस्थापन पूरे स्ट्रिंग के लिए है, और दूसरा खाली स्ट्रिंग के लिए है।


9
अधिक सरल उदाहरण: "asdf".match(/.*/g)वापसी ["asdf", ""]
Narro

32
वैश्विक (छ) ध्वज के कारण। वैश्विक ध्वज पिछले मैच के अंत में एक और खोज शुरू करने की अनुमति देता है, इस प्रकार एक खाली स्ट्रिंग ढूंढ रहा है।
सेलसिएस

6
और ईमानदारी से चलो: शायद कोई भी वास्तव में ऐसा व्यवहार नहीं चाहता था। यह संभवत: "aa".replace(/b*/, "b")परिणाम का इच्छुक कार्यान्वयन कार्यान्वयन था babab। और कुछ बिंदु पर हमने वेबसर्वर्स के सभी कार्यान्वयन विवरणों को मानकीकृत किया।
लक्स

4
@ गोनू जीएनयू सेड के पुराने संस्करण (अन्य कार्यान्वयन नहीं!) भी इस बग को प्रदर्शित कर रहे थे, जो कि 2.05 और 3.01 रिलीज (20+ साल पहले) के बीच कहीं तय किया गया था। मुझे संदेह है कि यह व्यवहार कहां से उत्पन्न हुआ है, पर्ल में अपना रास्ता बनाने से पहले (जहां यह एक विशेषता बन गई थी) और वहां से जावास्क्रिप्ट में।
मॉसवी

1
@recursive - पर्याप्त रूप से उचित। मैं उन दोनों को एक सेकंड के लिए आश्चर्यचकित करता हूं, फिर "शून्य-चौड़ाई मैच" का एहसास करता हूं और अब आश्चर्यचकित नहीं हूं। :-)
टीजे क्राउडर

जवाबों:


98

के अनुसार ECMA-262 मानक, String.prototype.replace कॉल RegExp.prototype [@@ की जगह] है, जो कहते हैं:

11. Repeat, while done is false
  a. Let result be ? RegExpExec(rx, S).
  b. If result is null, set done to true.
  c. Else result is not null,
    i. Append result to the end of results.
    ii. If global is false, set done to true.
    iii. Else,
      1. Let matchStr be ? ToString(? Get(result, "0")).
      2. If matchStr is the empty String, then
        a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")).
        b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
        c. Perform ? Set(rx, "lastIndex", nextIndex, true).

कहाँ rxहै /.*/gऔर Sहै 'asdf'

11.c.iii.2.b देखें:

ख। नेक्स्टइन्डेक्स एडवांसस्ट्रीमिंगइंडेक्स (S, thisIndex, fullUnicode) होने दें।

इसलिए 'asdf'.replace(/.*/g, 'x')वास्तव में यह है:

  1. परिणाम (अपरिभाषित), परिणाम = [], lastIndex =0
  2. परिणाम = 'asdf', परिणाम = [ 'asdf' ], lastIndex =4
  3. परिणाम = '', परिणाम = [ 'asdf', '' ], lastIndex = 4,, AdvanceStringIndexlastIndex को सेट करें5
  4. परिणाम = null, परिणाम = [ 'asdf', '' ], वापसी

इसलिए 2 मैच हैं।


42
इस उत्तर को समझने के लिए मुझे इसका अध्ययन करने की आवश्यकता है।
फेलिप

टीएल; डीआर यह है कि यह मेल खाता है 'asdf'और खाली स्ट्रिंग है ''
जिम्ह

34

यवकत के साथ एक ऑफ़लाइन चैट में , हमने यह देखने का एक सहज तरीका पाया कि "abcd".replace(/.*/g, "x")वास्तव में दो मैचों का उत्पादन क्यों होता है। ध्यान दें कि हमने जाँच नहीं की है कि क्या यह ECMAScript मानक द्वारा लगाए गए शब्दार्थ को पूरी तरह से समान करता है, इसलिए इसे केवल एक नियम के रूप में लें।

अंगूठे का नियम

  • (matchStr, matchIndex)कालानुक्रमिक क्रम में ट्यूल की सूची के रूप में मैचों पर विचार करें जो इंगित करता है कि इनपुट स्ट्रिंग के किन भागों और सूचकांकों को पहले ही खा लिया गया है।
  • यह सूची लगातार रेगेक्स के लिए इनपुट स्ट्रिंग के बाईं ओर से शुरू होती है।
  • पहले से खाए गए भागों का अब मिलान नहीं किया जा सकता है
  • प्रतिस्थापन उस स्थिति में विकल्प को matchIndexअधिलेखित करके दिए गए सूचकांकों पर किया जाता है matchStr। यदि matchStr = "", तो "प्रतिस्थापन" प्रभावी ढंग से सम्मिलन है।

औपचारिक रूप से, मिलान और प्रतिस्थापन के कार्य को एक पाश के रूप में वर्णित किया जाता है जैसा कि दूसरे उत्तर में देखा जाता है ।

आसान उदाहरण

  1. "abcd".replace(/.*/g, "x")आउटपुट "xx":

    • मैच सूची है [("abcd", 0), ("", 4)]

      विशेष रूप से, इसमें निम्नलिखित मैच शामिल नहीं हैं जिन्हें निम्नलिखित कारणों से सोचा जा सकता है:

      • ("a", 0), ("ab", 0): परिमाणक *लालची है
      • ("b", 1), ("bc", 1): पिछले मैच की वजह से ("abcd", 0), तार "b"और "bc"पहले से ही खाया जाता है
      • ("", 4), ("", 4) (यानी दो बार): सूचकांक स्थिति 4 पहले से ही स्पष्ट मैच द्वारा खाया जाता है
    • इसलिए, प्रतिस्थापन स्ट्रिंग "x"पाया गया मैच स्ट्रिंग्स को उन स्थितियों में बिल्कुल बदल देता है: स्थिति 0 पर यह स्ट्रिंग को बदल देता है "abcd"और स्थिति 4 पर यह बदलता है ""

      यहां आप देख सकते हैं कि प्रतिस्थापन पिछले स्ट्रिंग के सही प्रतिस्थापन या एक नए स्ट्रिंग के सम्मिलन के रूप में कार्य कर सकता है।

  2. "abcd".replace(/.*?/g, "x")एक आलसी मात्रात्मक*? आउटपुट के साथ"xaxbxcxdx"

    • मैच सूची है [("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]

      पिछले उदाहरण के विपरीत, यहाँ ("a", 0), ("ab", 0), ("abc", 0), या यहाँ तक ("abcd", 0)परिमाणक के आलस्य कि सख्ती से सीमित करता है यह कम से कम मैच खोजने के लिए की वजह से शामिल नहीं हैं।

    • चूंकि सभी मैच स्ट्रिंग खाली हैं, इसलिए कोई वास्तविक प्रतिस्थापन नहीं होता है, लेकिन इसके xस्थान पर 0, 1, 2, 3, और 4 के सम्मिलन हैं ।

  3. "abcd".replace(/.+?/g, "x")एक आलसी मात्रात्मक+? आउटपुट के साथ"xxxx"

    • मैच सूची है [("a", 0), ("b", 1), ("c", 2), ("d", 3)]
  4. "abcd".replace(/.{2,}?/g, "x")एक आलसी मात्रात्मक[2,}? आउटपुट के साथ"xx"

    • मैच सूची है [("ab", 0), ("cd", 2)]
  5. "abcd".replace(/.{0}/g, "x")"xaxbxcxdx"उदाहरण 2 में उसी तर्क द्वारा आउटपुट ।

कठिन उदाहरण

हम प्रतिस्थापन के विचार का लगातार दोहन कर सकते हैं यदि हम हमेशा एक खाली स्ट्रिंग से मेल खाते हैं और उस स्थिति को नियंत्रित करते हैं जहां इस तरह के मैच हमारे लाभ के लिए होते हैं। उदाहरण के लिए, हम एक अक्षर डालने के लिए हर स्ट्रिंग में खाली स्ट्रिंग से मेल खाते हुए रेग्युलर एक्सप्रेशन बना सकते हैं:

  1. "abcdefgh".replace(/(?<=^(..)*)/g, "_"))एक साथ सकारात्मक lookbehind(?<=...) आउटपुट "_ab_cd_ef_gh_"(केवल अब तक Chrome में समर्थित)

    • मैच सूची है [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
  2. "abcdefgh".replace(/(?=(..)*$)/g, "_"))एक सकारात्मक रूपांतर(?=...) आउटपुट के साथ"_ab_cd_ef_gh_"

    • मैच सूची है [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]

4
मुझे लगता है कि इसे सहज (और बोल्ड, उस पर) कहने के लिए थोड़ा खिंचाव है। मेरे लिए यह स्टॉकहोम सिंड्रोम और पोस्ट-हॉक युक्तिकरण के समान है। आपका जवाब अच्छा है, BTW, मैं केवल JS डिज़ाइन या उस मामले के लिए डिज़ाइन की कमी के बारे में शिकायत करता हूं।
एरिक डुमिनील

7
@ EricDuminil मैंने पहले भी ऐसा सोचा था, लेकिन जवाब लिखने के बाद, स्केच्ड ग्लोबल-रेगेक्स-रिप्लेश एल्गोरिथ्म बिल्कुल वैसा ही प्रतीत होता है, जिस तरह से अगर कोई स्क्रैच से शुरू होता है तो उसके साथ आएगा। यह पसंद है while (!input not eaten up) { matchAndEat(); }। इसके अलावा, ऊपर दी गई टिप्पणियाँ संकेत देती हैं कि व्यवहार की उत्पत्ति जावास्क्रिप्ट के अस्तित्व से बहुत पहले हुई थी।
कॉमफ्रीक

2
वह हिस्सा जो अभी भी समझ में नहीं आता है ("कि मानक क्या कहता है" के अलावा किसी भी अन्य कारण के लिए) यह है कि चार-वर्ण मैच ("abcd", 0)स्थिति 4 को नहीं खाता है जहां निम्न चरित्र जाएगा, फिर भी शून्य-चरित्र मैच ("", 4)करता है स्थिति 4 खाएं जहां निम्नलिखित चरित्र जाएगा। अगर मैं इसे खरोंच से डिजाइन कर रहा था, मुझे लगता है कि मैं जिस नियम का उपयोग करूंगा वह इफ़ (str2, ix2)का पालन कर सकता है , जिससे यह मिसफिट नहीं होता है। (str1, ix1)ix2 >= ix1 + str1.length() && ix2 + str2.length() > ix1 + str1.length()
एंडर्स कासोर्ग

2
@AndersKaseorg ("abcd", 0)स्थिति नहीं खाती है 4 क्योंकि "abcd"केवल 4 वर्ण लंबे होते हैं और इसलिए केवल सूचकांक 0, 1, 2 को खाता है। मैं देख सकता हूं कि आपका तर्क कहां से आ सकता है: हम ("abcd" ⋅ ε, 0)5-वर्ण-लंबे समय तक क्यों नहीं रह सकते हैं, जहां ⋅ εसंघ और शून्य-चौड़ाई का मेल है? औपचारिक रूप से, क्योंकि "abcd" ⋅ ε = "abcd"। मैंने अंतिम मिनटों के लिए एक सहज कारण के बारे में सोचा, लेकिन एक खोजने में विफल रहा। मुझे लगता है कि किसी को हमेशा के εरूप में ही इलाज करना चाहिए ""मैं उस बग या करतब के बिना एक वैकल्पिक कार्यान्वयन के साथ खेलना पसंद करूंगा। साझा करने के लिए स्वतंत्र महसूस करें!
कॉमफ्रिक

1
यदि चार वर्ण स्ट्रिंग को चार सूचक खाने चाहिए, तो शून्य वर्ण स्ट्रिंग को कोई सूचक नहीं खाना चाहिए। आपके द्वारा किए गए किसी भी तर्क को समान रूप से दूसरे पर लागू होना चाहिए (उदाहरण के लिए "" ⋅ ε = "", हालांकि मुझे यकीन नहीं है कि आप किस अंतर को बीच में खींचने का इरादा रखते हैं ""और ε, जिसका मतलब वही है)। इसलिए अंतर को सहज नहीं बताया जा सकता है - यह बस है।
एंडर्स कासोर्ग

26

पहला मैच जाहिर है "asdf"(स्थिति [0,4])। क्योंकि वैश्विक ध्वज ( g) सेट है, यह खोज जारी है। इस बिंदु (स्थिति 4) पर, यह एक दूसरा मैच, एक रिक्त स्ट्रिंग (स्थिति [4,4]) पाता है।

याद रखें कि *शून्य या अधिक तत्वों से मेल खाता है।


4
तो तीन मैच क्यों नहीं? अंत में एक और खाली मैच हो सकता है। ठीक-ठीक दो हैं। यह व्याख्या बताती है कि दो क्यों हो सकते हैं, लेकिन एक या तीन के बजाय क्यों नहीं होना चाहिए।
पुनरावर्ती

7
नहीं, अन्य खाली स्ट्रिंग नहीं हैं। क्योंकि वह खाली स्ट्रिंग मिली है। स्थिति 4,4 पर एक खाली स्ट्रिंग, यह एक अद्वितीय परिणाम के रूप में पाया जाता है। "4,4" लेबल वाला मैच दोहराया नहीं जा सकता। शायद आप सोच सकते हैं कि स्थिति में एक खाली स्ट्रिंग है [0,0] लेकिन * ऑपरेटर तत्वों का अधिकतम संभव रिटर्न देता है। यही कारण है कि केवल 4,4 संभव है
डेविड एसके

16
हमें यह याद रखना होगा कि रीगेक्स नियमित अभिव्यक्ति नहीं हैं। नियमित अभिव्यक्तियों में, प्रत्येक दो पात्रों के बीच, साथ ही शुरुआत और अंत में अनंत रूप से कई खाली तार हैं। रेगेक्स में, विशेष रूप से रेगेक्स इंजन के विशेष स्वाद के लिए विनिर्देश के रूप में कई खाली स्ट्रिंग हैं।
जोर्ग डब्ल्यू मित्तग

7
यह सिर्फ पोस्ट-हॉक युक्तिकरण है।
मच्छी

9
@mosvy को छोड़कर यह वास्तव में उपयोग किए जाने वाले सटीक तर्क है।
हॉब्स
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.