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

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

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

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

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

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

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

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

Хорошо, это регулярное выражение ужасно, я сам это вижу. Это также не проверено. Но ты получил идею.

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

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

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

  • Да, я мог бы просто протестировать оба решения, но я пытаюсь кое-что узнать о python в целом и о том, как он здесь работает, и если я просто сравним эти два, я все еще не знаю, какие функции python я пропустил.
  • Да, оптимизация на этом уровне действительно требуется только для высокопроизводительных вещей, но, как я уже сказал, я пытаюсь узнать что-то о 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 имеет некоторые проблемы с производительностью (извините ' насчет этого, но спасибо за решение в любом случае).

Спасибо всем за участие.

26
задан malexmave 7 March 2012 в 15:52
поделиться

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

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

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

10
ответ дан 28 November 2019 в 07:39
поделиться

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

>>> 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 как встроенную команду).

2
ответ дан 28 November 2019 в 07:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: