python join - سلاسل الانقسام مع محددات متعددة؟




string split (24)

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

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

يجب ان يكون

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

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


Answers

نصيحة للمحترفين: استخدم 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() !


حالة يتم فيها تبرير التعبيرات العادية:

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

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

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

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

هنا الجواب مع بعض الشرح.

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

# replace all the non alpha-numeric with space and then join.
new_string = ''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])
# output of new_string
'Hey  you  what are you doing here  '

# str.split() will remove all the empty string if separator is not provided
new_list = new_string.split()

# output of new_list
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

# we can join it to get a complete string without any non alpha-numeric character
' '.join(new_list)
# output
'Hey you what are you doing'

أو في سطر واحد ، يمكننا القيام بذلك على النحو التالي:

(''.join([x.replace(x, ' ') if not x.isalnum() else x for x in st])).split()

# output
['Hey', 'you', 'what', 'are', 'you', 'doing', 'here']

إجابة محدثة


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

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']

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

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

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

حصلت على نفس المشكلة كماooboo والعثور على هذا الموضوع @ ghostdog74 ألهمني ، ربما شخص ما يجد حل بلدي مفيد

str1='adj:sg:nom:m1.m2.m3:pos'
splitat=':.'
''.join([ s if s not in splitat else ' ' for s in str1]).split()

إدخال شيء ما في مكان فضائي وتقسيمه باستخدام نفس الحرف إذا كنت لا تريد الانقسام على مسافات.


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

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

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


الكثير من الإجابات ، ومع ذلك لا يمكنني العثور على أي حل يحقق كفاءة ما يطرحه عنوان الأسئلة حرفياً (مع الفصل بين عدة فواصل - بدلاً من ذلك ، فإن العديد من الإجابات تزيل أي شيء ليس كلمة ، وهو أمر مختلف). إذاً هنا إجابة للسؤال في العنوان ، الذي يعتمد على وحدة 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 كفاءة أكثر بكثير من القيام الحلقات بايثون والاختبارات "باليد".


باستخدام maketrans وترجمة يمكنك القيام بذلك بسهولة وبشكل مرتب

import string
specials = ',.!?:;"()<>[]#$=-/'
trans = string.maketrans(specials, ' '*len(specials))
body = body.translate(trans)
words = body.strip().split()

جرب هذا:

import re

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

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


أولاً ، لا أعتقد أن نيتك هي استخدام علامات الترقيم كمحددات في وظائف التقسيم. يقترح الوصف الخاص بك أنك تريد ببساطة إزالة علامات الترقيم من السلاسل الناتجة.

جئت عبر هذا كثيرا جدا ، ولا يتطلب حل بلدي المعتاد إعادة.

وظيفة lambda واحدة بطانة w / list الفهم:

(يتطلب import string ):

split_without_punc = lambda text : [word.strip(string.punctuation) for word in 
    text.split() if word.strip(string.punctuation) != '']

# Call function
split_without_punc("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']


وظيفة (تقليدي)

كوظيفة تقليدية ، لا يزال هذا سطرين فقط مع فهم قائمة (بالإضافة إلى import string ):

def split_without_punctuation2(text):

    # Split by whitespace
    words = text.split()

    # Strip punctuation from each word
    return [word.strip(ignore) for word in words if word.strip(ignore) != '']

split_without_punctuation2("Hey, you -- what are you doing?!")
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

كما أنه سيؤدي بطبيعة الحال إلى ترك التقلصات والكلمات الواصلة سليمة. يمكنك دائمًا استخدام text.replace("-", " ") لتحويل الواصلات إلى مسافات قبل الانقسام.

الوظيفة العامة w / o Lambda أو قائمة الإدراك

للحصول على حل أكثر عمومية (حيث يمكنك تحديد الأحرف للتخلص) ، وبدون فهم القائمة ، ستحصل على:

def split_without(text: str, ignore: str) -> list:

    # Split by whitespace
    split_string = text.split()

    # Strip any characters in the ignore string, and ignore empty strings
    words = []
    for word in split_string:
        word = word.strip(ignore)
        if word != '':
            words.append(word)

    return words

# Situation-specific call to general function
import string
final_text = split_without("Hey, you - what are you doing?!", string.punctuation)
# returns ['Hey', 'you', 'what', 'are', 'you', 'doing']

بالطبع ، يمكنك دائمًا تعميم وظيفة lambda على أي سلسلة أحرف محددة أيضًا.


كيندا إجابة متأخرة :) ، ولكن كان لي معضلة مماثلة ولم أكن أريد استخدام وحدة "إعادة".

