Tengo una cadena de varias líneas definida así:
foo = """
this is
a multi-line string.
"""
Esta cadena la usamos como entrada de prueba para un analizador que estoy escribiendo. La función analizador recibe un objeto file
-objeto como entrada y lo repite. También llama al next()
método directamente para omitir líneas, por lo que realmente necesito un iterador como entrada, no un iterable. Necesito un iterador que recorra las líneas individuales de esa cadena como lo file
haría un objeto sobre las líneas de un archivo de texto. Por supuesto que podría hacerlo así:
lineiterator = iter(foo.splitlines())
¿Existe una forma más directa de hacer esto? En este escenario, la cadena tiene que atravesar una vez para la división y luego otra vez por el analizador. No importa en mi caso de prueba, ya que la cuerda es muy corta allí, solo pregunto por curiosidad. Python tiene muchos elementos integrados útiles y eficientes para tales cosas, pero no pude encontrar nada que se adapte a esta necesidad.
foo.splitlines()
¿no?splitlines()
y una segunda vez, se repite el resultado de este método.Respuestas:
Aquí hay tres posibilidades:
Ejecutar esto como el script principal confirma que las tres funciones son equivalentes. Con
timeit
(y* 100
parafoo
obtener cadenas sustanciales para una medición más precisa):Tenga en cuenta que necesitamos la
list()
llamada para asegurarnos de que los iteradores se atraviesan, no solo se compilan.IOW, la implementación ingenua es mucho más rápida que ni siquiera es divertida: 6 veces más rápido que mi intento con
find
llamadas, que a su vez es 4 veces más rápido que un enfoque de nivel inferior.Lecciones para retener: la medición siempre es algo bueno (pero debe ser precisa); los métodos de cadena como
splitlines
se implementan de manera muy rápida; juntar cadenas programando a un nivel muy bajo (especialmente mediante bucles de+=
piezas muy pequeñas) puede ser bastante lento.Editar : se agregó la propuesta de @ Jacob, ligeramente modificada para dar los mismos resultados que los demás (se mantienen los espacios en blanco al final de una línea), es decir:
La medición da:
no es tan bueno como el
.find
enfoque basado; aún así, vale la pena tenerlo en cuenta porque podría ser menos propenso a pequeños errores uno por uno (cualquier bucle en el que vea apariciones de +1 y -1, como elf3
anterior, debería automáticamente desencadenar sospechas de uno en uno, y también deberían hacerlo muchos bucles que carecen de tales ajustes y deberían tenerlos, aunque creo que mi código también es correcto ya que pude verificar su salida con otras funciones ').Pero el enfoque basado en la división aún prevalece.
Un aparte: posiblemente un mejor estilo para
f4
sería:al menos, es un poco menos detallado. La necesidad de eliminar los trailing
\n
s desafortunadamente prohíbe el reemplazo más claro y rápido delwhile
bucle conreturn iter(stri)
(laiter
parte de la cual es redundante en las versiones modernas de Python, creo que desde 2.3 o 2.4, pero también es inocuo). Quizás valga la pena intentarlo, también:o variaciones de los mismos, pero me detengo aquí, ya que es más o menos un ejercicio teórico que es el
strip
más simple y rápido.fuente
(line[:-1] for line in cStringIO.StringIO(foo))
es bastante rápido; casi tan rápido como la implementación ingenua, pero no del todo.timeit
un hábito.list
llamada para cronometrar todas las partes relevantes).split()
claramente intercambia memoria por rendimiento, manteniendo una copia de todas las secciones además de las estructuras de la lista.No estoy seguro de lo que quiere decir con "luego otra vez por el analizador". Una vez que se ha realizado la división, no hay más recorrido de la cadena , solo un recorrido de la lista de cadenas divididas. Esta será probablemente la forma más rápida de lograrlo, siempre que el tamaño de su cuerda no sea absolutamente enorme. El hecho de que Python use cadenas inmutables significa que siempre debes crear una nueva cadena, por lo que esto debe hacerse en algún momento de todos modos.
Si su cadena es muy grande, la desventaja está en el uso de la memoria: tendrá la cadena original y una lista de cadenas divididas en la memoria al mismo tiempo, duplicando la memoria requerida. Un enfoque de iterador puede ahorrarle esto, construyendo una cadena según sea necesario, aunque todavía paga la penalización de "división". Sin embargo, si la cadena es tan grande, por lo general, quiere evitar incluso la flor sin dividir cadena esté en la memoria. Sería mejor simplemente leer la cadena de un archivo, que ya le permite iterar a través de él como líneas.
Sin embargo, si ya tiene una cadena enorme en la memoria, un enfoque sería usar StringIO, que presenta una interfaz similar a un archivo para una cadena, lo que incluye permitir la iteración por línea (usando internamente .find para encontrar la siguiente nueva línea). Entonces obtienes:
fuente
io
paquete para esto, por ejemplo, use enio.StringIO
lugar deStringIO.StringIO
. Ver docs.python.org/3/library/io.htmlStringIO
también es una buena manera de obtener un manejo de nueva línea universal de alto rendimiento.Si leo
Modules/cStringIO.c
correctamente, esto debería ser bastante eficiente (aunque algo detallado):fuente
La búsqueda basada en expresiones regulares es a veces más rápida que el enfoque del generador:
fuente
Supongo que podrías rodar el tuyo:
No estoy seguro de cuán eficiente es esta implementación, pero solo iterará sobre su cadena una vez.
Mmm, generadores.
Editar:
Por supuesto, también querrá agregar cualquier tipo de acciones de análisis que desee realizar, pero eso es bastante simple.
fuente
+=
parte tiene el peorO(N squared)
rendimiento de los casos , aunque varios trucos de implementación intentan reducirlo cuando sea posible)..join
método en realidad parece una complejidad O (N). Como todavía no pude encontrar la comparación particular hecha en SO, comencé una pregunta stackoverflow.com/questions/3055477/… (¡que sorprendentemente recibió más respuestas que las mías!)Puede iterar sobre "un archivo", lo que produce líneas, incluido el carácter de nueva línea final. Para hacer un "archivo virtual" a partir de una cadena, puede usar
StringIO
:fuente