Подтвердить что ты не робот

Самый эффективный способ разделить строки в Python

Мой текущий проект Python потребует много разделения строк для обработки входящих пакетов. Поскольку я буду запускать его на довольно медленной системе, мне было интересно, какой самый эффективный способ сделать это. Строки будут отформатированы примерно так:

Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5

Объяснение: Этот конкретный пример будет получен из списка, в котором первые два элемента являются заголовком и датой, тогда как пункт 3 - пункт 5 будут приглашены людям (число может быть любым от нуля до n, где n это количество зарегистрированных пользователей на сервере).

Из того, что я вижу, у меня есть следующие опции:

  • многократно используйте split()
  • Используйте регулярное выражение и функции регулярных выражений
  • Некоторые другие функции Python, о которых я еще не думал (возможно, некоторые из них)

Решение 1 будет включать разделение в |, а затем разбиение последнего элемента результирующего списка на <> для этого примера, тогда как решение 2, вероятно, приведет к регулярному выражению, например:

((.+)|)+((.+)(<>)?)+

Хорошо, этот RegEx ужасен, я сам это вижу. Он также непроверен. Но у вас есть идея.

Теперь я ищу способ, которым a) занимает наименьшее количество времени и б) идеально использует наименьший объем памяти. Если возможно только одно из двух, я бы предпочел меньше времени. Идеальное решение также будет работать для строк, у которых больше элементов разделено с | и строками, которым полностью не хватает <>. По крайней мере, решение, основанное на регулярном выражении, сделало бы это

Мое понимание было бы в том, что split() будет использовать больше памяти (так как вы в основном получаете два результирующих списка, разделяемых на |, а второй - на <>), но я недостаточно знаю о реализации регулярных выражений Pythons, чтобы судить о том, как RegEx будет выполнять. split() также менее динамичен, чем регулярное выражение, если оно имеет значение для разных чисел элементов и отсутствия второго разделителя. Тем не менее, я не могу поколебать впечатление, что python может сделать это лучше без регулярных выражений, поэтому я прошу

Некоторые примечания:

  • Да, я мог бы просто проверить оба решения, но я пытаюсь узнать что-то о питоне вообще и о том, как он работает здесь, и если я просто сравниваю эти два, я до сих пор не знаю, какие функции python я пропустил.
  • Да, оптимизация на этом уровне действительно нужна только для высокопроизводительных вещей, но, как я уже сказал, я пытаюсь узнать о python.
  • Добавление: в исходном вопросе, я совершенно забыл упомянуть, что мне нужно различать части, разделенные | от частей с помощью разделителя <>, поэтому простой плоский список, созданный re.split(\||<>,input) (как было предложено @obmarg), не будет работать слишком хорошо. Решения, соответствующие этому критерию, очень ценятся.

Подводя вопрос: какое решение было бы наиболее эффективным, по каким причинам.

Из-за нескольких запросов я запустил некоторое время на split() -решении и первом предложенном регулярном выражении by @obmarg, а также решениях @mgibsonbr и @duncan:

import timeit
import re

def splitit(input):
    res0 = input.split("|")
    res = []
    for element in res0:
        t = element.split("<>")
        if t != [element]:
            res0.remove(element)
            res.append(t)
    return (res0, res)

def regexit(input):
    return re.split( "\||<>", input )


def mgibsonbr(input): # Solution by @mgibsonbr
    items = re.split(r'\||<>', input) # Split input in items
    offset = 0
    result = [] # The result: strings for regular itens, lists for <> separated ones
    acc = None
    for i in items:
        delimiter = '|' if offset+len(i) < len(input) and input[offset+len(i)] == '|' else '<>'
        offset += len(i) + len(delimiter)
        if delimiter == '<>': # Will always put the item in a list
            if acc is None:
                acc = [i] # Create one if doesn't exist
                result.append(acc)
            else:
                acc.append(i)
        else:
            if acc is not None: # If there was a list, put the last item in it
                acc.append(i)
            else:
                result.append(i) # Add the regular items
            acc = None # Clear the list, since what will come next is a regular item or a new list
    return result

def split2(input): # Solution by @duncan
    res0 = input.split("|")
    res1, res2 = [], []
    for r in res0:
        if "<>" in r:
            res2.append(r.split("<>"))
        else:
            res1.append(r)
    return res1, res2

