[python] سلاسل الانقسام مع محددات متعددة؟



13 Answers

re.split()

re.split (نمط ، سلسلة [، maxsplit = 0])

تقسيم السلسلة من خلال تواجد النمط. إذا تم استخدام التقاط الأقواس في نمط ، فسيتم أيضًا إرجاع نص جميع المجموعات في النموذج كجزء من القائمة الناتجة. إذا كان maxsplit غير صفري ، يحدث تقسيمات maxsplit على الأكثر ، ويتم إرجاع ما تبقى من السلسلة كعنصر أخير من القائمة. (ملاحظة عدم التوافق: في الإصدار Python 1.5 الأصلي ، تم تجاهل maxsplit. وقد تم إصلاح هذا في الإصدارات الأحدث.)

>>> re.split('\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split('(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split('\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
Question

أعتقد أن ما أريد القيام به هو مهمة شائعة إلى حد ما ، لكني لم أجد أي مرجع على الويب. لدي نص ، مع علامات ترقيم ، وأريد قائمة الكلمات.

"Hey, you - what are you doing here!?"

يجب ان يكون

['hey', 'you', 'what', 'are', 'you', 'doing', 'here']

لكن str.split() في Python لا يعمل إلا مع حجة واحدة ... لذلك لدي كل الكلمات مع علامات الترقيم بعد أن انقسمت مع المسافات البيضاء. أيه أفكار؟




هنا هو الذهاب في انقسام مع عدة deliminaters:

def msplit( str, delims ):
  w = ''
  for z in str:
    if z not in delims:
        w += z
    else:
        if len(w) > 0 :
            yield w
        w = ''
  if len(w) > 0 :
    yield w



هناك طريقة أخرى لتحقيق ذلك وهي استخدام مجموعة أدوات اللغة الطبيعية ( nltk ).

import nltk
data= "Hey, you - what are you doing here!?"
word_tokens = nltk.tokenize.regexp_tokenize(data, r'\w+')
print word_tokens

هذا يطبع: ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

العيب الأكبر لهذه الطريقة هو أنك تحتاج إلى تثبيت حزمة nltk .

الفوائد هي أنه يمكنك القيام بالكثير من الأشياء الممتعة مع بقية حزمة nltk بمجرد الحصول على الرموز الخاصة بك.




استخدم فهم القائمة لهذه الأشياء ... يبدو أسهل

data= "Hey, you - what are you doing here!?"
tokens = [c for c in data if c not in (',', ' ', '-', '!', '?')]

أجد هذا أسهل على الفهم (قراءة .. إبقاء) من استخدام regexp ، ببساطة لأنني لست جيدة في regexp ... وهذا هو الحال مع معظمنا :). أيضًا إذا كنت تعرف مجموعة الفواصل التي قد تستخدمها ، يمكنك الاحتفاظ بها في مجموعة. مع مجموعة ضخمة جدا ، قد يكون هذا أبطأ ... ولكن وحدة "إعادة" بطيئة كذلك.




أنا أعيد التعرف على بيثون وأحتاج نفس الشيء. قد يكون الحل findall أفضل ، لكني خرجت بهذا:

tokens = [x.strip() for x in data.split(',')]



join = lambda x: sum(x,[])  # a.k.a. flatten1([[1],[2,3],[4]]) -> [1,2,3,4]
# ...alternatively...
join = lambda lists: [x for l in lists for x in l]

ثم يصبح هذا ثلاثة خطوط:

fragments = [text]
for token in tokens:
    fragments = join(f.split(token) for f in fragments)

تفسير

هذا هو ما يعرف في هاسكل باسم قائمة موناد. الفكرة من وراء Monad هي أنه بمجرد "في Monad" فإنك "تبقى في Monad" حتى يأخذك شيء ما. على سبيل المثال في Haskell ، لنفترض أنك تقوم بتعيين range(n) -> [1,2,...,n] python range(n) -> [1,2,...,n] فوق قائمة. إذا كانت النتيجة عبارة عن قائمة ، فسيتم إلحاقها map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0] في المكان ، بحيث تحصل على شيء مثل map(range, [3,4,1]) -> [0,1,2,0,1,2,3,0] . يُعرف هذا باسم map-append (أو mappend ، أو ربما شيء من هذا القبيل). الفكرة هنا هي أنك حصلت على هذه العملية التي تقوم بتطبيقها (تقسيم على رمز مميز) ، وكلما قمت بذلك ، يمكنك الانضمام إلى النتيجة في القائمة.

يمكنك تجريد ذلك إلى إحدى الدوال وتضع tokens=string.punctuation افتراضيًا.

مزايا هذا النهج:

  • يمكن أن يعمل هذا النهج (على عكس النهج الساذجة المستندة إلى regex) مع الرموز ذات الطول التعسفي (والتي يمكن أن يعيدها التعبير المعتاد أيضًا مع بناء الجملة الأكثر تقدمًا).
  • أنت لا تقتصر على مجرد الرموز. يمكن أن يكون لديك منطقًا اعتباطيًا بدلاً من كل رمز ، على سبيل المثال يمكن أن تكون إحدى "الرموز المميزة" دالة تقسم وفقًا للأقواس المتداخلة.



def get_words(s):
    l = []
    w = ''
    for c in s.lower():
        if c in '-!?,. ':
            if w != '': 
                l.append(w)
            w = ''
        else:
            w = w + c
    if w != '': 
        l.append(w)
    return l

هنا هو الاستخدام:

>>> s = "Hey, you - what are you doing here!?"
>>> print get_words(s)
['hey', 'you', 'what', 'are', 'you', 'doing', 'here']



إذا كنت تريد عملية قابلة للعكس (الحفاظ على المحددات) ، يمكنك استخدام هذه الوظيفة:

def tokenizeSentence_Reversible(sentence):
    setOfDelimiters = ['.', ' ', ',', '*', ';', '!']
    listOfTokens = [sentence]

    for delimiter in setOfDelimiters:
        newListOfTokens = []
        for ind, token in enumerate(listOfTokens):
            ll = [([delimiter, w] if ind > 0 else [w]) for ind, w in enumerate(token.split(delimiter))]
            listOfTokens = [item for sublist in ll for item in sublist] # flattens.
            listOfTokens = filter(None, listOfTokens) # Removes empty tokens: ''
            newListOfTokens.extend(listOfTokens)

        listOfTokens = newListOfTokens

    return listOfTokens



جرب هذا:

import re

phrase = "Hey, you - what are you doing here!?"
matches = re.findall('\w+', phrase)
print matches

سيطبع هذا ['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']




الكثير من الإجابات ، ومع ذلك لا يمكنني العثور على أي حل يحقق كفاءة ما يطرحه عنوان الأسئلة حرفياً (مع الفصل بين عدة فواصل - بدلاً من ذلك ، فإن العديد من الإجابات تزيل أي شيء ليس كلمة ، وهو أمر مختلف). إذاً هنا إجابة للسؤال في العنوان ، الذي يعتمد على وحدة re المعايير القياسية والفعالة في بيثون:

>>> import re  # Will be splitting on: , <space> - ! ? :
>>> filter(None, re.split("[, \-!?:]+", "Hey, you - what are you doing here!?"))
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

أين:

  • يوجد \- في التعبير العادي هنا لمنع التفسير الخاص لـ - كمؤشر نطاق حرف ،
  • يتخطى + محدد واحد أو أكثر ، و
  • يزيل filter(None, …) السلاسل الفارغة التي ربما تم إنشاؤها بواسطة فواصل الأقدم واللاحقة (حيث أن السلاسل الفارغة لها قيمة منطقية زائفة).

هذا re.split() بدقة "انقسام مع فواصل متعددة" ، كما هو re.split() في عنوان السؤال.

هذا الحل لا يعاني أيضا من مشاكل مع أحرف غير ASCII في الكلمات ، وكذلك (انظر التعليق الأول على إجابة ghostdog74 ).

وحدة re كفاءة أكثر بكثير من القيام الحلقات بايثون والاختبارات "باليد".




قم بإنشاء دالة تأخذ كإدخال سلسلتين (السلسلة المصدر المراد تقسيمها وسلسلة محددات المحدد) وتخرج قائمة بالكلمات المقسمة:

def split_string(source, splitlist):
    output = []  # output list of cleaned words
    atsplit = True
    for char in source:
        if char in splitlist:
            atsplit = True
        else:
            if atsplit:
                output.append(char)  # append new word after split
                atsplit = False
            else: 
                output[-1] = output[-1] + char  # continue copying characters until next split
    return output



أولاً وقبل كل شيء ، استخدم re.compile () دائماً قبل تنفيذ أي عملية RegEx في حلقة لأنه يعمل أسرع من التشغيل العادي.

لذلك بالنسبة لمشكلتك أولاً ، قم بتجميع النمط ثم تنفيذ الإجراء عليه.

import re
DATA = "Hey, you - what are you doing here!?"
reg_tok = re.compile("[\w']+")
print reg_tok.findall(DATA)



نصيحة للمحترفين: استخدم string.translate العمليات الأسرع التي string.translate Python.

بعض الأدلة ...

أولا ، طريقة بطيئة (pprzemek آسف):

>>> import timeit
>>> S = 'Hey, you - what are you doing here!?'
>>> def my_split(s, seps):
...     res = [s]
...     for sep in seps:
...         s, res = res, []
...         for seq in s:
...             res += seq.split(sep)
...     return res
... 
>>> timeit.Timer('my_split(S, punctuation)', 'from __main__ import S,my_split; from string import punctuation').timeit()
54.65477919578552

بعد ذلك ، نستخدم re.findall() (كما هو re.findall() في الإجابة المقترحة). أسرع بكثير:

>>> timeit.Timer('findall(r"\w+", S)', 'from __main__ import S; from re import findall').timeit()
4.194725036621094

أخيرًا ، نستخدم translate :

>>> from string import translate,maketrans,punctuation 
>>> T = maketrans(punctuation, ' '*len(punctuation))
>>> timeit.Timer('translate(S, T).split()', 'from __main__ import S,T,translate').timeit()
1.2835021018981934

تفسير:

يتم تنفيذ string.translate في C وعلى عكس العديد من وظائف معالجة السلسلة في Python ، لا ينتج string.translate سلسلة جديدة. لذا فالأمر بأسرع ما يمكن أن تحصل عليه لاستبدال السلسلة.

إنه أمر محرج بعض الشيء ، رغم أنه يحتاج إلى جدول ترجمة من أجل القيام بهذا السحر. يمكنك إنشاء جدول ترجمة مع وظيفة الراحة maketrans() . الهدف هنا هو ترجمة جميع الأحرف غير المرغوب فيها إلى مسافات. بديل واحد مقابل واحد. مرة أخرى ، لا يتم إنتاج أي بيانات جديدة. لذلك هذا سريع !

المقبل ، ونحن نستخدم split() القديم الجيد split() . سيعمل split() افتراضيًا على جميع أحرف المسافات البيضاء ، مع تجميعها معًا للانقسام. ستكون النتيجة قائمة الكلمات التي تريدها. وهذا النهج هو ما يقرب من 4x أسرع من re.findall() !




هيريس لي على ذلك ....

def split_string(source,splitlist):
    splits = frozenset(splitlist)
    l = []
    s1 = ""
    for c in source:
        if c in splits:
            if s1:
                l.append(s1)
                s1 = ""
        else:
            print s1
            s1 = s1 + c
    if s1:
        l.append(s1)
    return l

>>>out = split_string("First Name,Last Name,Street Address,City,State,Zip Code",",")
>>>print out
>>>['First Name', 'Last Name', 'Street Address', 'City', 'State', 'Zip Code']



Related