Regex สำหรับคำ 10 ตัวอักษรทั้งหมดด้วยตัวอักษรที่ไม่ซ้ำกัน


23

ฉันพยายามเขียน regex ที่จะแสดงคำทั้งหมดที่มีความยาว 10 ตัวอักษรและไม่มีตัวอักษรซ้ำ

จนถึงตอนนี้ฉันได้รับ

grep --colour -Eow '(\w{10})'

ซึ่งเป็นส่วนแรกของคำถาม ฉันจะตรวจสอบหา "เอกลักษณ์" ได้อย่างไร ฉันไม่มีเบาะแสนอกเหนือจากนั้นฉันต้องใช้การอ้างอิงกลับ


1
สิ่งนี้จะต้องทำด้วย regex หรือไม่?
Hauke ​​Laging

ฉันกำลังฝึก regex ดังนั้นควรใช่ :)
ดีแลนอุส

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

3
@ BruceEdiger ตราบใดที่มีจำนวน จำกัด ของตัวอักษรในภาษา (26) และตัวอักษรในสตริง (10) มันเป็นไปได้ที่จะทำ มันเป็นเพียงรัฐจำนวนมาก แต่ไม่มีอะไรที่จะทำให้มันไม่ใช่ภาษาปกติ

1
คุณหมายถึง "คำภาษาอังกฤษทั้งหมด ... "? คุณหมายถึงการรวมคำที่สะกดด้วยเครื่องหมายยัติภังค์และอะพอสโทรฟีหรือไม่ คุณหมายถึงการรวมคำเช่นcafé, naïve, façadeหรือไม่?
hippietrail

คำตอบ:


41
grep -Eow '\w{10}' | grep -v '\(.\).*\1'

ไม่รวมคำที่มีอักขระเหมือนกันสองตัว

grep -Eow '\w{10}' | grep -v '\(.\)\1'

ไม่รวมอักขระที่มีอักขระซ้ำกัน

POSIXly:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -xE '.{10}' |
   grep -v '\(.\).*\1'

trใส่คำในบรรทัดของตนเองโดยแปลงความsเท่าเทียมกันของคำที่ไม่ใช่ตัวอักษร (ไม่ใช้ตัวอักษรcและตัวเลขและขีดเส้นใต้) เป็นอักขระขึ้นบรรทัดใหม่

หรือหนึ่งgrep:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'

(ยกเว้นบรรทัดที่น้อยกว่า 10 และมากกว่า 10 ตัวอักษรและบรรทัดที่มีตัวอักษรปรากฏอย่างน้อยสองครั้ง)

ด้วยหนึ่งgrepเดียว (grep GNU พร้อมการสนับสนุน PCRE หรือpcregrep):

grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'

นั่นคือขอบเขตของคำ ( \b) ตามด้วยลำดับของ 10 ตัวอักษรคำ (โดยมีเงื่อนไขว่าแต่ละคนจะไม่ได้ตามลำดับตัวอักษรของคำและตัวเองโดยใช้ผู้ประกอบการเชิงลบมองไปข้างหน้า PCRE (?!...))

เราโชคดีที่ทำงานได้ที่นี่เนื่องจากมีเครื่องมือ regexp ไม่มากที่ทำงานกับ backreferences ภายในส่วนที่ทำซ้ำ

โปรดทราบว่า (ด้วยรุ่น GNU grep ของฉันอย่างน้อย)

grep -Pow '(?:(\w)(?!\w*\1)){10}'

ใช้งานไม่ได้ แต่

grep -Pow '(?:(\w)(?!\w*\2)){10}'

does (as echo aa | grep -Pw '(.)\2') ซึ่งฟังดูเหมือนบั๊ก

คุณอาจต้องการ:

grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'

ถ้าคุณต้องการ\wหรือ\bพิจารณาตัวอักษรใด ๆ เป็นองค์ประกอบคำและไม่ใช่แค่ตัวอักษร ASCII ในที่ไม่ใช่ ASCII

ทางเลือกอื่น:

grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'

นั่นคือขอบเขตคำ (หนึ่งที่ไม่ได้ตามด้วยลำดับของตัวอักษรคำที่หนึ่งซ้ำ) ตามด้วย 10 ตัวอักษรคำ

