Неожиданное поведение функции, созданной для замены split () - PullRequest
1 голос
/ 17 марта 2012

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

better_split("After  the flood   ...  all the colors came out."," .")

Я ожидал такого результата:

['After', 'the', 'flood', 'all', 'the', 'colors', 'came', 'out']

Однако, как ни странно, функция вызывает непонятное (для меня) поведение. Когда он достигает последних двух слов, он не подавляет больше '' и вместо того, чтобы добавить к списку результатов "cam" и "out", добавляет к нему "Выхода" и, таким образом, я получил это:

['After', 'the', 'flood', 'all', 'the', 'colors', 'came out']

Кто-то с большим опытом понимает, почему это происходит? Заранее благодарю за любую помощь!

def better_split(text,markersString):
markers = []
splited = []
for e in markersString:
    markers.append(e)    
for character in text:
    if character in markers:
        point = text.find(character)
        if text[:point] not in character:
            word = text[:point]
            splited.append(word)            
            while text[point] in markers and point+1 < len(text):
                point = point + 1
            text = text[point:]                   
print 'final splited = ', splited

better_split ("Это тест кода разделения строк!", ",! -")

better_split ("После потопа ... все цвета вышли.", ".")

split () С НЕСКОЛЬКИМИ РАЗДЕЛЕНИЯМИ Если вы ищете split () с несколькими разделениями, смотрите: Разделить строки с несколькими разделителями?

Лучший ответ без импорта, который я нашел, был следующим:

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

Ответы [ 5 ]

3 голосов
/ 17 марта 2012

Проблема в том, что это:

    for character in text:

перебирает символы в исходной строке & mdash; оригинальное значение text & mdash; пока это:

        point = text.find(character)

ищет разделитель в текущей строке & mdash; текущее значение text. Таким образом, эта часть вашей функции работает в предположении, что вы обрабатываете один символ-разделитель за раз; то есть предполагается, что всякий раз, когда вы сталкиваетесь с символом-разделителем в цикле над original text, это первый символ-разделитель в current text.

Между тем, это:

            while text[point] in markers and point+n < len(text):
                point = point + 1
            text = text[point:]

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

Итак, обработка идет так:

  [After  the flood   ...  all the colors came out.]
handling first space after "After":
  [After] [the flood   ...  all the colors came out.]
handling second space after "After":
  [After] [the] [flood   ...  all the colors came out.]
handling space after "the":
  [After] [the] [flood] [all the colors came out.]
handling first space after "flood":
  [After] [the] [flood] [all] [the colors came out.]
handling second space after "flood":
  [After] [the] [flood] [all] [the] [colors came out.]
handling third space after "flood":
  [After] [the] [flood] [all] [the] [colors] [came out.]
handling first period of the "...":
  [After] [the] [flood] [all] [the] [colors] [came out] []
-- text is now empty, no more splitting happens

Как видите, разделитель, с которым вы работаете, не является разделителем, на который вы делитесь.

Решение состоит в том, чтобы просто удалить логику, которая позволяет пропустить несколько разделителей одновременно & mdash; то есть измени это:

            while text[point] in markers and point+n < len(text):
                point = point + 1
            text = text[point:]

к этому: текст = текст [(точка + 1):]

и вместо этого, непосредственно перед тем, как добавить word к splited, убедитесь, что оно непустое:

            if len(word) > 0:
                splited.append(word)
3 голосов
/ 17 марта 2012

Более простое решение

Ваша better_split функция проще, чем вы думаете. Я реализовал это так:

def better_split(s, seps):
    result = [s]
    def split_by(sep):
        return lambda s: s.split(sep)
    for sep in seps:
        result = sum(map(split_by(sep), result), [])
    return filter(None, result)  # Do not return empty elements

Тесты

>>> better_split("This is a test-of the,string separation-code!", " ,!-")
['This', 'is', 'a', 'test', 'of', 'the', 'string', 'separation', 'code']
>>> better_split("After the flood ... all the colors came out."," .")
['After', 'the', 'flood', 'all', 'the', 'colors', 'came', 'out']

Советы о вашем коде

  • вам не нужно менять markersString на markers, вы можете выполнять итерации непосредственно через markersString,
  • text[:point] not in character равен True всегда, когда point > 1, поэтому довольно бесполезен,
  • point = text.find(character) даст вам point = -1 каждый раз, когда character не найден в text,
  • попробуйте упростить ваш код, одно из правил Python гласит: " Если что-то сложно объяснить, это плохая идея ". К сожалению, ваш код даже трудно читать, он содержит много избыточных операторов и операторов, которые выглядят так, как будто они должны работать не так, как они (например, используя str.find для получения места разделителя, а затем используя его без проверок для получения ломтиков,
2 голосов
/ 17 марта 2012

better_split() не хорошее имя.Как «лучше», каким образом?

yourmodule.split() достаточно, чтобы отличить ее от любой другой функции split().

Вы можете реализовать ее, используя re.split():

import  re

def split(text, separators):
    re_sep = re.compile(r"(?:{0})+".format("|".join(map(re.escape, separators))))
    return filter(None, re_sep.split(text))

Пример

>>> split("After  the flood   ...  all the colors came out.", " .")
['After', 'the', 'flood', 'all', 'the', 'colors', 'came', 'out']

Если вам не разрешено использовать map, filter, вы можете легко заменитьих:

  • "|".join(map(re.escape, separators)):

    "|".join(re.escape(s) for s in separators)
    
  • filter(None, re_sep.split(text)):

    [s for s in re_sep.split(text) if s]
    
2 голосов
/ 17 марта 2012

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

for character in text:

было выполнено,

но ваша цель - вывести измененный текст после каждого цикла for.

Таким образом, решение состоит в том, чтобы переместить цикл for во внутреннюю функцию и использовать его рекурсивно:

def better_split(text,markersString):
    # simple and better way for 'for e in markerString...'
    markers = list(markersString)
    splited = []

    # there is no need to assign variable n, we all know it should be 1
    # n = 1    

    def iter_text(text):
        # check if text is an empty string,
        # NOTE this `text` will cover `text` in upper function as to local scope,
        # so it's actually the text everytime iter_text() get,
        # not the one better_split() get.
        if not text:
            return
        # [UPDATES 2012-03-17 01:07 EST]
        # add a flag to judge if there are markers in `text`
        _has_marker = False
        for character in text:
            if character in markers:
                # set `_has_marker` to True to indicate `text` has been handled
                _has_marker = True
                point = text.find(character)
                word = text[:point]
                splited.append(word)
                # check if text[point] is legal, to prevent raising of IndexError
                while point + 1 <= len(text) and text[point] in markers:
                    point = point + 1
                text = text[point:]
                # break the loop when you find a marker
                # and change `text` according to it,
                # so that the new loop will get started with changed `text`
                break
        # if no marker was found in `text`, add the whole `text` to `splited`
        if not _has_marker:
            splited.append(text)
        else:
            iter_text(text)

    iter_text(text)

    print 'final splited = ', splited

Другие подробности смотрите в комментариях в коде.

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

def better_split(s, seprators):
    assert isinstance(seprators, str), 'seprators must be string'
    buf = [s]
    for sep in seprators:
        for loop, text in enumerate(buf):
            buf[loop:loop+1] = [i for i in text.split(sep) if i]
    return buf
0 голосов
/ 19 августа 2017
def spli(str,sep=' '):
    index=0
    string=''
    list=[]
    while index<len(str):
       if(str[index] not in sep):
          string+=str[index]
       elif(str[index] in sep):
          list.append(string)
          string=''
       index+=1
    if string:list.append(string)
        return(list)
n='hello'
print(spli(n))

output:
 ['h','e','l','l','o']
...