def my_split(s, seps):
    res = [s]
    for sep in seps:
        s, res = res, []
        for seq in s:
            res += seq.split(sep)
    return res

print my_split('1111  2222 3333;4444,5555;6666', [' ', ';', ','])
['1111', '', '2222', '3333', '4444', '5555', '6666']


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']

أعتقد أن ما يلي هو أفضل إجابة لتناسب احتياجاتك:

قد يكون مناسبًا لهذه الحالة ، ولكن قد لا يكون مناسبًا لحالات أخرى.

filter(None, re.compile('[ |,|\-|!|?]').split( "Hey, you - what are you doing here!?")

هناك طريقة أخرى لتحقيق ذلك وهي استخدام مجموعة أدوات اللغة الطبيعية ( 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 بمجرد الحصول على الرموز الخاصة بك.


هناك طريقة أخرى سريعة لإجراء ذلك بدون استخدام التعبير العادي ، وهي استبدال الأحرف أولاً ، كما يلي:

>>> 'a;bcd,ef g'.replace(';',' ').replace(',',' ').split()
['a', 'bcd', 'ef', 'g']

أنا أحب طريقة replace() الأفضل. يعمل الإجراء التالي على تغيير كل الفواصل المعرفة في قائمة splitlist السلسلة إلى الفاصل الأول في splitlist ثم تقسيم النص على ذلك الفاصل الواحد. كما أنها splitlist إذا كان splitlist هو عبارة عن سلسلة فارغة. تقوم بإرجاع قائمة الكلمات ، بدون أي سلاسل فارغة بها.

def split_string(text, splitlist):
    for sep in splitlist:
        text = text.replace(sep, splitlist[0])
    return filter(None, text.split(splitlist[0])) if splitlist else [text]

أنا أحب إعادة ، ولكن هنا هو الحل الخاص بي دون ذلك:

from itertools import groupby
sep = ' ,-!?'
s = "Hey, you - what are you doing here!?"
print [''.join(g) for k, g in groupby(s, sep.__contains__) if not k]

sep .__ contains__ هي طريقة يستخدمها المشغل 'in'. في الأساس هو نفسه

lambda ch: ch in sep

ولكن أكثر ملاءمة هنا.

groupby يحصل على سلسلة لدينا وظيفة. يقوم بتجزئة السلسلة في مجموعات باستخدام هذه الوظيفة: كلما تغيرت قيمة الدالة - يتم إنشاء مجموعة جديدة. لذا ، sep .__ يحتوي على ما هو بالضبط ما نحتاجه.

يُرجع groupby تسلسلًا من الأزواج ، حيث يكون الزوج [0] نتيجةً لوظائفنا والزوج [1] هو مجموعة. باستخدام "إن لم يكن k" ، نقوم بتصفية المجموعات بفواصل (لأن نتيجة sep .__ يحتوي__ هي True على فواصل). حسنا ، هذا كل شيء - لدينا الآن سلسلة من المجموعات حيث كل كلمة كلمة (المجموعة هي في الواقع قابلة للتكرار لذلك نستخدم الانضمام لتحويله إلى سلسلة).

هذا الحل عام تمامًا ، لأنه يستخدم وظيفة لفصل السلسلة (يمكنك التقسيم بأي شرط تحتاجه). أيضًا ، لا يُنشئ سلاسل / قوائم وسيطة (يمكنك إزالة الانضمام وسيصبح التعبير كسولًا ، نظرًا لأن كل مجموعة عبارة عن مكرر)


طريقة أخرى ، دون تعبير

import string
punc = string.punctuation
thestring = "Hey, you - what are you doing here!?"
s = list(thestring)
''.join([o for o in s if not o in punc]).split()

استخدم استبدال مرتين:

a = '11223FROM33344INTO33222FROM3344'
a.replace('FROM', ',,,').replace('INTO', ',,,').split(',,,')

النتائج في:

['11223', '33344', '33222', '3344']

إليك حل يستند إلى Java 8 ، والذي يستخدم واجهة برمجة تطبيقات Stream الجديدة لجمع كل الأسطر من InputStream:

public static String toString(InputStream inputStream) {
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(inputStream));
    return reader.lines().collect(Collectors.joining(
        System.getProperty("line.separator")));
}




python string split