สิ่งที่อาจมีที่ด้านหลังของจิตใจ:

  • การเปรียบเทียบเป็นแบบตรงตามตัวพิมพ์ใหญ่และตัวพิมพ์เล็กBabylonishตัวอย่างเช่นจะจับคู่กันเนื่องจากตัวละครทั้งหมดจะแตกต่างกันแม้ว่าจะมีสองตัวBหนึ่งตัวล่างและตัวพิมพ์ใหญ่หนึ่งตัว (ใช้-iเพื่อเปลี่ยนสิ่งนั้น)
  • สำหรับ-w, \wและ\bคำเป็นตัวอักษร (คน ASCII เฉพาะสำหรับ GNU grep สำหรับตอนนี้ที่[:alpha:]ชั้นตัวในสถานที่ของคุณถ้าใช้-Pและ(*UCP)) ตัวเลขทศนิยมหรือขีด
  • นั่นหมายความว่าc'est(คำสองคำตามนิยามของคำในภาษาฝรั่งเศส) หรือit's(คำหนึ่งคำตามคำจำกัดความภาษาอังกฤษบางคำ) หรือ(คำหนึ่งคำตามคำนิยามของคำศัพท์rendez-vousภาษาฝรั่งเศส) ไม่ถือว่าเป็นหนึ่งคำ
  • ถึงแม้จะมี(*UCP)ตัวละครที่รวม Unicode ไม่ถือเป็นองค์ประกอบของคำดังนั้นtéléphone( $'t\u00e9le\u0301phone') ถือเป็น 10 ตัวอักษรซึ่งหนึ่งในนั้นไม่ใช่อัลฟา défavorisé( $'d\u00e9favorise\u0301') จะจับคู่แม้ว่ามันจะมีสองตัวéเพราะนั่นคือ 10 ตัวอักษรอัลฟาที่แตกต่างกันทั้งหมดตามด้วยการรวมกันของสำเนียงเฉียบพลัน (ไม่ใช่อัลฟาดังนั้นจึงมีขอบเขตของคำระหว่างeและสำเนียง)

1
น่ากลัว \wไม่ตรงกับ-แม้ว่า
แกรม

@Stephane คุณสามารถโพสต์คำอธิบายสั้น ๆ เกี่ยวกับสองนิพจน์สุดท้าย
mkc

บางครั้งดูเหมือนว่า lookarounds เป็นทางออกสำหรับทุกสิ่งที่เคยเป็นไปไม่ได้กับ RE
Barmar

1
@Barmar พวกเขายังคงเป็นไปไม่ได้ด้วยการแสดงออกปกติ "นิพจน์ทั่วไป" เป็นโครงสร้างทางคณิตศาสตร์ที่อนุญาตเฉพาะโครงสร้างบางอย่างเท่านั้นคือตัวอักษรตัวอักษรคลาสตัวอักษรและ '|', '(... )', '?', '+' และ '*' ตัวดำเนินการ ใด ๆ ที่เรียกว่า "การแสดงออกปกติ" ที่ใช้ตัวดำเนินการที่ไม่ใช่ตัวใดตัวหนึ่งข้างต้นนั้นไม่ได้เป็นนิพจน์ปกติ
จูลส์

1
@Jules นี่คือ unix.stackexchange.com ไม่ใช่ math.stackexchange.com RE ทางคณิตศาสตร์นั้นไม่เกี่ยวข้องในบริบทนี้เรากำลังพูดถึงชนิดของ REs ที่คุณใช้กับ grep, PCRE และอื่น ๆ
Barmar

12

โอเค ... นี่คือวิธีที่ clunky สำหรับสตริงอักขระห้าตัว:

grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'

เพราะคุณไม่สามารถใส่อ้างอิงกลับมาในชั้นเรียนตัวอักษร (เช่น[^\1|\2]), คุณต้องใช้ในเชิงลบที่มองไปข้างหน้า(?!foo) - นี่คือคุณสมบัติ PCRE ดังนั้นคุณต้องมี-Pสวิตช์