print "mgibs:", timeit.Timer("mgibsonbr('a|b|c|de|f<>ge<>ah')","from __main__ import mgibsonbr").timeit()
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "mgibs:", timeit.Timer("mgibsonbr('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import mgibsonbr").timeit()
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "split:", timeit.Timer("split2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()

Результаты:

mgibs: 14.7349407408
split: 6.403942732
split2: 3.68306812233
regex: 5.28414318792
mgibs: 107.046683735
split: 46.0844590775
split2: 26.5595985591
regex: 28.6513302646

На данный момент похоже, что split2 by @duncan превосходит все остальные алгоритмы, независимо от длины (с этим ограниченным набором данных, по крайней мере), а также выглядит так: решение @mgibsonbr имеет некоторые проблемы с производительностью (извините, но, но спасибо за решение независимо).

Спасибо за ввод, все.

4b9b3361

Ответ 1

Я был немного удивлен, что split() так плохо работал в вашем коде, поэтому я посмотрел на него немного ближе и заметил, что вы вызываете list.remove() во внутреннем цикле. Также вы вызываете split() дополнительное время для каждой строки. Избавьтесь от них и решения с помощью split() бьет регулярное выражение на более коротких строках и занимает довольно близкую секунду на более длинном.

import timeit
import re

def splitit(input):
    res0 = input.split("|")
    res = []
    for element in res0:
        t = element.split("<>")
        if t != [element]:
            res0.remove(element)
            res.append(t)
    return (res0, res)

def split2(input):
    res0 = input.split("|")
    res1, res2 = [], []
    for r in res0:
        if "<>" in r:
            res2.append(r.split("<>"))
        else:
            res1.append(r)
    return res1, res2

def regexit(input):
    return re.split( "\||<>", input )


print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "split2:", timeit.Timer("split2('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import split2").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()

Что дает следующий результат:

split: 6.14933066757
split2: 4.09465555192
regex: 5.10777759588
split: 47.0092413098
split2: 26.9300592585
regex: 25.9168630604

и, конечно, split2() предоставляет вложенные списки, которые вы хотели, в то время как для решения регулярного выражения нет.

Ответ 2

Я не уверен, что это самый эффективный, но, безусловно, самый легкий для кода, выглядит примерно так:

>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> re.split( "\||<>", input )
>>> ['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']

Я бы подумал, что существует справедливая вероятность того, что он будет более эффективным, чем простой старый раскол (в зависимости от входных данных), так как вам нужно будет выполнить вторую операцию разделения на каждой строке, выводимой из первого сплита, что похоже, не эффективна ни для памяти, ни для времени.

Хотя я сказал, что могу легко ошибаться, и единственный способ убедиться, что это будет время.

Ответ 3

Вызов разбивки несколько раз, вероятно, будет неполным, поскольку он может создавать ненужные промежуточные строки. Использование регулярного выражения, как вы предложили, не будет работать, поскольку группа захвата получит только последний элемент, а не каждый из них. Разделение с использованием регулярного выражения, например, предлагаемого obmarg, кажется лучшим маршрутом, предполагая, что "сплющенный" список - это то, что вы ищете.

Если вам не нужен сплющенный список, вы можете сначала разбить его с помощью регулярного выражения, а затем перебрать результаты, проверив исходный ввод, чтобы увидеть, какой разделитель был использован:

items = re.split(r'\||<>', input)
offset = 0
for i in items:
    delimiter = '|' if input[offset+len(i)] == '|' else '<>'
    offset += len(i) + len(delimiter)
    # Do something with i, depending on whether | or <> was the delimiter

Наконец, если вы не хотите, чтобы подстроки, созданные вообще (используя только начальные и конечные индексы для экономии места, например), re.finditer может выполнить задание. Итерации над разделителями и сделайте что-нибудь для текста между ними, в зависимости от того, какой разделитель (| или <>) был найден. Это более сложная операция, так как вам придется обрабатывать множество угловых шкафов, но может стоить того, что вам нужно.

Обновление: для вашего конкретного случая, где формат ввода является однородным, оптимальные решения являются лучшими. Если необходимо, выполните обработку результата, чтобы иметь вложенный список:

split_result = re.split( "\||<>", input )
result = [split_result[0], split_result[1], [i for i in split_result[2:] if i]]

(это последнее понимание списка заключается в том, что вы получите [] вместо [''], если после последнего | нет элементов)

Обновление 2:. Прочитав обновленный вопрос, я наконец понял, чего вы пытаетесь достичь. Здесь полный пример, используя ранее предложенную структуру:

items = re.split(r'\||<>', input) # Split input in items
offset = 0
result = [] # The result: strings for regular itens, lists for <> separated ones
acc = None
for i in items:
    delimiter = '|' if offset+len(i) < len(input) and input[offset+len(i)] == '|' else '<>'
    offset += len(i) + len(delimiter)
    if delimiter == '<>': # Will always put the item in a list
        if acc is None:
            acc = [i] # Create one if doesn't exist
            result.append(acc)
        else:
            acc.append(i)
    else:
        if acc is not None: # If there was a list, put the last item in it
            acc.append(i)
        else:
            result.append(i) # Add the regular items
        acc = None # Clear the list, since what will come next is a regular item or a new list
print result

Протестировав его с помощью вашего примера, результат:

['a', 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c','de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'aha'],
 'b', 'c', 'de', ['f', 'ge', 'ah']]

Ответ 4

Если вы знаете, что <> не появится в другом месте строки, вы можете заменить '< > ' на '|' а затем один раскол:

>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> input.replace("<>", "|").split("|")
['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']

Это почти наверняка будет быстрее, чем выполнение нескольких разделов. Это может быть или не быть быстрее, чем использование re.split - timeit - ваш друг.

Edit: В моей системе с образцовой строкой, которую вы предоставили, моя версия более чем в три раза быстрее, чем re.split:

>>> timeit input.replace("<>", "|").split("|")
1000000 loops, best of 3: 980 ns per loop
>>> import re
>>> timeit re.split(r"\||<>", input)
100000 loops, best of 3: 3.07 us per loop

(N.B. это использование ipython, который имеет timeit как встроенную команду).

Ответ 5

Вы можете использовать заменить. Сначала замените <> на |, а затем разделите на |.

def replace_way(input):
    return input.replace('<>','|').split('|')

Производительность времени:

import timeit
import re

def splitit(input):
    res0 = input.split("|")
    res = []
    for element in res0:
        t = element.split("<>")
        if t != [element]:
            res0.remove(element)
            res.append(t)
    return (res0, res)

def regexit(input):
    return re.split( "\||<>", input )

def replace_way(input):
    return input.replace('<>','|').split('|')


print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit()
print "split:", timeit.Timer("splitit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import splitit").timeit()
print "regex:", timeit.Timer("regexit('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import regexit").timeit()
print "replace:",timeit.Timer("replace_way('a|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>aha|b|c|de|f<>ge<>ah')","from __main__ import replace_way").timeit()

Результаты на моей машине:

split: 11.8682055461
regex: 12.7430856814
replace: 2.54299265006
split: 79.2124379066
regex: 68.6917008003
replace: 10.944842347