ventajas recorrer patron linkedlist ejemplo desventajas colecciones iterator go

iterator - patron - recorrer arraylist java foreach



¿Cuál es la forma más idiomática de crear un iterador en Go? (6)

Al mirar el paquete contenedor / lista, parece que no hay forma de hacerlo. Se debe usar la forma de C si itera sobre un objeto.

Algo como esto.

type Foo struct { ... } func (f *Foo) Next() int { ... } foo := Foo(10) for f := foo.Next(); f >= 0; f = foo.Next() { ... }

Una opción es usar canales. Los canales son como iteradores de una manera y puede iterar sobre ellos utilizando la palabra clave rango. Pero cuando descubres que no puedes salir de este circuito sin filtrar la rutina, el uso se vuelve limitado.

¿Cuál es la forma idiomática de crear un patrón de iterador en go?

Editar :

El problema fundamental con los canales es que son un modelo de empuje. Iterator es un modelo de atracción. No tiene que decirle a iterator que se detenga. Estoy buscando una manera de iterar sobre las colecciones de una manera expresiva agradable. También me gustaría encadenar iteradores (asignar, filtrar, doblar alternativas).


Aquí hay una manera en que pensé hacerlo con canales y rutinas:

package main import ( "fmt" ) func main() { c := nameIterator(3) for batch := range c { fmt.Println(batch) } } func nameIterator(batchSize int) <-chan []string { names := []string{"Cherry", "Cami", "Tildy", "Cory", "Ronnie", "Aleksandr", "Billie", "Reine", "Gilbertina", "Dotti"} c := make(chan []string) go func() { for i := 0; i < len(names); i++ { startIdx := i * batchSize endIdx := startIdx + batchSize if startIdx > len(names) { continue } if endIdx > len(names) { c <- names[startIdx:] } else { c <- names[startIdx:endIdx] } } close(c) }() return c }

https://play.golang.org/p/M6NPT-hYPNd

Obtuve la idea de la charla Go Concurrency Patterns de Rob Pike.


Los canales son útiles, pero los cierres suelen ser más adecuados.