รูปแบบสำหรับสตริงอักขระ 10 ตัวจะยาวขึ้นอย่างแน่นอน แต่มีวิธีที่สั้นกว่าโดยใช้ตัวแปรความยาวอะไรก็ได้ที่ตรงกัน ('. *') ใน lookahead:

grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'

หลังจากอ่านคำตอบการรู้แจ้งของ Stephane Chazelas ฉันรู้ว่ามีรูปแบบที่เรียบง่ายคล้ายกันสำหรับการใช้งานนี้ผ่าน-vสวิตช์ของ grep :

    (.).*\1

เนื่องจากการตรวจสอบดำเนินการครั้งละหนึ่งอักขระสิ่งนี้จะดูว่าอักขระที่กำหนดใด ๆ ก็ตามตามด้วยอักขระศูนย์หรือมากกว่านั้น ( .*) แล้วจึงจับคู่สำหรับการอ้างอิงด้านหลัง -vกลับด้านพิมพ์เฉพาะสิ่งที่ไม่ตรงกับรูปแบบนี้ สิ่งนี้ทำให้การอ้างอิงด้านหลังมีประโยชน์มากขึ้นเนื่องจากพวกเขาไม่สามารถปฏิเสธด้วยคลาสอักขระและมีนัยสำคัญ:

grep -v '\(.\).*\1'

จะทำงานเพื่อระบุสตริงของความยาวใด ๆ ที่มีอักขระเฉพาะในขณะที่:

grep -P '(.)(?!.*\1)'

จะไม่เนื่องจากจะจับคู่ส่วนต่อท้ายใด ๆ กับอักขระที่ไม่ซ้ำกัน (เช่นabcabcจับคู่เนื่องจากabcท้ายและaaaaเพราะaท้าย - สตริงใด ๆ ) นี่คือภาวะแทรกซ้อนที่เกิดจาก lookarounds เป็นศูนย์กว้าง (พวกเขาไม่กินอะไรเลย)


ทำได้ดี! นี้จะทำงานร่วมกับหนึ่งใน Q แม้ว่า
แกรม

1
ผมเชื่อว่าคุณสามารถลดความซับซ้อนคนแรกถ้าเครื่องยนต์ regex ของคุณให้ lookahead ยาวตัวแปรเชิงลบ:(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
คริสโต Creutzig

@ChristopherCreutzig: แน่นอนโทรดี ฉันได้เพิ่มสิ่งต่อไปนี้แล้ว
goldilocks

6

หากคุณไม่ต้องการทำสิ่งทั้งหมดใน regex ฉันจะทำสองขั้นตอน: จับคู่คำ 10 ตัวอักษรทั้งหมดก่อนแล้วจึงกรองให้มีเอกลักษณ์ วิธีที่สั้นที่สุดที่ฉันรู้วิธีการทำเช่นนี้คือใน Perl:

perl -nle 'MATCH:while(/\W(\w{10})\W/g){
             undef %seen;
             for(split//,$1){next MATCH if ++$seen{$_} > 1}
             print
           }' your_file

สังเกต\Wจุดยึดเพิ่มเติมเพื่อให้แน่ใจว่ามีการจับคู่คำที่มีความยาว 10 ตัวอักษรเท่านั้น


ขอบคุณ แต่ผมอยากเป็น oneliner regex :)
ดีแลนอุส

4

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

(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)

เห็นได้ชัดว่านี่เป็นการรีบออกไปจากตัวอักษรและตัวอักษรที่ใหญ่ขึ้น :-)


ฉันต้องโหวตขึ้นเพราะนี่เป็นคำตอบที่ใช้งานได้จริง แม้ว่ามันอาจเป็นวิธีที่มีประสิทธิภาพน้อยที่สุดที่ทุกคนเคยเขียน regex: P
Dylan Meeus

4

ตัวเลือก--perl-regexp(สั้น-P) ของ GNU grepใช้นิพจน์ทั่วไปที่มีประสิทธิภาพยิ่งกว่าซึ่งรวมถึงรูปแบบการมองล่วงหน้า รูปแบบต่อไปนี้มองหาตัวอักษรแต่ละตัวที่ตัวอักษรนี้ไม่ปรากฏในส่วนที่เหลือของคำว่า:

