2010-07-19 8 views
5

Tengo un problema de codificación en Erlang que probablemente sea un patrón de diseño común, pero no encuentro información sobre cómo resolverlo.¿Patrón de diseño? Función que itera a través de una lista en busca del primer resultado {success}

Tengo una lista L. Quiero aplicar una función f a cada elemento en L, y hacer que se ejecute en todos los elementos en L al mismo tiempo. Cada llamada a f (Elemento) tendrá éxito o fallará; en la mayoría de los casos se producirá un error, pero en ocasiones tendrá éxito para un elemento específico dentro L.

Si/cuando af (Elemento) tiene éxito, quiero volver "éxito" y terminar todas las invocaciones de f de otros elementos en L - el primer "éxito" es todo lo que me interesa. Por otro lado, si f (Element) falla para cada elemento en L, entonces quiero devolver "fail".

Como ejemplo trivial, supongamos que L es una lista de enteros, y F devuelve {success} si un elemento en L es 3 o {falla} para cualquier otro valor. Quiero encontrar lo más rápido posible si hay 3 en L; No me importa cuántos 3 hay, solo si existe al menos un 3 o no. f podría tener este aspecto:

f(Int) -> 
    case Int of 
    3 -> {success}; 
    _ -> {fail} 
    end. 

¿Cómo puedo iterar a través de una lista de enteros para averiguar si la lista contiene al menos un 3, y volver lo más rápido posible?

Sin duda, este es un patrón de diseño funcional común, y yo no estoy usando los términos de búsqueda adecuados dentro de Google ...

Respuesta

2

Como ya se ha respondido, su solución es utilizar listas: any/2.

Al ver que se desea una versión concurrente de la misma:

any(F, List) -> 
    Parent = self(), 
    Pid = spawn(fun() -> spawner(Parent, F, List) end), 
    receive {Pid, Result} -> Result 
    end, 
    Result. 

spawner(Parent, F, List) -> 
    Spawner = self(), 
    S = spawn_link(fun() -> wait_for_result(Spawner, Parent, length(List)) end), 
    [spawn_link(fun() -> run(S, F) end) || X <- List], 
    receive after infinity -> ok end. 

wait_for_result(Spawner, Parent, 0) -> 
    Parent ! {Spawner, false}, 
    exit(have_result); 
wait_for_result(Spawner, Parent, Children) -> 
    receive 
    true -> Parent ! {Spawner, true}, exit(have_result); 
    false -> wait_for_result(Spawner, Parent, Children -1) 
    end. 

run(S, F) -> 
    case catch(F()) of 
    true -> S ! true; 
    _ -> S ! false 
    end. 

Tenga en cuenta que todos los niños (los procesos "run") morirán cuando el proceso "wait_for_children" hace una salida (have_result).

Completamente no probado ... Ah, qué diablos. Haré un ejemplo:

4> play:any(fun(A) -> A == a end, [b,b,b,b,b,b,b,b]). 
false 
5> play:any(fun(A) -> A == a end, [b,b,b,b,b,b,a,b]). 
true 

Todavía podría haber errores (y probablemente existan).

+0

Puede optimizar esto al darse cuenta de que en 'wait_for_result/2' no estamos realmente interesados ​​en qué trabajador devuelve' false', solo cuántos lo han hecho. Por lo tanto, es suficiente eliminar el primer elemento de la lista cada vez. – rvirding

+0

También debe mencionar que al hacer 'exit (have_result)' se eliminarán todos los procesos de trabajo restantes, ya que están enlazados (comenzados con 'spawn_link') y' have_result' no es 'normal', por lo que se trata como un error de salida. – rvirding

+0

Tiene razón, por supuesto. Actualizó la respuesta con sus comentarios. –

4

Hay básicamente dos formas diferentes de hacer esto. O bien escribir su propia función, que itera sobre la lista de volver true o false dependiendo de si se encuentra un 3:

contains_3([3|_]) -> true; 
contains_3([_|T]) -> contains_3(T); 
contains_3([]) -> false. 

El segundo es utilizar una función ya definida hacer la iteración actual hasta que una prueba de los elementos de la lista es cierto y proporcionarle la prueba. lists:any vuelve true o false dependiendo de si la prueba tiene éxito durante al menos un elemento:

contains_3(List) -> lists:any(fun (E) -> E =:= 3 end, List). 

quieren hacer lo mismo. Cuál eliges depende de ti. El segundo probablemente esté más cerca de un patrón de diseño, pero creo que incluso si lo usa, debe tener una idea de cómo funciona internamente. En este caso, es trivial y muy cercano al caso explícito.

Es algo muy común de hacer, pero no sé si se clasificaría como un patrón de diseño. Parece tan básico y en cierto sentido "trivial" que dudaría en llamarlo un patrón de diseño.

+0

Gracias a enrutar, parece que mi ejemplo fue demasiado trivial ... Tengo una función bastante compleja y lenta que debe ejecutarse contra cada elemento de la lista. Quiero ejecutarlo en contra de cada elemento de la lista al mismo tiempo, por lo que puedo obtener un resultado {success} lo antes posible, si lo hay. Sin embargo, una vez que tenga ese primer {éxito}, no tiene sentido que la función continúe ejecutándose contra todos los demás elementos de la lista: llevará demasiado tiempo y masticará demasiados recursos sin ningún beneficio. Finalmente, si no hay {éxito} para ninguno de los elementos de la lista, entonces debo reconocerlo también. – monch1962

+0

Espero que haya una solución basada en, por ejemplo, generando múltiples funciones (una por elemento de lista), haciendo que se ejecuten simultáneamente y luego envíe de vuelta {success} o {fail} al proceso principal. Es fácil de construir usando una lista de comprensión y un bucle de recepción ejecutándose en el proceso principal; los desafíos son (a) en el primer {success} recibido, indicar a los otros subprocesos que dejen de procesar y salir lo antes posible, y (b) manejar el caso donde cada resultado del subproceso es un {fail}, por lo que el padre nunca ve un éxito}. – monch1962

3

Ha pasado un tiempo desde que hice alguna erlang, así que no voy a intentar proporcionarle la sintaxis, sin embargo, erlang y la OTP tienen la solución esperando por usted.

Genera un proceso que representa la función; haga que itere sobre la lista, generando tantos procesos como considere apropiados para realizar el cálculo por elemento de manera eficiente.

Enlace cada proceso al proceso de función y haga que el proceso de la función finalice después de que devuelve el primer resultado.

Deje erlang/otp para limpiar el resto de los procesos.

+0

Eso tiene mucho sentido, lo intentaré más tarde hoy. Gracias por la sugerencia – monch1962

0

Es posible que desee ver en el módulo plists: http://code.google.com/p/plists/ Aunque no sé si plists:any maneja

(a) en la primera {} recibido éxito, decir a los otros subprocesos para detener el procesamiento & exit ASAP

Cuestiones relacionadas