Saltar al contenido

Explica este fragmento de código Haskell que genera un flujo de números primos.

Nuestro equipo de expertos pasados varios días de investigación y recopilación de de información, obtuvimos la solución, esperamos que todo este artículo sea de utilidad en tu plan.

Solución:

Al contrario de lo que otros han dicho aquí, esta función no implementar el true tamiz de Eratóstenes. Sin embargo, devuelve una secuencia inicial de los números primos, y de manera similar, por lo que está bien pensar en él como el tamiz de Eratóstenes.

Estaba a punto de terminar de explicar el código cuando mipadi publicó su respuesta; Podría eliminarlo, pero como le dediqué un tiempo y nuestras respuestas no son completamente idénticas, lo dejo aquí.


Primero que nada, tenga en cuenta que hay algunos errores de sintaxis en el código que publicó. El código correcto es,

let sieve (p:xs) = p : sieve (filter (x -> x `mod` p /= 0) xs) in sieve [2..]
  1. let x in y define x y permite utilizar su definición en y. El resultado de esta expresión es el resultado de y. Entonces en este caso definimos una función sieve y devolver el resultado de aplicar [2..] para sieve.

  2. Ahora echemos un vistazo más de cerca al let parte de esta expresión:

    sieve (p:xs) = p : sieve (filter (x -> x `mod` p /= 0) xs)
    
    1. Esto define una función sieve que toma una lista como primer argumento.
    2. (p:xs) es un patrón que coincide p con el encabezado de dicha lista y xs con la cola (todo menos la cabeza).
    3. En general, p : xs es una lista cuyo primer elemento es p. xs es una lista que contiene los elementos restantes. Por lo tanto, sieve devuelve el primer elemento de la lista que recibe.
    4. No mire el resto de la lista:

      sieve (filter (x -> x `mod` p /= 0) xs)
      
      1. Podemos ver eso sieve se llama de forma recursiva. Por lo tanto, la filter expresión devolverá una lista.
      2. filter toma una función de filtro y una lista. Devuelve solo aquellos elementos de la lista para los que devuelve la función de filtro true.
      3. En este caso xs es la lista que se filtra y

        (x -> x `mod` p /= 0)
        

        es la función de filtro.

      4. La función de filtro toma un solo argumento, x y vuelve true si no es un múltiplo de p.
  3. Ahora eso sieve está definido, lo pasamos [2 .. ], la lista de todos los números naturales que comienzan en 2. Por lo tanto,

    1. Se devolverá el número 2. Todos los demás números naturales que sean múltiplos de 2 serán descartados.
    2. Por tanto, el segundo número es 3. Se devolverá. Todos los demás múltiplos de 3 se descartarán.
    3. Por lo tanto, el siguiente número será 5. Etc.

De hecho, es bastante elegante.

Primero, definimos una función sieve que toma una lista de elementos:

sieve (p:xs) =

En el cuerpo de sieve, tomamos la cabeza de la lista (porque estamos pasando la lista infinita [2..], y 2 se define como primo) y agregarlo (¡perezosamente!) al resultado de aplicar sieve al resto de la lista:

p : sieve (filter ( x -> x 'mod' p /= 0) xs)

Así que veamos el código que hace el trabajo en el resto de la lista:

sieve (filter ( x -> x 'mod' p /= 0) xs)

Estamos aplicando sieve a la lista filtrada. Analicemos lo que hace la parte del filtro:

filter ( x -> x 'mod' p /= 0) xs

filter toma una función y una lista en la que aplicamos esa función, y retiene los elementos que cumplen con los criterios dados por la función. En este caso, filter toma una función anónima:

 x -> x 'mod' p /= 0

Esta función anónima toma un argumento, x. Comprueba el módulo de x contra p (la cabeza de la lista, cada vez sieve se llama):

 x 'mod' p

Si el módulo no es igual a 0:

 x 'mod' p /= 0

Entonces el elemento x se mantiene en la lista. Si es igual a 0, se filtra. Esto tiene sentido: si x es divisible por p, que x es divisible por más de 1 y por sí mismo, por lo que no es primo.

Define un generador – un transformador de corriente llamado “tamiz”,

