Topic: Streams Continued Date: Nov. 18, 2009 Number: 25 Examples: streams.hs, digitalLogic.hs Reading: Chapter 14 -- Hamming's problem Continued - the first section is repeated. First, seems clear that if we want this in increasing order we need some sort of merge function, and one that eliminates duplicates: merge :: Ord a => [a] -> [a] -> [a] merge xs@(x:xt) ys@(y:yt) = if x < y then x:(merge xt ys) else if y < x then y:(merge xs yt) else x:(merge xt yt) Now how should the stream work? Hamming starts with 1. For every number n in hamming, 2*n, 3*n, and 5*n are also in hamming. Gives us: -- Computes all numbers of form 2^i * 3^j * 5^k hamming = 1 : (merge (map (*2) hamming) (merge (map (*3) hamming) (map (*5) hamming))) Do the bubble diagram and show how it works. Show how the three streams are created, merged. We can generalize this to any list of factors. What we are doing at the end is foldr'ing merge over what we get by mapping (*k) over the hamming stream. So given a finite list of integers, the following works: -- Computes all numbers whose factors are integers in a given list, -- in increasing order. A generalized version of hamming. prodOfFactors :: [Integer] -> [Integer] prodOfFactors ps = 1:(foldr1 merge (map (\p -> map (p*) (prodOfFactors ps)) ps)) hamming2 = take 20 (prodOfFactors [2, 3, 5]) But what if we want an INFINITE list of factors? Then we can't use foldr, or foldl either. So impossible, right? Maybe not. Warning - the following is not for the faint of heart! It will really test your understanding of streams. --------- Suppose we had a function mergeStreams that could merge an infinite number of streams! Then we could use this instead of foldr1 above: -- Version of prodOfFactors that handles an infinite stream of factors! -- Assumes that the factors list is in increasing order. prodOfFactorsInf :: Stream -> Stream prodOfFactorsInf ps = 1 : mergeStreams (map (\p -> map (p*) (prodOfFactorsInf ps)) ps) ----- Note that prodOfFactorsInf starts by creating an infinite stream of streams - one for each factor! So how do we merge an infinite number of streams? We can't look at the first item of each stream to merge them, so if the stream of streams were a general one this task would be impossible. But there is a trick. Let's first look at a related problem: ----- Enumerating everything in a stream of streams. The problem of merging an infinite number of streams is related to a simpler task: how can you enumerate (make a single stream) out of a stream of streams? Can't just concat them all - never finish with the first. Trick is to go in diagonals. So the order to visit the things is: 1) the first diagonal (thing in position (1,1)) 2) the second diagonal (things in positions (2,1) and (1,2)) 3) the third diagonal (things in positions (3,1), (2,2), (1,3)) ... How can we do this? Similar idea - have a current list of streams, take the head of each, then repeat on the tails of the current list plus an additional stream: enumerateStreams :: [Stream] -> Stream enumerateStreams (s:ss) = helper [s] ss where helper xss (ys:yss) = map head xss ++ helper (ys : (map tail xss)) yss We want to test this on some stream of streams where we can tell what is coming from which stream. So we create a stream of powers of primes: [[2,4,8,16, ...], [3,9,27,81,...], [5,25,125,625,...], ... ] We do this in two steps. First, we create a stream of powers of n, where n is a parameter: -- Creates a stream [1, n, n^2, n^3, ...] powStream :: Integer -> Stream powStream n = 1 : map (*n) (powStream n) Then we make a stream of streams: streamOfStreams :: [Stream] streamOfStreams = map (\x -> tail (powStream x)) primes To see part of this: takeSomeSofS = take 10 ((map (take 4)) streamOfStreams) So enumerate streamOfStreams => [2,3,4,5,9,8,7,25,27,16, ...] --------- How does this help us merge an infinite number of streams? We can use the idea of merging one stream at a time to a previous group of merged streams. If the factors are unique and in increasing order, we won't need to take the first item in kth stream until we have taken at least one item from each of the first k-1 streams. Thus we don't need the kth stream involved in the merge until we are ready to generate the kth item in the output stream. We: 1) Take the first item from the first stream, and then merge the tail of the first stream with the second stream. 2) Take the first remaining item from the merge of the first two streams, and then merge the third stream with what is left. 3) Take the first remaining item from the merge of the first three streams, and then merge the fourth stream with what is left. ... k) Take the first remaining item from the merge of the first k streams, and then merge the (k+1)st stream with what is left. ... The following code accomplishes this: -- Merges an infinite number of streams into a single stream. -- Assumes that each stream is in increasing order and that -- the first value of the kth stream is larger than at least -- k-1 distinct values in the first k-1 streams. mergeStreams :: [Stream] -> Stream mergeStreams (xs : xss) = mergeHelper xs xss where mergeHelper (minVal : restMerged) (xs : xss) = minVal : mergeHelper (merge restMerged xs) xss The first parameter to mergeHelper is the stream of remaining items in the set of streams merged so far. We will make the first value (minVal) of this stream the next item in the output stream. The second parameter to mergeHelper is the remaining infinite stream of streams. We will take the first stream from this (xs) and merge it with the remaining items in the merges streams (restMerged). Then the rest of the stream will be the mergeHelper of the merged streams and the remaining unmerged streams (xss). Why would we want to do this? Because we can? And because it leads to the world's most convoluted way to generate the natural numbers and odds: -- Some really convoluted ways to get naturals and odds! -- Naturals are 0 and numbers that are products of primes. naturals5 = 0 : prodOfFactorsInf primes -- Odds are products of all primes except 2. odds2 = prodOfFactorsInf (tail primes) -- Numbers that have no factors of 2, 3, or 5 antiHamming = prodOfFactorsInf ((tail . tail . tail) primes) This idea of merging an infinite number of streams can be used for other things. Consider the Goldbach conjecture, probably the most famous unsolved problem in mathematics. One form of it is that every even number greater than 4 can be expressed as the sum of two odd primes. This has been open since 1742! You might want to test this conjecture for small numbers. One approach is to write a computer program to sum pairs of odd primes, keep track of the results, and see if all even integers greater than 4 and less than some limit can be obtained as the sum of some pair of odd primes. (This has been tested up to 10^18, by the way!) How could you write such a program? One approach would be to generate a stream for each odd prime, consisting of that prime added to each prime greater than or equal to itself. This can be generalized to any increasing list of numbers, with a stream for each number: listsOfSums :: Stream -> [Stream] listsOfSums xs@(x:xt) = map (x +) xs : listsOfSums xt Given this, we can define: goldbach = mergeStreams (listsOfSums (tail primes)) This will be an increasing list of numbers obtained by adding two odd primes. We can then compare it to evens to see if there is anything missing. (Might take a while, probably forever!) Alternately, we might want to generate a list of numbers that is the sum of two squares: squares = map (^2) (tail naturals) sumsOfSquares = mergeStreams (listsOfSums squares) You can probably think of other problems that can be solved by generating an infinite list of streams and then merging them. --------- Can also generate all prefixes of a stream. Use an accumulator pre: -- Prefixes prefixes :: [a] -> [[a]] prefixes str = prefixHelp [] str where prefixHelp pre (x:xs) = let next = (pre ++ [x]) in next : prefixHelp next xs triangular2 = map sum (prefixes naturals) I will take this opportunity to note that it is occasionally possible to use foldr on a stream. An example is the following: concatPre = concat (prefixes naturals) Remember that concat lst = foldr (++) [] lsts This works just fine, in the sense that it creates a stream and we can do something like: take 10 concatPre Why? Because ++ operates only on its first argument, leaving the second unevaluated. Note that foldr (++) [] lsts is equivalent to: [] ++ (lst1 ++ (lst2 ++ (lst3 ++ ... We can therefore take the first k items without having to evaluate to the end. Lazy evaluation saves us from evaluating the whole stream. Using scanr instead of foldr will create a stream of streams. Therefore head (scanr (++) [] (prefixes naturals)) gives the same stream as concatPre. Surprisingly, (scanr (++) [] (prefixes naturals)) !! n works for any n, returning the stream: [0..n] ++ [0..(n+1)] !! [0..(n+2)] ++ ... ----- Book gives examples of induction on infinite lists. Base case is now bottom. ----- As a final example I will show the Thue-Morse sequence, which arises in a branch of mathematics called Morse Theory. It is an infinite non-repeating sequence. It contains no subsequence of the form xxx, where x is a non-empty subsequence. The first 16 values are: [0,1,1,0,1,0,0,1,1,0,0,1,0,1,1,0] There are two ways to describe it. The first is to recursively define its prefixes of length 2^k as follows: p(0) = [0] p(n) = p(n-1) ++ q(n-1), where q(n-1) is the complement of p(n-1) (Verify that the sequence above has this property.) Using this definition, we can generate the sequence as follows: -- Takes a sequence of 0's and 1's and complements it complement :: [Int] -> [Int] complement s = map (1-) s -- Given a sequence xs, treats it as a prefix and generates the rest -- of the Thue-Morse sequence. genRest :: [Int] -> [Int] genRest xs = let cxs = complement xs in cxs ++ genRest (xs ++ cxs) tm = 0 : genRest [0] An alternate definition is that the sequence is that it starts with 0 and then is interleaved with its complement! (Note that the even positions of the sequence are identical to the sequence, while the odd positions are the complement of the sequence.) Using this definition we can define the sequence as follows. -- Interleaves two streams interleave :: [a] -> [a] -> [a] interleave (x:xs) ys = x : interleave ys xs tm2 = 0 : interleave (complement tm2) (tail tm2) One final way written by Jay Misra that avoids calling complement. us is the sequence, vs is the complement of the sequence. us = 0 : ut vs = 1 : vt ut = interleave vs ut vt = interleave us vt Practical uses of Streams Streams are used to facilitate communication within operating systems. A common construct in Unix is the pipe operation "|". The command: prog1 | prog2 says to run prog1 and use its output as the input to prog2. It is useful to allow prog1 and prog2 to run independently, or even in parallel. prog1 should be able to get arbitrarily far ahead of prog2. prog2 waits when the pipe is empty. One way to model a pipe is as a stream, where prog1 thinks it is writing to a stream and prog2 thinks that it is reading from a stream. Because an operating system is designed to run potentially forever, using streams to connect "producers" to "consumers" is a useful model. In what is below we look at a digital simulator. Circuits are designed by connecting gates (AND, OR, NOT, etc.) together with wires. For computers and similar circuits there is a clock, which is an infinite stream 0, 1, 0, 1, ... The input and output of each gate is a stream representing the values taken on by that wire in each time period. *** Not required or discussed in class *** Example: Digital Logic Simulator For a more practical example, we look at digital logic simulation. I am giving this to you so that you can study it if you know about digital logic, but I won't do it in class. (I tried the first term, and discovered that I would need to spend a class or two on digital logic for the example to make sense.) In a clocked digital circuit each clock pulse we get a value on each input line, forever. So can simulate using streams. type Stream = [Int] zeros = 0:zeros ones = 1:ones stream1 = cycle [0,1,0,1] stream2 = cycle [0,0,1,1] Draw the not, and , or , nand, nor, xor gates. Truth tables: x y | not x | x and y | x or y | x xor y | x nand y | x nor y 0 0 1 0 0 0 1 1 0 1 1 0 1 1 1 0 1 0 0 0 1 1 1 0 1 1 0 1 1 0 0 0 Can implement using streams: notGate :: Stream -> Stream notGate (x:xs) = (1-x) : notGate xs -- Ex: take 2 $ notGate $ stream1 andGate :: Stream -> Stream -> Stream andGate (x:xs) (y:ys) = x*y : andGate xs ys -- Ex: take 4 $ andGate stream2 stream1 -- A function to allow us to construct any binary gate. binaryGate :: (Int -> Int -> Int) -> Stream -> Stream -> Stream binaryGate f (x:xs) (y:ys) = f x y : binaryGate f xs ys -- Define all of the usual gates. andGate2, orGate, nandGate, norGate, xorGate :: Stream -> Stream -> Stream andGate2 = binaryGate (*) -- Ex: take 4 $ andGate2 stream2 stream1 orGate = binaryGate (\x y -> x+y-x*y) nandGate = binaryGate (\x y -> 1-x*y) norGate = binaryGate (\x y -> 1-x-y+x*y) xorGate = binaryGate (\x y -> x+y-2*x*y) Given these, can build circuits. Show how to create a half-adder. Input two bits, return carry and sum: -- Adds a pair of bit streams, returning (carry, sum) pair halfAdder :: Stream -> Stream -> [(Int, Int)] halfAdder x y = zip (andGate x y) (xorGate x y) Then take 2 half adders and an or gate to create a full adder: -- Adds one pair of bit streams, with carry in and out. -- Inputs are x and y. The carry from the previous gate is cIn. fullAdder :: Stream -> Stream -> Stream -> [(Int, Int)] fullAdder x y cIn = let (c1, s) = unzip $ halfAdder x y (c2, sum) = unzip $ halfAdder s cIn cOut = orGate c1 c2 in zip cOut sum Finally, take 4 full adders and chain together, with the carry bit from column i added into column i+1, and 0 into column 0: -- Four bit adder. -- The first list has the streams for x. The second has the streams -- for y. The output has five streams, because the carry is the most -- significant stream. fourBitAdder :: [Stream] -> [Stream] -> [Stream] fourBitAdder xss yss = let (c0, s0) = unzip $ fullAdder (xss !! 0) (yss !! 0) zeros (c1, s1) = unzip $ fullAdder (xss !! 1) (yss !! 1) c0 (c2, s2) = unzip $ fullAdder (xss !! 2) (yss !! 2) c1 (c3, s3) = unzip $ fullAdder (xss !! 3) (yss !! 3) c2 in [s0, s1, s2, s3, c3] Test it. First, create 8 input streams, so create every possible pair of 4-bit numbers (2^8 or 256 possibilities). stream3 = cycle (take 4 zeros ++ take 4 ones) stream4 = cycle (take 8 zeros ++ take 8 ones) stream5 = cycle (take 16 zeros ++ take 16 ones) stream6 = cycle (take 32 zeros ++ take 32 ones) stream7 = cycle (take 64 zeros ++ take 64 ones) stream8 = cycle (take 128 zeros ++ take 128 ones) xStrs = [stream1, stream2, stream3, stream4] yStrs = [stream5, stream6, stream7, stream8] adds = fourBitAdder xStrs yStrs So adds tests all possible combinations. Try a few. Create a function to allow us to print the problem reasonably: -- Prints out the addition problem appearing on nth row of adds. printAdd :: Int -> IO () printAdd n = putStrLn ((show $ extractRow n xStrs) ++ " + " ++ (show $ extractRow n yStrs) ++ " = " ++ (show $ extractRow n adds)) -- Extracts a row out of a list of streams. Reverses so normal -- addition order. extractRow :: Int -> [Stream] -> [Int] extractRow n = reverse . (map (!! n)) Another example: a memory circuit, or a "latch". First, we need a delay gate, so the value that came into the gate last clock period comes out this period: -- Takes a signal and delays it one clock period. delayGate :: Stream -> Stream delayGate x = 0 : x Now look at a latch. While control c is 0, output = input. When c is 1, the value is "latched" and remains the same until c goes back to 0. Note the delayGate f gets the previous output o. o gets the or of x and y, where x passes on f when c is 1 (and is 0 otherwise), and y passes on i when c is 0 (and is 0 otherwise). So o gets i when c is 0, and the previous value of o when c is 1. latch :: Stream -> Stream -> Stream latch c i = let f = delayGate o x = andGate f c z = notGate c y = andGate z i o = orGate x y in o -- Ex: take 7 $ latch ([0,0,1,1,1,0,0]++zeros) ([0,1,0,1,0,1,0]++zeros)