Zulip Chat Archive

Stream: lean4

Topic: On the search of a prime sieve


Bernardo Borges (Mar 15 2024 at 18:55):

Hello everybody! I am tackling a programming problem that involves the Sieve of Eratosthenes, meaning I want to precalculate the prime numbers up to 2000000 (2M). The first naive approach was the pure functional one:

def fromTo (l r : Nat) : List Nat := map (·+l) $ range (r-l+1)

partial def primes : List Nat := go (fromTo 2 2000000)
  where
    go : List Nat  List Nat
    | [] => []
    | p :: xs => p :: go (xs.filter  (· % p != 0))

No, it would'nt even run.
To make it more feasible, i diverged to a imperative approach. Now I will try to generate a is_prime : Array Bool that tells me what I need to know. I arrived at this code, based on a Rust submission that worked:

/-- Range with `[l,r)` step `s` -/
def fromToStep (l r s : Nat) : List Nat := map (·+l) $ map (·*s) $ range ((r-l+1)/s)

def LIM := 2000000

def sieve : IO (Array Bool) := do
  let mut p := Array.mk $ replicate (LIM+1) true
  p := p.set! 0 false
  p := p.set! 1 false
  for i in fromToStep 4 (LIM+1) 2 do
    p := p.set! i false
  let mut i := 3
  while i * i <= LIM + 1 do
    if p.get! i then
      for j in fromToStep (i*i) (LIM+1) (2*i) do
        p := p.set! j false
    i := i + 1
  return p

This attempt gave me a

Stack overflow detected. Aborting.

What can be done next? Can I use the heap? Happy to hear any pointers!

Jireh Loreaux (Mar 15 2024 at 19:45):

Well, I can't help but say that if you're implementing the Sieve of Erastosthenes, then you should really have a look at this paper: https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf

Jireh Loreaux (Mar 15 2024 at 19:45):

Barring that I have nothing meaningful to say .

Number Eighteen (Mar 15 2024 at 21:03):

My best attempt at implementing naive Eratosthenes:

@[specialize, inline]
partial def iterfun {α : Type u} (x y : Nat) (a : α) (f : α  α) : α :=
  cond (y  x) a (iterfun x (y-1) (f a) f)

partial def simpleSieve (n : Nat) : Array Nat :=
  let s := n.toFloat.sqrt.ceil.toUInt64.toNat
  let rec loop i A :=
    if i  s then
      A.foldl (fun (R, j) b  (cond b (R.push j) R, j.succ)) (#[], 0) |>.fst
    else
      loop i.succ <| cond A[i]!
        (iterfun i (n/i).succ (A, i)
          (fun (R, j)  (R.set! (j * i) false, j.succ)) |>.fst)
        A
  loop 2 <| Array.mkArray n.succ true

This can sift up to a couple of billion integers in 30sec/1min. All optimizations I tried on this code made it significantly slower, which is very frustrating.


Last updated: May 02 2025 at 03:31 UTC