อะไรคือความแตกต่างระหว่าง "กลุ่ม" และ "จับ" ในการแสดงออกปกติ. NET


161

ฉันสับสนเล็กน้อยเกี่ยวกับความแตกต่างระหว่าง "กลุ่ม" และ "การจับภาพ" เมื่อพูดถึงภาษานิพจน์ปกติของ. NET พิจารณารหัส C # ต่อไปนี้:

MatchCollection matches = Regex.Matches("{Q}", @"^\{([A-Z])\}$");

ฉันคาดว่าสิ่งนี้จะส่งผลให้มีการจับตัวเดียวสำหรับตัวอักษร 'Q' แต่ถ้าฉันพิมพ์คุณสมบัติของที่ส่งคืนMatchCollectionฉันเห็น:

matches.Count: 1
matches[0].Value: {Q}
        matches[0].Captures.Count: 1
                matches[0].Captures[0].Value: {Q}
        matches[0].Groups.Count: 2
                matches[0].Groups[0].Value: {Q}
                matches[0].Groups[0].Captures.Count: 1
                        matches[0].Groups[0].Captures[0].Value: {Q}
                matches[0].Groups[1].Value: Q
                matches[0].Groups[1].Captures.Count: 1
                        matches[0].Groups[1].Captures[0].Value: Q

เกิดอะไรขึ้นที่นี่? ฉันเข้าใจว่ายังมีการจับภาพสำหรับการแข่งขันทั้งหมด แต่กลุ่มจะเข้ามาได้อย่างไร และทำไมไม่matches[0].Capturesรวมการจับภาพตัวอักษร 'Q' ด้วย?

คำตอบ:


126

คุณจะไม่เป็นคนแรกที่คลุมเครือเกี่ยวกับเรื่องนี้ นี่คือสิ่งที่Jeffrey Friedlผู้โด่งดังพูดถึง (หน้า 437+):

ขึ้นอยู่กับมุมมองของคุณซึ่งจะเพิ่มมิติใหม่ที่น่าสนใจให้กับผลลัพธ์การแข่งขันหรือเพิ่มความสับสนและขยาย

และเพิ่มเติมเกี่ยวกับ:

ความแตกต่างที่สำคัญระหว่างวัตถุกลุ่มและวัตถุการจับภาพคือวัตถุแต่ละกลุ่มมีชุดของการจับภาพที่เป็นตัวแทนของการจับ คู่ตัวกลางทั้งหมดโดยกลุ่มในระหว่างการแข่งขันเช่นเดียวกับข้อความสุดท้ายที่จับคู่โดยกลุ่ม

และอีกไม่กี่หน้านี้เป็นข้อสรุปของเขา:

หลังจากได้รับเอกสาร. NET และเข้าใจสิ่งที่วัตถุเหล่านี้เพิ่มแล้วฉันมีความรู้สึกที่หลากหลายเกี่ยวกับพวกเขา ในอีกด้านหนึ่งมันเป็นนวัตกรรมที่น่าสนใจ [.. ] ในทางกลับกันดูเหมือนว่าจะเพิ่มภาระประสิทธิภาพ [.. ] ของฟังก์ชั่นที่จะไม่ใช้ในกรณีส่วนใหญ่

กล่าวอีกนัยหนึ่ง: พวกมันคล้ายกันมาก แต่บางครั้งและเมื่อมันเกิดขึ้นคุณจะพบประโยชน์สำหรับพวกมัน ก่อนที่คุณจะเติบโตหนวดเคราสีเทาตัวอีกตัวคุณอาจจะชื่นชอบการจับภาพ ...