package main import "fmt" func main() { gen := newEven() fmt.Println(gen()) fmt.Println(gen()) fmt.Println(gen()) gen = nil // release for garbage collection } func newEven() func() int { n := 0 // closure captures variable n return func() int { n += 2 return n } }

Área de juegos: http://play.golang.org/p/W7pG_HUOzw

No me gustan los cierres tampoco? Use un tipo con nombre con un método:

package main import "fmt" func main() { gen := even(0) fmt.Println(gen.next()) fmt.Println(gen.next()) fmt.Println(gen.next()) } type even int func (e *even) next() int { *e += 2 return int(*e) }

Área de juegos: http://play.golang.org/p/o0lerLcAh3

Hay intercambios entre las tres técnicas, por lo que no puede nominar a uno como idiomático. Use lo que mejor se adapte a sus necesidades.

El encadenamiento es fácil porque las funciones son objetos de primera clase. Aquí hay una extensión del ejemplo de cierre. Agregué un tipo intGen para generador de enteros que deja en claro dónde se usan las funciones del generador como argumentos y valores de retorno. mapInt se define de forma general para asignar cualquier función entera a un generador de enteros. Otras funciones como filtro y plegado podrían definirse de manera similar.

package main import "fmt" func main() { gen := mapInt(newEven(), square) fmt.Println(gen()) fmt.Println(gen()) fmt.Println(gen()) gen = nil // release for garbage collection } type intGen func() int func newEven() intGen { n := 0 return func() int { n += 2 return n } } func mapInt(g intGen, f func(int) int) intGen { return func() int { return f(g()) } } func square(i int) int { return i * i }

Área de juegos: http://play.golang.org/p/L1OFm6JuX0


Puedes escapar sin fugas dando a tus goroutines un segundo canal para mensajes de control. En el caso más simple, es solo un chan bool . Cuando desee detener la rutina, envíe este canal. Dentro de la goroutine, pones el canal de envío del iterador y la escucha en el canal de control dentro de una selección.

Aquí hay un ejemplo.

Puede llevar esto más lejos al permitir diferentes mensajes de control, como "omitir".

Su pregunta es bastante abstracta, por así decirlo, un ejemplo concreto sería útil.


TL; DR: Iteradores no son idiomáticos en Go. Déjelos en otros idiomas.

En profundidad, comienza la entrada de Wikipedia "Patrón de iterador", "En la programación orientada a objetos, el patrón de iterador es un patrón de diseño ..." Dos banderas rojas: Primero, los conceptos de programación orientada a objetos a menudo no se traducen bien en Ir y, en segundo lugar, muchos programadores de Go no piensan demasiado en los patrones de diseño. Ese primer párrafo también incluye "El patrón de iterador desacopla los algoritmos de los contenedores", pero solo después de indicar "un iterador [accede] a los elementos del contenedor. Bueno, ¿qué es? Si un algoritmo está accediendo a los elementos del contenedor, difícilmente puede alegarse que está desacoplado. La respuesta en muchos idiomas implica algún tipo de genérico que permite que el lenguaje se generalice sobre estructuras de datos similares. Las respuestas en Go son interfaces. Las interfaces imponen un desacoplamiento más estricto de algoritmos y objetos al negar el acceso a la estructura y exigir que toda interacción se base en el comportamiento Comportamiento significa capacidades expresadas a través de métodos en los datos.

Para un tipo de iterador mínimo, la capacidad necesaria es un método Next. Una interfaz Go puede representar un objeto iterador simplemente especificando esta única firma de método. Si desea que un tipo de contenedor sea iterable, debe cumplir con la interfaz del iterador implementando todos los métodos de la interfaz. (Solo tenemos uno aquí, y de hecho es común que las interfaces tengan solo un método).

Un ejemplo de trabajo mínimo:

package main import "fmt" // IntIterator is an iterator object. // yes, it''s just an interface. type intIterator interface { Next() (value int, ok bool) } // IterableSlice is a container data structure // that supports iteration. // That is, it satisfies intIterator. type iterableSlice struct { x int s []int } // iterableSlice.Next implements intIterator.Next, // satisfying the interface. func (s *iterableSlice) Next() (value int, ok bool) { s.x++ if s.x >= len(s.s) { return 0, false } return s.s[s.x], true } // newSlice is a constructor that constructs an iterable // container object from the native Go slice type. func newSlice(s []int) *iterableSlice { return &iterableSlice{-1, s} } func main() { // Ds is just intIterator type. // It has no access to any data structure. var ds intIterator // Construct. Assign the concrete result from newSlice // to the interface ds. ds has a non-nil value now, // but still has no access to the structure of the // concrete type. ds = newSlice([]int{3, 1, 4}) // iterate for { // Use behavior only. Next returns values // but without insight as to how the values // might have been represented or might have // been computed. v, ok := ds.Next() if !ok { break } fmt.Println(v) } }

Área de juegos: http://play.golang.org/p/AFZzA7PRDR

Esta es la idea básica de las interfaces, pero es una exageración absurda para iterar sobre una porción. En muchos casos en los que buscaría un iterador en otros idiomas, escriba el código Go utilizando primitivas de lenguaje integradas que se repiten directamente sobre los tipos básicos. Tu código permanece claro y conciso. Donde eso se complique, considere las características que realmente necesita. ¿Necesita emitir resultados de lugares aleatorios en alguna función? Los canales proporcionan una capacidad de rendimiento que permite eso. ¿Necesitas listas infinitas o evaluación perezosa? Los cierres funcionan muy bien. ¿Tiene diferentes tipos de datos y los necesita para respaldar de manera transparente las mismas operaciones? Interfaces entregan. Con canales, funciones e interfaces todos los objetos de primera clase, estas técnicas son fácilmente compostables. Entonces, ¿cuál es la forma más idiomática ? Es experimentar con diferentes técnicas, sentirse cómodo con ellas y usar lo que se ajuste a sus necesidades de la manera más simple posible. Iteradores, en el sentido orientado a objetos de todos modos, casi nunca son los más simples.


TL; DR: Olvídese de cierres y canales, demasiado lento. Si los elementos individuales de su colección son accesibles por índice, elija la iteración C clásica sobre un tipo similar a una matriz. De lo contrario, implemente un iterador con estado.

Necesitaba iterar sobre algún tipo de colección para la cual la implementación exacta de almacenamiento aún no está escrita en piedra. Esto, más otros millones de razones para abstraer los detalles de implementación del cliente, me llevan a hacer algunas pruebas con varios métodos de iteración. El código completo aquí , incluidas algunas implementaciones que hacen uso de errores como valores . Estos son los resultados de referencia:

  • iteración C clásica sobre una estructura tipo array. El tipo proporciona los métodos ValueAt () y Len ():

    l := Len(collection) for i := 0; i < l; i++ { value := collection.ValueAt(i) } // benchmark result: 2492641 ns/op

  • Iterador de estilo de cierre El método iterador de la colección devuelve una función next () (un cierre sobre la colección y el cursor) y un booleano hasNext. next () devuelve el siguiente valor y un hasNext booleano. Tenga en cuenta que esto se ejecuta mucho más rápido que el uso de cierres next () y hasNext () separados que devuelven valores únicos:

    for next, hasNext := collection.Iterator(); hasNext; { value, hasNext = next() } // benchmark result: 7966233 ns/op !!!

  • Stateful iterator. Una estructura simple con dos campos de datos, la colección y un cursor, y dos métodos: Next () y HasNext (). Esta vez, el método iterador () de la colección devuelve un puntero a una estructura de iterador inicializada correctamente:

    for iter := collection.Iterator(); iter.HasNext(); { value := iter.Next() } // benchmark result: 4010607 ns/op

Por mucho que me gustan los cierres, en cuanto al rendimiento, es un no-Go. En cuanto a los patrones de diseño, bueno, los Gophers prefieren el término "forma idiomática de hacer" por buenas razones. También grep the go source tree para iterators: con tan pocos archivos que mencionan el nombre, los iterators definitivamente no son una cosa de Go.

Consulte también esta página: http://ewencp.org/blog/golang-iterators/

De todos modos, las interfaces no ayudan de ninguna manera aquí, a menos que quiera definir alguna interfaz Iterable, pero este es un tema completamente diferente.