Python: นิพจน์ทั่วไปทำงานไม่ถูกต้อง

ฉันใช้ regex ต่อไปนี้ มันควรจะค้นหาสตริง 'U.S.A.' แต่ได้รับเพียง 'A.' มีใครรู้บ้างว่าเกิดอะไรขึ้น

#INPUT
import re

text = 'That U.S.A. poster-print costs $12.40...'

print re.findall(r'([A-Z]\.)+', text)

#OUTPUT
['A.']

ผลลัพธ์ที่คาดหวัง:

['U.S.A.']

ฉันกำลังติดตามหนังสือ NLTK บทที่ 3.7 ที่นี่ มีชุดของ regex แต่มันก็ใช้งานไม่ได้ ฉันได้ลองทั้งใน Python 2.7 และ 3.4 แล้ว

>>> text = 'That U.S.A. poster-print costs $12.40...'
>>> pattern = r'''(?x)    # set flag to allow verbose regexps
...     ([A-Z]\.)+        # abbreviations, e.g. U.S.A.
...   | \w+(-\w+)*        # words with optional internal hyphens
...   | \$?\d+(\.\d+)?%?  # currency and percentages, e.g. $12.40, 82%
...   | \.\.\.            # ellipsis
...   | [][.,;"'?():-_`]  # these are separate tokens; includes ], [
... '''
>>> nltk.regexp_tokenize(text, pattern)
['That', 'U.S.A.', 'poster-print', 'costs', '$12.40', '...']

nltk.regexp_tokenize() ทำงานเหมือนกับ re.findall() ฉันคิดว่า python ของฉันที่นี่ไม่รู้จัก regex ตามที่คาดไว้ regex ที่ระบุไว้ข้างต้นให้ผลลัพธ์ดังนี้:

[('', '', ''),
 ('A.', '', ''),
 ('', '-print', ''),
 ('', '', ''),
 ('', '', '.40'),
 ('', '', '')]

person LingxB    schedule 31.01.2016    source แหล่งที่มา
comment
เนื่องจากคุณไม่ได้กล่าวถึงรูปแบบ และหากจุดประสงค์เดียวของคุณคือการค้นหา U.S.A. โดยใช้ (U.S.A.) ก็เพียงพอแล้ว   -  person    schedule 31.01.2016
comment
ดู github.com/nltk/nltk/issues/1206 และ stackoverflow.com/questions/32300437/ และ stackoverflow.com/questions/22175923/   -  person alvas    schedule 31.01.2016


คำตอบ (4)


อาจเป็นไปได้ว่าอาจเกี่ยวข้องกับวิธีการคอมไพล์ regexes ก่อนหน้านี้โดยใช้ nltk.internals.compile_regexp_to_noncapturing() ที่ถูกยกเลิกในเวอร์ชัน 3.1 ดูที่ ที่นี่)

>>> import nltk
>>> nltk.__version__
'3.0.5'
>>> pattern = r'''(?x)               # set flag to allow verbose regexps
...               ([A-Z]\.)+         # abbreviations, e.g. U.S.A.
...               | \$?\d+(\.\d+)?%? # numbers, incl. currency and percentages
...               | \w+([-']\w+)*    # words w/ optional internal hyphens/apostrophe
...               | [+/\-@&*]        # special characters with meanings
...             '''
>>> 
>>> from nltk.tokenize.regexp import RegexpTokenizer
>>> tokeniser=RegexpTokenizer(pattern)
>>> line="My weight is about 68 kg, +/- 10 grams."
>>> tokeniser.tokenize(line)
['My', 'weight', 'is', 'about', '68', 'kg', '+', '/', '-', '10', 'grams']

แต่มันใช้งานไม่ได้ใน NLTK v3.1:

>>> import nltk
>>> nltk.__version__
'3.1'
>>> pattern = r'''(?x)               # set flag to allow verbose regexps
...               ([A-Z]\.)+         # abbreviations, e.g. U.S.A.
...               | \$?\d+(\.\d+)?%? # numbers, incl. currency and percentages
...               | \w+([-']\w+)*    # words w/ optional internal hyphens/apostrophe
...               | [+/\-@&*]        # special characters with meanings
...             '''
>>> from nltk.tokenize.regexp import RegexpTokenizer
>>> tokeniser=RegexpTokenizer(pattern)
>>> line="My weight is about 68 kg, +/- 10 grams."
>>> tokeniser.tokenize(line)
[('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', '')]

ด้วยการปรับเปลี่ยนวิธีกำหนดกลุ่ม regex เล็กน้อย คุณจะสามารถใช้รูปแบบเดียวกันใน NLTK v3.1 ได้โดยใช้ regex นี้:

pattern = r"""(?x)                   # set flag to allow verbose regexps
              (?:[A-Z]\.)+           # abbreviations, e.g. U.S.A.
              |\d+(?:\.\d+)?%?       # numbers, incl. currency and percentages
              |\w+(?:[-']\w+)*       # words w/ optional internal hyphens/apostrophe
              |(?:[+/\-@&*])         # special characters with meanings
            """

ในรหัส:

>>> import nltk
>>> nltk.__version__
'3.1'
>>> pattern = r"""
... (?x)                   # set flag to allow verbose regexps
... (?:[A-Z]\.)+           # abbreviations, e.g. U.S.A.
... |\d+(?:\.\d+)?%?       # numbers, incl. currency and percentages
... |\w+(?:[-']\w+)*       # words w/ optional internal hyphens/apostrophe
... |(?:[+/\-@&*])         # special characters with meanings
... """
>>> from nltk.tokenize.regexp import RegexpTokenizer
>>> tokeniser=RegexpTokenizer(pattern)
>>> line="My weight is about 68 kg, +/- 10 grams."
>>> tokeniser.tokenize(line)
['My', 'weight', 'is', 'about', '68', 'kg', '+', '/', '-', '10', 'grams']