เนื่องจากไม่ใช่สิ่งที่กล่าวมาหรือสิ่งที่กล่าวไว้ในโพสต์อื่น ๆ ดูเหมือนว่าจะตอบคำถามของคุณจริงๆให้พิจารณาสิ่งต่อไปนี้ คิดว่าการจับภาพเป็นเครื่องมือติดตามประวัติ เมื่อ regex ทำให้การแข่งขันของเขาก็ผ่านไปสตริงจากซ้ายไปขวา (ละเว้นย้อนรอยสักครู่) และเมื่อมันพบการจับคู่จับวงเล็บก็จะเก็บว่าใน$x(x เป็นหลักใด ๆ ) $1ให้การพูดของ

เอ็นจิน regex ปกติเมื่อวงเล็บซ้ำจะถูกโยนทิ้งปัจจุบัน$1และจะแทนที่ด้วยค่าใหม่ ไม่ .NET Captures[0]ซึ่งจะเก็บประวัตินี้และสถานที่ใน

หากเราเปลี่ยน regex ของคุณให้มีลักษณะดังนี้:

MatchCollection matches = Regex.Matches("{Q}{R}{S}", @"(\{[A-Z]\})+");

คุณจะสังเกตเห็นว่ากลุ่มแรกGroupจะมีหนึ่งCapturesกลุ่ม (กลุ่มแรกจะเป็นคู่แข่งขันเสมอนั่นคือเท่ากับ$0) และกลุ่มที่สองจะถือครอง{S}เช่นกลุ่มจับคู่ล่าสุดเท่านั้น อย่างไรก็ตามและนี่คือการจับถ้าคุณต้องการที่จะหาอีกสองจับพวกเขาอยู่ในCapturesที่มีการจับตัวกลางทั้งหมดและ{Q} {R}{S}

Capturesหากคุณเคยสงสัยว่าคุณจะได้รับจากหลายจับภาพซึ่งแสดงให้เห็นถึงการจับคู่สุดท้ายที่จะจับบุคคลที่เห็นได้ชัดว่ามีในสตริงคุณต้องใช้

คำสุดท้ายสำหรับคำถามสุดท้ายของคุณ: การจับคู่ทั้งหมดมักจะมีการจับหนึ่งครั้งไม่รวมกับแต่ละกลุ่ม จับเป็นเพียงที่น่าสนใจภายในกลุ่ม


1
a functionality that won't be used in the majority of casesฉันคิดว่าเขาพลาดเรือ ในระยะสั้น(?:.*?(collection info)){4,20}เพิ่มประสิทธิภาพมากขึ้นกว่าร้อยเปอร์เซ็นต์

1
@sln ไม่แน่ใจว่าคุณหมายถึงอะไรและ 'เขา' คือใคร (Friedl?) ตัวอย่างที่คุณให้ดูเหมือนไม่เกี่ยวข้องกับการสนทนานี้หรือการแสดงออกที่ใช้ นอกจากนี้ปริมาณโลภที่ไม่โลดโผนก็มีประสิทธิภาพน้อยกว่าควอนตัมโลภมากและต้องการความรู้เกี่ยวกับชุดอินพุตและการทดสอบ perf อย่างระมัดระวัง
Abel

@Abel - ฉันลงจอดที่นี่จากคำถามที่ทำเครื่องหมายว่าซ้ำกัน ฉันเห็น Friedl ที่ยกมา โพสต์นี้เก่าและต้องมีการรีเฟรชเพื่อให้ทันสมัย เฉพาะกับ Dot Net เท่านั้นที่สามารถทำได้มันเป็นสิ่งที่แยกจากคนอื่น ๆ ส่วนใหญ่ ทำลายลง: (?:..)+การวัดที่ไม่ใช่จับตัวอย่างเช่นกลุ่มโดยรวม Lazily จับคู่สิ่งใด ๆ.*?กับนิพจน์ย่อยการจับภาพ (กลุ่ม) ดำเนินการต่อไป ภายในการจับคู่ครั้งเดียวการรวบรวมกลุ่มจะเร่งรัดอาร์เรย์ของสิ่งที่ต้องการ ไม่จำเป็นต้องค้นหาต่อไปไม่มีการเข้าอีกครั้งทำให้เร็วขึ้น 10 ถึง 20 เท่าหรือมากกว่า

