簡體   English   中英

在Python中解析第4行大寫字母?

[英]Parse 4th capital letter of line in Python?

如何解析大寫字母第4次出現的文本行? 例如給出以下行:

adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj
oiwuewHsajlkjfasNasldjgalskjgasdIasdllksjdgaPlsdakjfsldgjQ

我想抓住:

`ZsdalkjgalsdkjTlaksdjfgasdkgj`
`PlsdakjfsldgjQ`

我確信有可能比正則表達式更好的方式,但我試圖做一個非貪婪的比賽; 這樣的事情:

match = re.search(r'[A-Z].*?$', line).group()

我提出兩種方法。

方法1:全力以赴的正則表達式

In [1]: import re

In [2]: s = 'adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj'

In [3]: re.match(r'(?:.*?[A-Z]){3}.*?([A-Z].*)', s).group(1)
Out[3]: 'ZsdalkjgalsdkjTlaksdjfgasdkgj'

.*?[AZ]消費字符,包括第一個大寫字母。

(?: ... ){3}重復上述三次而不創建任何捕獲組。

以下.*? 匹配第四個大寫字母前的剩余字符。

最后, ([AZ].*)捕獲第四個大寫字母以及隨后進入捕獲組的所有內容。

方法2:更簡單的正則表達式

In [1]: import re

In [2]: s = 'adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj'

In [3]: ''.join(re.findall(r'[A-Z][^A-Z]*', s)[3:])
Out[3]: 'ZsdalkjgalsdkjTlaksdjfgasdkgj'

這會直接攻擊問題,我認為更容易閱讀。

無論如何不使用正則表達式將被視為過於冗長 - 雖然在bytcodelevel它是一個非常簡單的算法運行,因此輕量級。

可能是regexps更快,因為它們是用本機代碼實現的,但“一種顯而易見的方法”雖然很無聊,但在可讀性方面肯定勝過任何合適的正則表達式:

def find_capital(string, n=4):
    count = 0
    for index, letter in enumerate(string):
        # The boolean value counts as 0 for False or 1 for True
        count += letter.isupper()  
        if count == n:
            return string[index:]
    return ""

通過使用正則表達式拆分字符串,然后切片結果列表,發現這個更簡單:

import re

text = ["adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj",
        "oiwuewHsajlkjfasNasldjgalskjgasdIasdllksjdgaPlsdakjfsldgjQ"]

for t in text:
     print "".join(re.split("([A-Z])", t, maxsplit=4)[7:])

方便的是,如果沒有足夠的大寫字母,這會給你一個空字符串。

一個不錯的單線解決方案可能是:

>>> s1 = 'adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj'
>>> s2 = 'oiwuewHsajlkjfasNasldjgalskjgasdIasdllksjdgaPlsdakjfsldgjQ'
>>> s1[list(re.finditer('[A-Z]', s1))[3].start():]
'ZsdalkjgalsdkjTlaksdjfgasdkgj'
>>> s2[list(re.finditer('[A-Z]', s2))[3].start():]
'PlsdakjfsldgjQ'

為什么會這樣(只有一行)?

  • 搜索字符串中的所有大寫字母: re.finditer('[AZ]', s1)
  • 獲取第四個大寫字母: [3]
  • 返回第4個大寫字母的位置: .start()
  • 使用切片表示法,我們從字符串s1[position:]獲取我們需要的部分s1[position:]

我相信這對你有用,並且在未來很容易擴展:

check = 'adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj'
print re.match('([^A-Z]*[A-Z]){3}[^A-Z]*([A-Z].*)', check ).group(2)

正則表達式的第一部分([^AZ]*[AZ]){3}是真正的密鑰,它找到前三個大寫字母並將它們與它們之間的字符一起存儲在組1中,然后我們跳過任何數字在第三個大寫字母后面的非大寫字母,最后,我們捕獲字符串的其余部分。

測試各種方法。 我原來寫了string_after_Nth_upper並沒有發布; 看到j​​sbueno的方法是相似的; 除了對每個字符(甚至是小寫字母)進行加法/計數比較之外,他的方法稍慢。

s='adsasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj'
import re
def string_after_Nth_upper(your_str, N=4):
    upper_count = 0
    for i, c in enumerate(your_str):
        if c.isupper():
            upper_count += 1
            if upper_count == N:
               return your_str[i:]
    return ""

def find_capital(string, n=4):
    count = 0
    for index, letter in enumerate(string):
        # The boolean value counts as 0 for False or 1 for True
        count += letter.isupper()  
        if count == n:
            return string[index:]
    return ""

