Skip to content

Commit

Permalink
More
Browse files Browse the repository at this point in the history
  • Loading branch information
pgujjula committed Apr 5, 2024
1 parent d2cb8f9 commit 980913a
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 14 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,5 @@ classic [The Genuine Sieve of Eratosthenes](https://www.cs.hmc.edu/~oneill/paper
Constrast this to other simple prime generation implementations, such as <pre>
primes = sieve [2..]
sieve (p : xs) = p : sieve [x | x <- xs, x \`rem\` p > 0]</pre>
which are actually trial division and not faithful implementations of
the Sieve of Erastosthenes.
which are actually trial division and not faithful implementations of the Sieve
of Erastosthenes.
45 changes: 33 additions & 12 deletions docs/ALGORITHM.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ Let's lay out the elements of `smooth3` in a 2D grid:
: | : : : : ॱ.
</pre>

Since `(*)` is monotonically increasing in both arguments, each element in the grid is ≥
its neighbors above and to the left. Therefore, when an element appears in
`smooth3`, its up- and left-neighbors must already have appeared.
Since `(*)` is monotonically increasing in both arguments, each element in the
grid is ≥ its neighbors above and to the left. Therefore, when an element
appears in `smooth3`, its up- and left-neighbors must already have appeared.

Let's think about `smooth3` after 3 elements have been produced:

Expand All @@ -82,7 +82,11 @@ Let's think about `smooth3` after 3 elements have been produced:
|
: | : : : : ॱ.
</pre>
After producing `1, 2, 3`, the next element in `smooth3` can only be one of `{4, 6, 9}`. We know this without preforming any comparisons, just by the positions of these elements in the grid, as these are the only elements whose up- and left-neighbors have already been produced.

After producing `1, 2, 3`, the next element in `smooth3` can only be one of
`{4, 6, 9}`. We know this without preforming any comparisons, just by the
positions of these elements in the grid, as these are the only elements whose
up- and left-neighbors have already been produced.

After producing the next element, `4`, our grid becomes

Expand All @@ -100,7 +104,10 @@ After producing the next element, `4`, our grid becomes
: | : : : : ॱ.
</pre>

Now, the "frontier" of possible next elements is `{6, 8, 9}`. We added `8` to the frontier as it is no longer "blocked" by `4`. Note that we cannot yet add `12` to the frontier even though it's no longer blocked by `4`, as it's still blocked by `6`.
Now, the "frontier" of possible next elements is `{6, 8, 9}`. We added `8` to
the frontier as it is no longer "blocked" by `4`. Note that we cannot yet add
`12` to the frontier even though it's no longer blocked by `4`, as it's still
blocked by `6`.

After producing the next element, `6`, our grid becomes

Expand All @@ -118,7 +125,8 @@ After producing the next element, `6`, our grid becomes
: | : : : : ॱ.
</pre>

Now, the frontier consists of `{8, 9, 12}`. We added `12` as it is now unblocked by `4` and `6`. We cannot yet add `18`, as it is still blocked by `9`.
Now, the frontier consists of `{8, 9, 12}`. We added `12` as it is now unblocked
by `4` and `6`. We cannot yet add `18`, as it is still blocked by `9`.

This investigation suggests the following algorithm for producing `smooth3`:

Expand All @@ -141,7 +149,8 @@ applyMerge :: (Ord c) => (a -> b -> c) -> [a] -> [b] -> [c]
```

where `applyMerge f xs ys` is
a sorted list of all `f x y`, for each `x` in `xs` and `y` in `ys`. In particular, `smooth3 = applyMerge (*) powersOf2 powersOf3`.
a sorted list of all `f x y`, for each `x` in `xs` and `y` in `ys`. In
particular, `smooth3 = applyMerge (*) powersOf2 powersOf3`.
We can also define a more general

```haskell
Expand All @@ -151,9 +160,19 @@ applyMergeBy :: (c -> c -> Ordering) -> (a -> b -> c) -> [a] -> [b] -> [c]
taking a custom comparison function.

## Implementation and complexity
We can use a priority queue to maintain the frontier of elements. To determine in step 3 whether a down- or right-neighbor is unblocked, we maintain two `IntSet`s, for the `x`- and `y`- indices of all elements in the frontier. Then in step 3, if the `x`- and `y`-index of a neighbor are not in the respective `IntSet`s, the neighbor is unblocked and can be added to the frontier.

After producing $n$ elements, the size of the frontier is at most $O(\sqrt{n})$ elements. <i>(TODO: add a proof of this)</i> Manipulating the priority queue and the `IntSet`s to produce the next element takes $O(\text{log } \sqrt{n}) = O(\text{log } n)$ time. Therefore, producing $n$ elements of `applyMerge f xs ys` takes $O(n \log n)$ time and $O(\sqrt{n})$ auxiliary space, assuming that `f` and `compare` take $O(1)$ time.
We can use a priority queue to maintain the frontier of elements. To determine
in step 3 whether a down- or right-neighbor is unblocked, we maintain two
`IntSet`s, for the `x`- and `y`- indices of all elements in the frontier. Then
in step 3, if the `x`- and `y`-index of a neighbor are not in the respective
`IntSet`s, the neighbor is unblocked and can be added to the frontier.

After producing $n$ elements, the size of the frontier is at most $O(\sqrt{n})$
elements. <i>(TODO: add a proof of this)</i> Manipulating the priority queue and
the `IntSet`s to produce the next element takes
$O(\text{log } \sqrt{n}) = O(\text{log } n)$ time. Therefore, producing $n$
elements of `applyMerge f xs ys` takes $O(n \log n)$ time and $O(\sqrt{n})$
auxiliary space, assuming that `f` and `compare` take $O(1)$ time.

## More examples

Expand Down Expand Up @@ -186,9 +205,9 @@ squarefrees = [1..] `minus` applyMerge (*) (map (^2) primes) [1..]
```

# Prior work

In <code>[data-ordlist](https://www.stackage.org/lts/package/data-ordlist)</code>,
there is
<code>[mergeAll](https://www.stackage.org/haddock/lts/data-ordlist/Data-List-Ordered.html#v:mergeAll) :: Ord a => [[a]] -> [a]</code>,
there is <code>[mergeAll](https://www.stackage.org/haddock/lts/data-ordlist/Data-List-Ordered.html#v:mergeAll) :: Ord a => [[a]] -> [a]</code>,
which merges a potentially infinite list of ordered lists, where the heads of
the inner lists are sorted. This is more general than `applyMerge`, in the sense
that we can implement `applyMerge` in terms of `mergeAll`:
Expand All @@ -202,7 +221,9 @@ applyMerge f xs ys =
However, `mergeAll` uses $O(n)$ auxiliary space in the worst case, while our
implementation of `applyMerge` uses just $O(\sqrt{n})$ auxiliary space.

[^1]: Note that this is really the Sieve of Erastosthenes, as defined in the classic [The Genuine Sieve of Eratosthenes](https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf). Constrast this to other simple prime generation implementations, such as <pre>
[^1]: Note that this is really the Sieve of Erastosthenes, as defined in the
classic [The Genuine Sieve of Eratosthenes](https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf).
Constrast this to other simple prime generation implementations, such as <pre>
primes = sieve [2..]
sieve (p : xs) = p : sieve [x | x <- xs, x \`rem\` p > 0]</pre>
which are actually trial division and not faithful implementations of the Sieve of Erastosthenes.

0 comments on commit 980913a

Please sign in to comment.