1
@sln คำถามนี้เกี่ยวกับสิ่งอื่นและเป็นคุณลักษณะเฉพาะเกี่ยวกับ. net ที่ไม่พบในเอนจิน regex อื่น ๆ (กลุ่ม vs จับภาพดูหัวข้อ) ฉันไม่เห็นอะไรที่ล้าสมัยที่นี่. net ยังคงทำงานเหมือนเดิมอันที่จริงส่วนนี้ไม่ได้เปลี่ยนไปเป็นเวลานานใน. net ประสิทธิภาพไม่ใช่ส่วนหนึ่งของคำถาม ใช่การจัดกลุ่มที่ไม่ใช่การจับภาพนั้นเร็วขึ้น แต่อีกครั้งหัวเรื่องที่นี่ตรงกันข้าม ทำไมโลภเร็วกว่าขี้เกียจอธิบายได้ในหลายตำราทางออนไลน์และตามหนังสือ Friedl แต่ OT ที่นี่ บางทีคำถามอื่น ๆ (ซึ่ง?) ไม่ซ้ำกันจริง?
อาเบล

2
@Abel - ฉันรู้ว่าฉันพูด แต่คุณไม่ได้ยินมัน ผมใช้ความโกรธเคืองให้กับคำสั่งนี้โดย a functionality that won't be used in the majority of casesFriedl ในความเป็นจริงมันเป็นฟังก์ชั่นที่ต้องการมากที่สุดใน regex ขี้เกียจ / โลภ? สิ่งที่เกี่ยวข้องกับความคิดเห็นของฉันคืออะไร จะช่วยให้มีจำนวนบัฟเฟอร์การจับตัวแปร มันสามารถกวาดทั้งสายในการแข่งขันเดียว หาก.*?(dog)พบสิ่งแรกdogแล้ว(?:.*?(dog))+จะพบทั้งหมด dogในสตริงทั้งหมดในการแข่งขันเดียว การเพิ่มประสิทธิภาพนั้นชัดเจน

20

กลุ่มคือสิ่งที่เราเชื่อมโยงกับกลุ่มในนิพจน์ทั่วไป

"(a[zx](b?))"

Applied to "axb" returns an array of 3 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.
group 2: b, the second group matched.

ยกเว้นว่าสิ่งเหล่านี้เป็นเพียงกลุ่ม 'จับ' กลุ่มที่ไม่ถูกจับภาพ (ใช้ไวยากรณ์ '(?:') จะไม่ถูกแสดงที่นี่

"(a[zx](?:b?))"

Applied to "axb" returns an array of 2 groups:

group 0: axb, the entire match.
group 1: axb, the first group matched.

การจับภาพเป็นสิ่งที่เราเชื่อมโยงกับ 'กลุ่มที่ถูกจับ' แต่เมื่อกลุ่มถูกนำมาใช้กับปริมาณหลายครั้งเฉพาะการแข่งขันครั้งสุดท้ายจะถูกเก็บไว้เป็นการแข่งขันของกลุ่ม อาร์เรย์ที่จับเก็บการแข่งขันทั้งหมดเหล่านี้

"(a[zx]\s+)+"

Applied to "ax az ax" returns an array of 2 captures of the second group.

group 1, capture 0 "ax "
group 1, capture 1 "az "

สำหรับคำถามสุดท้ายของคุณ - ฉันจะคิดก่อนที่จะพิจารณาในเรื่องนี้ว่าการจับภาพจะเป็นชุดคำสั่งที่จับโดยกลุ่มที่พวกเขาเป็นเจ้าของ ค่อนข้างเป็นเพียงนามแฝงของกลุ่ม [0] สวยไร้ประโยชน์ ..


คำอธิบายที่ชัดเจน (y)
Ghasan

19