Sieve s = 
  while( True ):
      p := s.head
      s := s.tail
      yield p                             -- produce this
      s := Filter (nomultsof p) s         -- go next

primes := Sieve (Nums 2)

que utiliza una forma de curry de una función anónima equivalente a

nomultsof p x = (mod x p) /= 0

Ambos Sieve y Filter son operaciones de construcción de datos con estado interno y semántica de paso de argumentos por valor.


Aquí podemos ver que el problema más evidente de este código es no, repetir no que utiliza la división de prueba para filtrar los múltiplos de la secuencia de trabajo, mientras que podría encontrarlos directamente, contando en incrementos de p. Si reemplazáramos el primero por el segundo, el código resultante aún tendría una complejidad de ejecución abismal.

No, su problema más evidente es que pone un Filter además de su secuencia de trabajo demasiado pronto, cuando realmente debería hacer eso Solo después el cuadrado del primo se ve en la entrada. Como resultado, crea una cuadrático número de Filters comparado con lo que realmente se necesita. La cadena de Filters que crea es demasiado largo y la mayoría de ellos ni siquiera son necesarios.

La versión corregida, con la creación del filtro. pospuesto hasta el momento adecuado, es

Sieve ps s = 
  while( True ):
      x := s.head
      s := s.tail
      yield x                             -- produce this
      p := ps.head
      q := p*p
      while( (s.head) < q ):
          yield (s.head)                  --    and these
          s := s.tail
      ps := ps.tail                       -- go next
      s  := Filter (nomultsof p) s

primes := Sieve primes (Nums 2) 

o en Haskell,

primes = sieve primes [2..] 
sieve ps (x:xs) = x : h ++ sieve pt [x | x <- t, rem x p /= 0]
     where (p:pt) = ps
           (h,t)  = span (< p*p) xs 

rem se usa aquí en lugar de mod ya que puede ser mucho más rápido en algunos intérpretes, y los números son todos positivos aquí de todos modos.

Medir los órdenes de crecimiento locales de un algoritmo tomando sus tiempos de ejecución t1,t2 en puntos del tamaño del problema n1,n2, como logBase (n2/n1) (t2/t1), obtenemos O(n^2) para el primero, y justo arriba O(n^1.4) por el segundo (en n primos producidos).


Solo para aclararlo, las partes faltantes podrían definirse en este lenguaje (imaginario) simplemente como

Nums x =            -- numbers from x
  while( True ):
      yield x
      x := x+1

Filter pred s =     -- filter a stream by a predicate
  while( True ):
      if pred (s.head) then yield (s.head)
      s := s.tail

ver también.


actualizar: Curiosamente, la primera instancia de este código en el manual SASL de 1976 de David Turner según el libro Haskell de 1992 de AJT Davie,

    primes = sieve [2..]

    sieve (p:nos) = p : sieve (remove (multsof p) nos)

realmente admite dospares de implementaciones para remove y multsof yendo juntos: un par para el tamiz de división de prueba como en esta pregunta, y el otro para la eliminación ordenada de los múltiplos de cada primo generados directamente por el conteo, también conocido como el auténtico Tamiz de Eratóstenes (ambos no se aplazarían, por supuesto). En Haskell,

    multsof p n = rem n p==0            remove m xs = filter (not . m) xs

    multsof p = [p*p, p*p+p..]          remove m xs = minus xs m

(Si tan solo hubiera pospuesto eligiendo la implementación real ...)

En cuanto al código pospuesto, en un pseudocódigo con "patrones de lista" podría haber sido

    primes = [2, ...sieve primes [3..]]

    sieve [p, ...ps] [...h, p*p, ...nos] =
                     [...h, ...sieve ps (remove (multsof p) nos)]

que en Haskell real, con ViewPatterns, es

-# LANGUAGE ViewPatterns #-

primes = 2 : sieve primes [3..]

sieve (p:ps) (span (< p*p) -> (h, _:nos)) 
                             = h ++ sieve ps (remove (multsof p) nos)

pero probablemente todavía no había patrones de visualización en ese entonces, y es un poco menos aparente visualmente de esa manera.

Comentarios y valoraciones

Si eres capaz, eres capaz de dejar un escrito acerca de qué le añadirías a este post.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *