diff --git a/README.md b/README.md index 30ea1bc..6657054 100644 --- a/README.md +++ b/README.md @@ -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
 primes = sieve [2..]
 sieve (p : xs) = p : sieve [x | x <- xs, x \`rem\` p > 0]
-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. diff --git a/docs/ALGORITHM.md b/docs/ALGORITHM.md index 6577d7d..60b83fe 100644 --- a/docs/ALGORITHM.md +++ b/docs/ALGORITHM.md @@ -63,9 +63,9 @@ Let's lay out the elements of `smooth3` in a 2D grid: : | : : : : ॱ. -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: @@ -82,7 +82,11 @@ Let's think about `smooth3` after 3 elements have 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 `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 @@ -100,7 +104,10 @@ After producing the next element, `4`, our grid becomes : | : : : : ॱ. -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 @@ -118,7 +125,8 @@ After producing the next element, `6`, our grid becomes : | : : : : ॱ. -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`: @@ -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 @@ -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. (TODO: add a proof of this) 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. (TODO: add a proof of this) 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 @@ -186,9 +205,9 @@ squarefrees = [1..] `minus` applyMerge (*) (map (^2) primes) [1..] ``` # Prior work + In [data-ordlist](https://www.stackage.org/lts/package/data-ordlist), -there is -[mergeAll](https://www.stackage.org/haddock/lts/data-ordlist/Data-List-Ordered.html#v:mergeAll) :: Ord a => [[a]] -> [a], +there is [mergeAll](https://www.stackage.org/haddock/lts/data-ordlist/Data-List-Ordered.html#v:mergeAll) :: Ord a => [[a]] -> [a], 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`: @@ -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
+[^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 
 primes = sieve [2..]
 sieve (p : xs) = p : sieve [x | x <- xs, x \`rem\` p > 0]
which are actually trial division and not faithful implementations of the Sieve of Erastosthenes.