สิ่งนี้สามารถอธิบายได้ด้วยตัวอย่างง่าย ๆ (และรูปภาพ)

จับคู่3:10pmกับนิพจน์ทั่วไป((\d)+):((\d)+)(am|pm)และใช้โมโนอินเทอร์แอคทีฟcsharp:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Zip(Enumerable.Range(0, int.MaxValue), (g, n) => "[" + n + "] " + g);
{ "[0] 3:10pm", "[1] 3", "[2] 3", "[3] 10", "[4] 0", "[5] pm" }

แล้วอันที่ 1 คืออะไร? ป้อนคำอธิบายรูปภาพที่นี่

เนื่องจากมีตัวเลขหลายหลักที่จับคู่กับกลุ่มที่สี่เราจึง "รับที่" นัดสุดท้ายเมื่อเราอ้างอิงกลุ่ม (โดยนัยToString()ซึ่งก็คือ) ในการแสดงการจับคู่ระดับกลางเราจำเป็นต้องเจาะลึกลงไปและอ้างอิงCapturesคุณสมบัติของกลุ่มที่มีปัญหา:

csharp> Regex.Match("3:10pm", @"((\d)+):((\d)+)(am|pm)").
      > Groups.Cast<Group>().
      > Skip(4).First().Captures.Cast<Capture>().
      > Zip(Enumerable.Range(0, int.MaxValue), (c, n) => "["+n+"] " + c);
{ "[0] 1", "[1] 0" }

ป้อนคำอธิบายรูปภาพที่นี่

มารยาทของบทความนี้


3
บทความที่ดี ภาพที่มีค่าพันคำ.
AlexWei

คุณคือดาว.
mikemay

14

จากเอกสาร MSDN :

ยูทิลิตี้ที่แท้จริงของคุณสมบัติการจับเกิดขึ้นเมื่อมีการใช้ปริมาณกับกลุ่มจับเพื่อให้กลุ่มจับหลายสตริงย่อยในการแสดงออกปกติเดียว ในกรณีนี้วัตถุกลุ่มมีข้อมูลเกี่ยวกับสตริงย่อยที่ถูกจับครั้งสุดท้ายในขณะที่คุณสมบัติจับภาพมีข้อมูลเกี่ยวกับสตริงย่อยทั้งหมดที่จับโดยกลุ่ม ในตัวอย่างต่อไปนี้นิพจน์ทั่วไป \ b (\ w + \ s *) + จับคู่ประโยคทั้งหมดที่ลงท้ายด้วยจุด กลุ่ม (\ w + \ s *) + รวบรวมคำแต่ละคำในชุดรวม เนื่องจากกลุ่มคอลเลกชันมีข้อมูลเกี่ยวกับสตริงย่อยที่ถูกจับล่าสุดเท่านั้นจึงรวบรวมคำสุดท้ายในประโยค "ประโยค" อย่างไรก็ตามแต่ละคำที่กลุ่มจับใช้ได้จากการรวบรวมที่ส่งคืนโดยคุณสมบัติการจับ


4

ลองนึกภาพคุณมีตัวอักษรdogcatcatcatและรูปแบบดังต่อไปนี้dog(cat(catcat))

ในกรณีนี้คุณมี 3 กลุ่มกลุ่มแรก ( กลุ่มหลัก ) สอดคล้องกับการแข่งขัน

จับคู่ == dogcatcatcatและ Group0 ==dogcatcatcat

กลุ่ม 1 == catcatcat

กลุ่ม 2 == catcat

แล้วมันเกี่ยวกับอะไร

ลองพิจารณาตัวอย่างเล็ก ๆ น้อย ๆ ที่เขียนใน C # (.NET) โดยใช้Regexคลาส

int matchIndex = 0;
int groupIndex = 0;
int captureIndex = 0;

