Archive for January, 2013

Programming Praxis – Imperative Heaps

January 25, 2013

In today’s Programming Praxis exercise, our goal is to implement an array-based heap. Let’s get started, shall we?

We’ll be using Vectors as our array datatype.

import qualified Data.Vector as V

The algorithm assumes that the array is 1-based instead of the usual 0-based. Having to do this at every array lookup would be annoying, so we define a new array index operator.

(!) :: V.Vector a -> Int -> a
v ! i = v V.! (i-1)

Swapping two elements can be done without having to use a temporary variable thanks to Vector’s bulk update feature.

swap :: Int -> Int -> V.Vector a -> V.Vector a
swap i j heap = heap V.// [(i-1, heap ! j), (j-1, heap ! i)]

Sifting up is fairly simple: keep swapping elements with their parents as long as necessary.

siftup :: Ord a => Int -> V.Vector a -> V.Vector a
siftup i heap = let j = div i 2 in if i == 1 || heap ! j <= heap ! i
                then heap else siftup j $ swap i j heap

Sifting down is less convenient, since we need to count up to n, necessitating a worker function. A quick tip on recursive worker functions: make sure they call themselves rather than their parent functions. I initially didn’t, and it took me quite a while to find the bug.

siftdown :: Ord a => Int -> V.Vector a -> V.Vector a
siftdown n = f 1 where
    f i heap = if 2*i > n || heap ! i <= c  then heap
               else f j $ swap i j heap where
                   (c, j) = minimum [(heap ! x, x) | x <- [2*i, 2*i+1], x <= n]

Sorting is a matter of first sifting up and then sifting down.

hsort :: Ord a => V.Vector a -> V.Vector a
hsort heap = foldr (\i -> siftdown (i - 1) . swap 1 i)
             (foldl (flip siftup) heap [2..V.length heap]) [2..V.length heap]

And finally a test to see if everything is working properly.

main :: IO ()
main = print $ hsort (V.fromList [4,7,8,1,5,3,2,9,6]) == V.fromList [9,8..1]

Programming Praxis – Translate CSV To HTML

January 15, 2013

In today’s Programming Praxis exercise, our goal is to translate a csv file to an HTML table. Let’s get started, shall we?

We will be using the Text.CSV library to read in the csv file.

import Text.CSV
import Text.Printf

Converting the rows to a table consists of doing pretty much the same thing three times: the content is transformed in some way and surrounded by two html tags. Surprisingly, Haskell was not able to correctly figure out the type of the wrap function, complaining about an ambiguity. Presumably this is caused by a combination of printf, which is pretty polymorphic in and of itself and using the wrap function to process a [[String]], [String] and [Char], repsectively. Oh well, having to manually specify the type is not the end of the world.

toTable :: [[String]] -> String
toTable = wrap "table" unlines . wrap "tr" concat $ wrap "td" id id where
    wrap :: String -> ([b] -> String) -> (a -> b) -> [a] -> String
    wrap tag combine f xs = printf "<%s>%s</%s>" tag (combine $ map f xs) tag

All that’s left to do is to read in the csv file and call the toTable function if it was succesfully parsed.

main :: IO ()
main = putStrLn . either show toTable =<< parseCSVFromFile "test.csv"

Programming Praxis – Four Points Determine A Square

January 2, 2013

In today’s Programming Praxis exercise, our goal is to determine whether four given points in a 2D plane make up a square. Let’s get started, shall we?

AnĀ  import:

import Data.List

The provided solution calculates the squares distances between the first and the three other points and considers the points a square when the smallest two are equal and the largest is the sum of the two smallest, i.e. a2 + b2 = c2. This solution is incorrect, however. If we take the points (0, 0), (1, 0), (0, 1) and (-1, -1), both conditions are satisfied despite the fact that the four points do not form a square. To handle this case correctly, we can use the same basic approach, albeit expanded a bit. Instead of just three distances, we calculate the distances between all six pairs. Of these six, the four smallest ones should be equal, as should the two larger ones. Pythagoras’ theorem can remain the same.

isSquare :: (Num a, Ord a) => (a, a) -> (a, a) -> (a, a) -> (a, a) -> Bool
isSquare p q r s = and [a == b, b == c, c == d, e == f, a + b == e] where
    [a,b,c,d,e,f] = sort [l | (h:t) <- tails [p,q,r,s], l <- map (dist h) t]
    dist (x1,y1) (x2,y2) = (x2-x1)^2 + (y2-y1)^2

Testing reveals that this version handles the old test cases correctly, as well as the example described above.

main :: IO ()
main = do print $ isSquare (0,0) (0,1) (1,1) (1,0)
          print $ isSquare (0,0) (2,1) (3,-1) (1, -2)
          print $ isSquare (0,0) (1,1) (0,1) (1,0)
          print . not $ isSquare (0,0) (0,2) (3,2) (3,0)
          print . not $ isSquare (0,0) (3,4) (8,4) (5,0)
          print . not $ isSquare (0,0) (0,0) (1,1) (0,0)
          print . not $ isSquare (0,0) (0,0) (1,0) (0,1)
          print . not $ isSquare (0,0) (1,0) (0,1) (-1,-1)