Ockham
Ockham

Reputation: 475

Haskell Printing a List with some formatting

Just started learning Haskell a few days ago and I've come across a few issues. The first issue deals with printing a list of numbers. The desired behavior is as follows:

input: [1,2,3,4,5,6]

output: 1 2 3 | 4 5 6

So its a simple concept, I just need to output the elements of a list with the "|" symbol inserted between every three numbers, but I can't for the life of me figure it out. It seems like most of the stuff I've tried involves strings and even if I were able to get the list to strings such as ["1", "2", "3", ...] all the methods I've tried print the numbers each on their own line which is not what I need.

Any help would be greatly appreciated.

Upvotes: 0

Views: 1723

Answers (4)

hammar
hammar

Reputation: 139840

Using the split package (recently added to the Haskell Platform):

> import Data.List         -- for intercalate
> import Data.List.Split   -- for chunksOf
> intercalate " | " . map unwords . chunksOf 3 $ map show [1..7]
"1 2 3 | 4 5 6 | 7"

Relevant documentation: chunksOf, unwords, intercalate.

Upvotes: 5

Daniel Fischer
Daniel Fischer

Reputation: 183858

The first part is the easiest, you need to convert the numbers to Strings,

format :: (Num a, Show a) => [a] -> String
format xs = result
  where
    strings = map show xs

does that. Then we need to split any list into chunks of three (more general, n) elements. splitAt splits a list into a front part of the desired number of elements - if the list is long enough - and a remainder. Iterating the procedure on the remainder, while that is not empty leads to the desired result.

chunk :: Int -> [a] -> [[a]]
chunk _ [] = []
chunk n xs = ys : chunk n zs
  where
    (ys, zs) = splitAt n xs

That is a recurring pattern, so there is a combinator for that, and we could also write

import Data.List (unfoldr)

chunk :: Int -> [a] -> [[a]]
chunk n = unfoldr split
  where
    split [] = Nothing
    split xs = Just $ splitAt n xs

So we can continue our format,

format :: (Num a, Show a) => [a] -> String
format xs = result
  where
    strings = map show xs
    chunks = chunk 3 strings

Then we need to insert a "|" between all chunks, that is done by intercalate from Data.List, and finally, concatenate all strings with spaces between them, that's what unwords does, so

format :: (Num a, Show a) => [a] -> String
format xs = result
  where
    strings = map show xs
    chunks = chunk 3 strings
    result = unwords $ intercalate "|" chunks

Or

format = unwords . intercalate "|" . chunk 3 . map show

Upvotes: 1

dave4420
dave4420

Reputation: 47042

Here's one way.

import Data.List (cycle)

format :: Show a => [a] -> String
format = concat . zipWith (++) ("" : cycle [" ", " ", " | "]) . map show

This does has the drawback that the grouping into groups of three is hard-coded, but it is not too difficult to generalise.

Upvotes: 3

AndrewC
AndrewC

Reputation: 32455

You could do

threes [] = ""
threes xs = let (front,rest) = splitAt 3 xs in
   unwords (map show front) ++ 
      if null rest then "" else " | " ++ threes rest

giving

*Main> threes [1..10]
"1 2 3 | 4 5 6 | 7 8 9 | 10"

Functions I used:

splitAt :: Int -> [a] -> ([a], [a])
  -- splitAt 2 "Hello Mum" = ("He","llo Mum")

unwords :: [String] -> String
  -- unwords ["Hello","there","everyone"]
  --        = "Hello there everyone"

null :: [a] -> Bool
null [] = True
null _ = False

Upvotes: 2

Related Questions