หากไม่มี NLTK เมื่อใช้โมดูล re ของ python เราจะเห็นว่ารูปแบบ regex แบบเก่าไม่ได้รับการสนับสนุนโดยกำเนิด:

>>> pattern1 = r"""(?x)               # set flag to allow verbose regexps
...               ([A-Z]\.)+         # abbreviations, e.g. U.S.A.
...               |\$?\d+(\.\d+)?%? # numbers, incl. currency and percentages
...               |\w+([-']\w+)*    # words w/ optional internal hyphens/apostrophe
...               |[+/\-@&*]        # special characters with meanings
...               |\S\w*                       # any sequence of word characters# 
... """            
>>> text="My weight is about 68 kg, +/- 10 grams."
>>> re.findall(pattern1, text)
[('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', ''), ('', '', '')]
>>> pattern2 = r"""(?x)                   # set flag to allow verbose regexps
...                       (?:[A-Z]\.)+           # abbreviations, e.g. U.S.A.
...                       |\d+(?:\.\d+)?%?       # numbers, incl. currency and percentages
...                       |\w+(?:[-']\w+)*       # words w/ optional internal hyphens/apostrophe
...                       |(?:[+/\-@&*])         # special characters with meanings
...                     """
>>> text="My weight is about 68 kg, +/- 10 grams."
>>> re.findall(pattern2, text)
['My', 'weight', 'is', 'about', '68', 'kg', '+', '/', '-', '10', 'grams']

หมายเหตุ: การเปลี่ยนแปลงวิธีที่ RegexpTokenizer ของ NLTK คอมไพล์ regexes จะสร้างตัวอย่างบน Regular Expression Tokenizer ของ NLTK ก็ล้าสมัยเช่นกัน

person alvas    schedule 31.01.2016

วางส่วนท้าย + หรือวางไว้ในกลุ่ม:

>>> text = 'That U.S.A. poster-print costs $12.40...'
>>> re.findall(r'([A-Z]\.)+', text)
['A.']              # wrong
>>> re.findall(r'([A-Z]\.)', text)
['U.', 'S.', 'A.']  # without '+'
>>> re.findall(r'((?:[A-Z]\.)+)', text)
['U.S.A.']          # with '+' inside the group
person Andrea Corbellini    schedule 31.01.2016

ส่วนแรกของข้อความที่ regexp ตรงกันคือ "U.S.A." เนื่องจาก ([A-Z]\.)+ ตรงกับกลุ่มแรก (ส่วนหนึ่งอยู่ในวงเล็บ) สามครั้ง อย่างไรก็ตาม คุณสามารถส่งคืนได้เพียงรายการเดียวต่อกลุ่ม ดังนั้น Python จึงเลือกรายการสุดท้ายสำหรับกลุ่มนั้น

หากคุณเปลี่ยนนิพจน์ทั่วไปเพื่อรวม "+" ในกลุ่มแทน กลุ่มจะจับคู่เพียงครั้งเดียวและส่งคืนรายการที่ตรงกันทั้งหมด เช่น (([A-Z]\.)+) หรือ ((?:[A-Z]\.)+)

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

person Jonas Berlin    schedule 31.01.2016

ปัญหาคือ "กลุ่มการจับภาพ" หรือที่รู้จักกันในชื่อวงเล็บ ซึ่งมีผลกระทบที่ไม่คาดคิดกับผลลัพธ์ของ findall(): เมื่อมีการใช้กลุ่มการจับภาพหลายครั้งในการแข่งขัน กลไก regexp จะสูญเสียการติดตามและมีสิ่งแปลกประหลาดเกิดขึ้น โดยเฉพาะอย่างยิ่ง: regexp ตรงกับ U.S.A. ทั้งหมดอย่างถูกต้อง แต่ findall จะปล่อยไว้บนพื้นและส่งคืนเฉพาะการจับภาพกลุ่มสุดท้ายเท่านั้น

ดังที่ คำตอบนี้ กล่าวไว้ โมดูล re ไม่รองรับกลุ่มการจับภาพซ้ำ แต่คุณสามารถติดตั้ง regexp ที่จัดการสิ่งนี้ได้อย่างถูกต้อง (อย่างไรก็ตาม นี่จะไม่ช่วยคุณถ้าคุณต้องการส่ง regexp ของคุณไปที่ nltk.tokenize.regexp)

อย่างไรก็ตาม เพื่อให้จับคู่ U.S.A. ได้อย่างถูกต้อง ให้ใช้สิ่งนี้: r'(?:[A-Z]\.)+', text)

>>> re.findall(r'(?:[A-Z]\.)+', text)
['U.S.A.']

คุณสามารถใช้การแก้ไขเดียวกันนี้กับรูปแบบที่ซ้ำกันทั้งหมดใน NLTK regexp และทุกอย่างจะทำงานได้อย่างถูกต้อง ตามที่ @alvas แนะนำ NLTK เคยทำการทดแทนนี้เบื้องหลัง แต่ฟีเจอร์นี้เพิ่งถูกทิ้งและแทนที่ด้วย คำเตือน ในเอกสารประกอบของ tokenizer หนังสือเล่มนี้ล้าสมัยอย่างเห็นได้ชัด @alvas ได้ยื่นรายงานข้อบกพร่องเกี่ยวกับเรื่องนี้ย้อนกลับไปในเดือนพฤศจิกายน แต่ยังไม่ ดำเนินการต่อไป...

person alexis    schedule 02.02.2016