grep -Pow '((\w)(?!\w*\g{-1})){10}'

อย่างไรก็ตามพฤติกรรมรันไทม์ค่อนข้างแย่เนื่องจาก\w*สามารถมีความยาวเกือบไม่ จำกัด สามารถ จำกัด ได้\w{,8}แต่ก็ตรวจสอบเกินขีด จำกัด ของคำ 10 ตัวอักษร ดังนั้นรูปแบบต่อไปนี้ก่อนจะตรวจสอบความยาวของคำที่ถูกต้อง:

grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'

ในฐานะที่เป็นไฟล์ทดสอบฉันได้ใช้ไฟล์ขนาดใหญ่≈ 500 MB:

  • รูปแบบแรก: ≈ 43 วิ
  • รูปแบบหลัง: ≈ 15 วิ

ปรับปรุง:

ฉันไม่พบการเปลี่ยนแปลงที่สำคัญในพฤติกรรมการทำงานสำหรับผู้ประกอบการที่ไม่โลภ ( \w*?) หรือผู้ประกอบการที่เป็นเจ้าของ ( (...){10}+) bit เล็ก ๆ ได้เร็วขึ้นดูเหมือนว่าการเปลี่ยนของตัวเลือก-w:

grep -Po '\b(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}\b'

การอัพเดท grep จากเวอร์ชั่น 2.13 เป็น 2.18 นั้นมีประสิทธิภาพมากกว่า ไฟล์ทดสอบใช้เวลาเพียง s 6 วินาที


ประสิทธิภาพจะขึ้นอยู่กับลักษณะของข้อมูลเป็นอย่างมาก เมื่อทำการทดสอบบนของฉันฉันพบว่าการใช้ตัวดำเนินการที่ไม่ใช่โลภ ( \w{,8}?) ช่วยในการป้อนข้อมูลบางประเภท ใช้งานได้ดี\g{-1}เพื่อแก้ไขข้อผิดพลาด grep ของ GNU
Stéphane Chazelas

@ StepChaneChazelas: ขอบคุณสำหรับคำติชม ฉันได้ลองใช้ตัวดำเนินการที่ไม่โลภและเป็นเจ้าของและไม่พบการเปลี่ยนแปลงที่สำคัญในพฤติกรรมของรันไทม์ (เวอร์ชัน 2.13) เวอร์ชั่น 2.18 นั้นเร็วกว่ามากและฉันเห็นการปรับปรุงเล็กน้อยอย่างน้อย grep bug ของ GNU มีอยู่ในทั้งสองเวอร์ชัน อย่างไรก็ตามฉันชอบการอ้างอิงแบบสัมพัทธ์\g{-1}เพราะมันทำให้รูปแบบเป็นอิสระมากขึ้นในสถานที่ ในรูปแบบนี้มันสามารถใช้เป็นส่วนหนึ่งของรูปแบบขนาดใหญ่
Heiko Oberdiek

0

โซลูชัน Perl:

perl -lne 'print if (!/(.)(?=$1)/g && /^\w{10}$/)' file

แต่มันใช้งานไม่ได้

perl -lne 'print if (!/(.)(?=\1)/g && /^\w{10}$/)' file

หรือ

perl -lne 'print if ( /(.)(?!$1)/g && /^\w{10}$/)' file

ทดสอบกับ perl v5.14.2 และ v5.18.2


ตัวที่ 1 และตัวที่ 3 ไม่ทำอะไรเลยตัวที่ 2 จะส่งออกบรรทัดใดก็ได้ที่มีอักขระตั้งแต่ 10 ตัวขึ้นไปโดยมีช่องว่างติดต่อกันไม่เกิน 2 ช่อง pastebin.com/eEDcy02D
จัดการ

มันอาจจะเป็นรุ่น Perl ทดสอบกับ v5.14.2 และ v5.18.2

ฉันลองพวกเขาด้วย v5.14.1 บน Linux และ v5.14.2 บน Cygwin ทั้งสองประพฤติเหมือนในตัวอย่าง pastebin ที่ฉันลิงค์ไว้ก่อนหน้านี้
จัดการ

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

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