Eu tenho uma seqüência de várias linhas definida assim:
foo = """
this is
a multi-line string.
"""
Essa string que usamos como entrada de teste para um analisador que estou escrevendo. A função analisadora recebe um file
objeto-como entrada e itera sobre ele. Ele também chama o next()
método diretamente para pular linhas, então eu realmente preciso de um iterador como entrada, não de um iterável. Eu preciso de um iterador que itere sobre as linhas individuais dessa sequência, como um file
objeto-faria sobre as linhas de um arquivo de texto. É claro que eu poderia fazer assim:
lineiterator = iter(foo.splitlines())
Existe uma maneira mais direta de fazer isso? Nesse cenário, a seqüência de caracteres deve ser percorrida uma vez para a divisão e, em seguida, novamente pelo analisador. Não importa no meu caso de teste, já que a string é muito curta lá, estou apenas perguntando por curiosidade. O Python possui tantos recursos úteis e eficientes para esse tipo de coisa, mas não consegui encontrar nada que atenda a essa necessidade.
foo.splitlines()
certo?splitlines()
e uma segunda vez, iterando sobre o resultado desse método.Respostas:
Aqui estão três possibilidades:
A execução como o script principal confirma que as três funções são equivalentes. Com
timeit
(e a* 100
parafoo
obter seqüências substanciais para uma medição mais precisa):Observe que precisamos da
list()
chamada para garantir que os iteradores sejam percorridos, não apenas construídos.IOW, a implementação ingênua é muito mais rápida e nem engraçada: 6 vezes mais rápida do que minha tentativa de fazer
find
chamadas, que por sua vez é 4 vezes mais rápida que uma abordagem de nível inferior.Lições a reter: a medição é sempre uma coisa boa (mas deve ser precisa); métodos de string como
splitlines
são implementados de maneira muito rápida; juntar as strings programando em um nível muito baixo (especialmente por loops de+=
peças muito pequenas) pode ser bastante lento.Edit : adicionou a proposta de Jacob, ligeiramente modificada para dar os mesmos resultados que os outros (os espaços em branco em uma linha são mantidos), ou seja:
A medição fornece:
não é tão bom quanto a
.find
abordagem baseada - ainda assim, lembre-se, pois pode ser menos propenso a pequenos erros pontuais (qualquer loop em que você vê ocorrências de +1 e -1, como o meuf3
acima, deve automaticamente desencadear suspeitas de um por um - e o mesmo acontece com muitos loops que não possuem esses ajustes e devem tê-los - embora eu acredite que meu código também esteja correto, pois fui capaz de verificar sua saída com outras funções ').Mas a abordagem baseada em divisão ainda domina.
Um aparte: possivelmente um estilo melhor
f4
seria:pelo menos, é um pouco menos detalhado.
\n
Infelizmente, a necessidade de remover os trailing s proíbe a substituição mais clara e rápida dowhile
loopreturn iter(stri)
(aiter
parte da qual é redundante nas versões modernas do Python, acredito que desde 2.3 ou 2.4, mas também é inócua). Talvez valha a pena tentar também:ou variações - mas estou parando aqui, já que é praticamente um exercício teórico
strip
baseado no mais simples, e rápido.fonte
(line[:-1] for line in cStringIO.StringIO(foo))
é muito rápido; quase tão rápido quanto a implementação ingênua, mas não exatamente.timeit
hábito.list
chamada para realmente marcar todas as partes relevantes! -).split()
troca claramente a memória pelo desempenho, mantendo uma cópia de todas as seções, além das estruturas da lista.Não sei ao certo o que você quer dizer com "novamente pelo analisador". Após a divisão, não há mais a travessia da string , apenas a travessia da lista de strings divididas. Essa provavelmente será a maneira mais rápida de conseguir isso, desde que o tamanho da sua string não seja absolutamente enorme. O fato de o python usar seqüências imutáveis significa que você deve sempre criar uma nova sequência, portanto, isso deve ser feito em algum momento.
Se a sua string for muito grande, a desvantagem está no uso da memória: você terá a string original e uma lista de strings divididas na memória ao mesmo tempo, duplicando a memória necessária. Uma abordagem do iterador pode economizar isso, criando uma cadeia conforme necessário, embora ainda pague a penalidade de "divisão". No entanto, se sua string for muito grande, você geralmente deseja evitar que a string não dividida esteja na memória. Seria melhor apenas ler a string de um arquivo, o que já permite que você itere como linhas.
No entanto, se você já tem uma cadeia enorme de memória, uma abordagem seria usar o StringIO, que apresenta uma interface semelhante a arquivo para uma cadeia, incluindo a possibilidade de iterar por linha (usando internamente .find para encontrar a próxima nova linha). Você então obtém:
fonte
io
pacote para isso, por exemplo, use emio.StringIO
vez deStringIO.StringIO
. Veja docs.python.org/3/library/io.htmlStringIO
também é uma boa maneira de obter manipulação universal de nova linha de alto desempenho.Se eu leio
Modules/cStringIO.c
corretamente, isso deve ser bastante eficiente (embora um tanto detalhado):fonte
Às vezes, a pesquisa baseada em regex é mais rápida que a abordagem de gerador:
fonte
Suponho que você possa fazer o seu próprio:
Não tenho certeza de quão eficiente é essa implementação, mas isso só irá iterar sua string uma vez.
Mmm, geradores.
Editar:
É claro que você também desejará adicionar qualquer tipo de ação de análise que queira executar, mas isso é bastante simples.
fonte
+=
parte temO(N squared)
desempenho de pior caso , embora vários truques de implementação tentem diminuir isso quando possível)..join
método realmente se parece com a complexidade O (N). Desde que eu não poderia encontrar a comparação especial feita em mais Então, eu comecei uma pergunta stackoverflow.com/questions/3055477/... (que surpreendentemente recebeu mais respostas do que apenas o meu próprio!)Você pode iterar sobre "um arquivo", que produz linhas, incluindo o caractere de nova linha à direita. Para criar um "arquivo virtual" de uma string, você pode usar
StringIO
:fonte