foreach (Match match in Regex.Matches(
        "dogcatabcdefghidogcatkjlmnopqr", // input
        @"(dog(cat(...)(...)(...)))") // pattern
)
{
    Console.Out.WriteLine($"match{matchIndex++} = {match}");

    foreach (Group @group in match.Groups)
    {
        Console.Out.WriteLine($"\tgroup{groupIndex++} = {@group}");

        foreach (Capture capture in @group.Captures)
        {
            Console.Out.WriteLine($"\t\tcapture{captureIndex++} = {capture}");
        }

        captureIndex = 0;
    }

    groupIndex = 0;
    Console.Out.WriteLine();
        }

ผลผลิต :

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = abc
        capture0 = abc
    group4 = def
        capture0 = def
    group5 = ghi
        capture0 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

ลองวิเคราะห์การจับคู่แรก ( match0)

ที่คุณสามารถดูมีสามกลุ่มเล็ก ๆ น้อย ๆ : group3, group4และgroup5

    group3 = kjl
        capture0 = kjl
    group4 = mno
        capture0 = mno
    group5 = pqr
        capture0 = pqr

กลุ่มเหล่านั้น (3-5) ถูกสร้างขึ้นเนื่องจาก ' รูปแบบย่อย ' (...)(...)(...)ของรูปแบบหลัก (dog(cat(...)(...)(...)))

ค่าของgroup3สอดคล้องกับการจับภาพ ( capture0) (เช่นในกรณีของgroup4และgroup5) นั่นเป็นเพราะไม่มีการทำซ้ำกลุ่ม(...){3}เช่น


ตกลงขอพิจารณาอีกตัวอย่างหนึ่งที่มีกลุ่มการทำซ้ำ

ถ้าเราปรับเปลี่ยนรูปแบบการแสดงออกปกติที่จะจับคู่ (รหัสที่ปรากฏข้างต้น) จาก(dog(cat(...)(...)(...)))ไป(dog(cat(...){3}))คุณจะสังเกตเห็นว่ามีดังต่อไปนี้การทำซ้ำกลุ่ม(...){3} :

ตอนนี้มีการเปลี่ยนแปลงผลผลิต :

match0 = dogcatabcdefghi
    group0 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group1 = dogcatabcdefghi
        capture0 = dogcatabcdefghi
    group2 = catabcdefghi
        capture0 = catabcdefghi
    group3 = ghi
        capture0 = abc
        capture1 = def
        capture2 = ghi

match1 = dogcatkjlmnopqr
    group0 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group1 = dogcatkjlmnopqr
        capture0 = dogcatkjlmnopqr
    group2 = catkjlmnopqr
        capture0 = catkjlmnopqr
    group3 = pqr
        capture0 = kjl
        capture1 = mno
        capture2 = pqr

อีกครั้งมาวิเคราะห์การแข่งขันนัดแรก ( match0)

มีไม่มากมีกลุ่มเล็ก ๆ น้อย ๆ group4และgroup5เนื่องจากการ(...){3} ทำซ้ำ ( {n}ประเด็นn> = 2 ) group3พวกเขาได้รับการรวมอยู่ในกลุ่มเดียว

ในกรณีนี้group3ค่าที่สอดคล้องกับมันcapture2( การจับกุมครั้งสุดท้ายในคำอื่น ๆ )

ดังนั้นหากคุณต้องจับทั้งหมดภายใน 3 ( capture0, capture1, capture2) คุณจะต้องผ่านรอบของกลุ่มCapturesคอลเลกชัน

ข้อสรุปคือ: ใส่ใจกับวิธีที่คุณออกแบบกลุ่มรูปแบบของคุณ คุณควรคิดล่วงหน้าสิ่งที่ทำให้เกิดพฤติกรรมที่สเปคของกลุ่มเช่น(...)(...), (...){2}หรือ(.{3}){2}อื่น ๆ


หวังว่ามันจะช่วยให้หลั่งน้ำตาแสงบางอย่างเกี่ยวกับความแตกต่างระหว่างจับ , กลุ่มและตรงกันเช่นกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.