Мой текущий проект Python потребует большого количества разбиений строк для обработки входящих пакетов. Поскольку я буду запускать его на довольно медленной системе, мне было интересно, какой самый эффективный способ сделать это. Строки должны быть отформатированы примерно так:
Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5
Объяснение: Этот конкретный пример будет получен из списка, где первые два элемента — это название и дата, а элементы с 3 по 5 — это приглашенные люди (количество которых может быть любым от нуля до n, где n — количество зарегистрированных пользователей на сервере).
Из того, что я вижу, у меня есть следующие варианты:
split()
Решение 1 будет включать разбиение на |
, а затем разбиение последнего элемента результирующего списка на <>
для этого примера, в то время как решение 2, вероятно, приведет к регулярное выражение типа:
((.+)|)+((.+)(<>)?)+
Хорошо, это регулярное выражение ужасно, я сам это вижу. Это также не проверено. Но ты получил идею.
Теперь я ищу способ, который а) занимает наименьшее количество времени и б) в идеале использует наименьший объем памяти. Если возможен только один из двух, я бы предпочел меньше времени.Идеальное решение также будет работать для строк, содержащих больше элементов, разделенных |
, и строк, в которых полностью отсутствует <>
. По крайней мере, решение на основе регулярных выражений сделает это
Насколько я понимаю, split()
будет использовать больше памяти (поскольку вы в основном получаете два результирующих списка, один из которых разбивается на |
и второй, который разделяется на <>
), но я недостаточно знаю о реализации регулярных выражений в Python, чтобы судить о том, как будет работать RegEx. split()
также менее динамичен, чем регулярное выражение, если оно связано с разным количеством элементов и отсутствием второго разделителя. Тем не менее, я не могу избавиться от впечатления, что python может делать это лучше без регулярных выражений, поэтому я спрашиваю
Некоторые примечания:
|
от частей с разделителем <>
, поэтому простой плоский список, сгенерированный re.split(\||<>,input)
(предложенный @obmarg), не будет работать слишком хорошо.Решения, соответствующие этому критерию, высоко ценятся. Подводя итоги вопроса: какое решение было бы наиболее эффективным и по каким причинам.
По многочисленным запросам я некоторое время работал над решением split()
и первым предложенным регулярным выражением @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 от @duncan превосходит все другие алгоритмы, независимо от длины (по крайней мере, с этим ограниченным набором данных), и также похоже, что решение @mgibsonbr имеет некоторые проблемы с производительностью (извините ' насчет этого, но спасибо за решение в любом случае).
Спасибо всем за участие.
Я не уверен, является ли это наиболее эффективным, но, безусловно, проще всего написать код, похожий на этот:
>>> input = "Item 1 | Item 2 | Item 3 <> Item 4 <> Item 5"
>>> re.split( "\||<>", input )
>>> ['Item 1 ', ' Item 2 ', ' Item 3 ', ' Item 4 ', ' Item 5']
Я думаю, есть хороший шанс, что он будет более эффективным, чем обычное старое разделение (в зависимости от входных данных), поскольку вам нужно будет выполнить вторую операцию разделения для каждой строки, выводимой из первого разделения, что, по-видимому, неэффективно ни для памяти, ни для времени.
Хотя я и сказал, что могу легко ошибаться, и единственный способ убедиться в этом - это рассчитать время.
Если вы знаете, что <>
не появится в другом месте строки, вы можете заменить '<>' на '|' с последующим одним разделением:
>>> 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 - время это ваш друг.
Редактировать : В моей системе с примером строки, которую вы предоставили, моя версия более чем в три раза быстрее, чем 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
(NB это использует ipython, который имеет timeit как встроенную команду).