def regex1(s):
    return re.match(r'(?:.*?[A-Z]){3}.*?([A-Z].*)', s).group(1)
def regex2(s):
    return re.match(r'([^A-Z]*[A-Z]){3}[^A-Z]*([A-Z].*)', s).group(2)
def regex3(s):
    return s[list(re.finditer('[A-Z]', s))[3].start():]
if __name__ == '__main__':
    from timeit import Timer
    t_simple = Timer("string_after_Nth_upper(s)", "from __main__ import s, string_after_Nth_upper")
    print 'simple:', t_simple.timeit()
    t_jsbueno = Timer("find_capital(s)", "from __main__ import s, find_capital")
    print 'jsbueno:', t_jsbueno.timeit()
    t_regex1 = Timer("regex1(s)", "from __main__ import s, regex1; import re")
    print  "Regex1:",t_regex1.timeit()
    t_regex2 = Timer("regex2(s)", "from __main__ import s, regex2; import re")
    print "Regex2:", t_regex2.timeit()

    t_regex3 = Timer("regex3(s)", "from __main__ import s, regex3; import re")
    print "Regex3:", t_regex3.timeit()

結果:

Simple: 4.80558681488
jsbueno: 5.92122507095
Regex1: 3.21153497696
Regex2: 2.80767202377
Regex3: 6.64155721664

因此regex2贏得了時間。

這不是最漂亮的方法,但是:

re.match(r'([^A-Z]*[A-Z]){3}[^A-Z]*([A-Z].*)', line).group(2)
import re
strings = [
    'adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj',
    'oiwuewHsajlkjfasNasldjgalskjgasdIasdllksjdgaPlsdakjfsldgjQ',
]

for s in strings:
    m = re.match('[a-z]*[A-Z][a-z]*[A-Z][a-z]*[A-Z][a-z]*([A-Z].+)', s)
    if m:
        print m.group(1)

解析幾乎總是涉及正則表達式。 但是,正則表達式本身並不構成解析器。 從最簡單的意義上講,解析器包括:

text input stream -> tokenizer 

通常它還有一個額外的步驟:

text input stream -> tokenizer -> parser

標記生成器處理打開輸入流並以適當的方式收集文本,以便程序員不必考慮它。 它消耗文本元素,直到只有一個匹配可用。 然后它運行與此“令牌”關聯的代碼。 如果你沒有tokenizer,你必須自己滾動它(在偽代碼中):

while stuffInStream:
    currChars + getNextCharFromString
    if regex('firstCase'):
         do stuff
    elif regex('other stuff'):
         do more stuff

這個循環代碼充滿了陷阱,除非你一直構建它們。 計算機也可以很容易地從一組規則中生成它。 這就是Lex / flex的工作方式。 您可以讓與令牌關聯的規則將令牌傳遞給yacc / bison作為解析器,從而添加結構。

請注意,詞法分析器只是一個狀態機 當它從一個州遷移到另一個州時,它可以做任何事情 我寫了一些詞法分析器,用於從輸入流中刪除字符,打開文件,打印文本,發送電子郵件等等。

所以,如果您只想在第四個大寫字母后收集文本,那么正則表達式不僅合適,而且是正確的解決方案。 但是如果你想要解析文本輸入 ,使用不同的規則來做什么和未知的輸入量,那么你需要一個詞法分析器/解析器。 我建議PLY,因為你使用python。

caps = set("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
temp = ''
for char in inputStr:
if char in caps:
    temp += char
    if len(temp) == 4:
        print temp[-1] # this is the answer that you are looking for
        break

或者,您可以使用re.sub來刪除任何不是大寫字母的內容並獲取剩下的第四個字符

另一個版本......不是那么漂亮,但完成工作。

def stringafter4thupper(s):    
    i,r = 0,''
    for c in s:
        if c.isupper() and i < 4:
            i+=1
        if i==4:
            r+=c
    return r

例子:

stringafter4thupper('adsgasdlkgasYasdgjaUUalsdkjgaZsdalkjgalsdkjTlaksdjfgasdkgj')
stringafter4thupper('oiwuewHsajlkjfasNasldjgalskjgasdIasdllksjdgaPlsdakjfsldgjQ')
stringafter4thupper('')
stringafter4thupper('abcdef')
stringafter4thupper('ABCDEFGH')

分別結果:

'ZsdalkjgalsdkjTlaksdjfgasdkgj'
'PlsdakjfsldgjQ'
''
''
'DEFGH'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM