Tuple initialization from IO data in Haskell - haskell

I would like to know what is the best way to get a tuple from data read from the input in Haskell. I often encounter this problem in competitive programming when the input is made up of several lines that contain space-separated integers. Here is an example:
1 3 10
2 5 8
10 11 0
0 0 0
To read lines of integers, I use the following function:
readInts :: IO [Int]
readInts = fmap (map read . words) getLine
Then, I transform these lists into tuples with of the appropriate size:
readInts :: IO (Int, Int, Int, Int)
readInts = fmap ((\l -> (l !! 0, l !! 1, l !! 2, l !! 3)) . map read . words) getLine
This approach does not seem very idiomatic to me.
The following syntax is more readable but it only works for 2-tuples:
readInts :: IO (Int, Int)
readInts = fmap ((\[x, y] -> (x, y)) . map read . words) getLine
(EDIT: as noted in the comments, the solution above works for n-tuples in general).
Is there an idiomatic way to initialize tuples from lists of integers without having to use !! in Haskell? Alternatively, is there a different approach to processing this type of input?

How about this:
readInts :: IO (<any tuple you like>)
readInts = read . ("(" ++) . (++ ")") . intercalate "," . words <$> getLine

Given that the context is 'competitive programming' (something I'm only dimly aware of as a concept), I'm not sure that the following offers a particularly competitive alternative, but IMHO I'd consider it idiomatic to use one of several available parser combinators.
The base package comes with a module called Text.ParserCombinators.ReadP. Here's how you could use it to parse the input file from the linked article:
module Q57693986 where
import Text.ParserCombinators.ReadP
parseNumber :: ReadP Integer
parseNumber = read <$> munch1 (`elem` ['0'..'9'])
parseTriple :: ReadP (Integer, Integer, Integer)
parseTriple =
(,,) <$> parseNumber <*> (char ' ' *> parseNumber) <*> (char ' ' *> parseNumber)
parseLine :: ReadS (Integer, Integer, Integer)
parseLine = readP_to_S (parseTriple <* eof)
parseInput :: String -> [(Integer, Integer, Integer)]
parseInput = concatMap (fmap fst . filter (null . snd)) . fmap parseLine . lines
You can use the parseInput against this input file:
1 3 10
2 5 8
10 11 0
0 0 0
Here's a GHCi session that parses that file:
*Q57693986> parseInput <$> readFile "57693986.txt"
[(1,3,10),(2,5,8),(10,11,0),(0,0,0)]
Each parseLine function produces a list of tuples that match the parser; e.g.:
*Q57693986> parseLine "11 32 923"
[((11,32,923),"")]
The second element of the tuple is any remaining String still waiting to be parsed. In the above example, parseLine has completely consumed the line, which is what I'd expect for well-formed input, so the remaining String is empty.
The parser returns a list of alternatives if there's more than one way the input could be consumed by the parser, but again, in the above example, there's only one suggested alternative, as the line has been fully consumed.
The parseInput function throws away any tuple that hasn't been fully consumed, and then picks only the first element of any remaining tuples.
This approach has often served me with puzzles such as Advent of Code, where the input files tend to be well-formed.

This is a way to generate a parser that works generically for any tuple (of reasonable size). It requires the library generics-sop.
{-# LANGUAGE DeriveGeneric, DeriveAnyClass,
FlexibleContexts, TypeFamilies, TypeApplications #-}
import GHC.Generics
import Generics.SOP
import Generics.SOP (hsequence, hcpure,Proxy,to,SOP(SOP),NS(Z),IsProductType,All)
import Data.Char
import Text.ParserCombinators.ReadP
import Text.ParserCombinators.ReadPrec
import Text.Read
componentP :: Read a => ReadP a
componentP = munch isSpace *> readPrec_to_P readPrec 1
productP :: (IsProductType a xs, All Read xs) => ReadP a
productP =
let parserOutside = hsequence (hcpure (Proxy #Read) componentP)
in Generics.SOP.to . SOP . Z <$> parserOutside
For example:
*Main> productP #(Int,Int,Int) `readP_to_S` " 1 2 3 "
[((1,2,3)," ")]
It allows components of different types, as long as they all have a Read instance.
It also parses records that have a Generics.SOP.Generic instance:
data Stuff = Stuff { x :: Int, y :: Bool }
deriving (Show,GHC.Generics.Generic,Generics.SOP.Generic)
For example:
*Main> productP #Stuff `readP_to_S` " 1 True"
[(Stuff {x = 1, y = True},"")]

Related

How can I randomly generate a string 5 symbols long in haskell?

I'm a newbie so I'm having problems doing this:P
Currently I have tried this:
xs1 :: RandomGen g => Int -> g -> [[Char]]
xs1 n = sequence $ replicate n $ randomRs ('!', '~' ::Char)
but I can't give this string to my function:
fun1 :: (Eq a, Num a) => [a] -> [Char] -> [Char]
fun1 a xs1=[if (a!!n==1) then (xs1!!n) else b | n <- [0, 1,2,3,4]]
any help would be greatly appreciated
IMO the easiest way for a beginner to use random stuff is randomIO or randomRIO - here is an example printing 5 random characters (in the range '!' to '~'):
import Control.Monad (replicateM)
import System.Random (Random(randomRIO))
randomString :: Int -> IO String
randomString len = replicateM len $ randomRIO ('!', '~')
select :: (Num f, Eq f) => [f] -> String -> String
select =
zipWith (\f c -> if f == 1 then c else ' ')
main :: IO ()
main = do
-- there will be a warning without the `:: Int`
s <- select [1 :: Int,0,1,0,1] <$> randomString 5
putStrLn s
I'm using randomRIO here because using just randomIO will give you random characters from all over the place most likely all unprintable ;)
I don't know what you are trying to do with fun1 but I'll gone edit if you make it clear
How can I randomly generate a string 5 symbols long ?
Due to the use of randomRs, your xs1 function returns a list of infinitely long strings.
Also, as xs1 is a function not a list, you cannot really index it thru the !! operator.
Please please: paste the full text of the error message in your question, rather than just writing:
“I can't give this string to my function:”.
In order to generate random strings, you need two things:
a generator, typically returned by mkStdGen or mkTFGen
a monadic action object with the appropriate return type
Let's try to do that under the ghci Haskell interpreter:
$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
λ>
λ> import System.Random
λ> import Control.Monad.Random
λ>
So let's get a generator first:
λ>
λ> seed = 42
λ> gen1 = mkStdGen seed
λ>
Then let's write the action object:
λ>
λ> act5 = sequence $ replicate 5 (getRandomR ('!', '~' ::Char))
λ>
λ> :type act5
act5 :: MonadRandom m => m [Char]
λ>
The action object is essentially a wrapper around a transition function that takes a generator and returns some (output value, new generator) pair.
Finally, we combine these two things thru the runRand :: Rand g a -> g -> (a, g) library function, which returns both the value you want and an updated generator:
λ>
λ> (str1, gen2) = runRand act5 gen1
λ>
λ> str1
"D6bK/"
λ>
And we can even get a second pseudo-random string, courtesy of the updated generator:
λ>
λ> (str2, gen3) = runRand act5 gen2
λ>
λ> str2
"|}#5h"
λ>
And so on ... As you can see, it is not really necessary to use the IO monad for this.
A slight generalization:
Now, say you need 6 such random strings:
λ>
λ> act5rep = sequence (replicate 6 act5)
λ>
λ> :type act5rep
act5rep :: MonadRandom m => m [[Char]]
λ>
λ> (strs, newGen) = runRand act5rep gen1
λ>
λ> strs
["D6bK/","|}#5h","0|qSQ","h4:.1","3+e-}","}eu,I"]
λ>

What is the Haskell idiom for walking a file and filling a structure when only some of the data is interesting?

Often I find I need to parse a little bit of text. Usually the text is not lines of uniform data like CSV rather it is more unstructured. So the goal is not to turn each line into a Haskell data type but to gather up data into a structure.
In an imperative language I would write something like this.
values = {} # could just as easily be a class or C struct
for line in input_lines:
if line matches A:
parse out interesting piece
values[A] = parsed chunk
elif line matches B:
parse out interesting piece
values[B] = parsed chunk
...
elif line matches Z:
parse out interesting piece
values[Z] = parsed chunk
break # we know there is nothing else after this
do something with values
I wrote a bit of Haskell this morning to do the same thing using foldr.
This parses the output of rsync --stats. A sample file looks like this.
Number of files: 1
Number of files transferred: 0
Total file size: 4953701 bytes
Total transferred file size: 0 bytes
Literal data: 10 bytes
Matched data: 230 bytes
File list size: 43
File list generation time: 0.001 seconds
File list transfer time: 0.000 seconds
Total bytes sent: 11
Total bytes received: 57
sent 11 bytes received 57 bytes 12.36 bytes/sec
total size is 4953701 speedup is 72848.54
Small and simple to demonstrate my problem. This particular file format is representative of this recurring style of problem where I want to quickly read 3 or 5 bits from a file and doing something else with the results. In an imperative language I'd just toss them into a few variables, a dictionary, something. The Haskell below is my attempt at a similar approach.
{-# LANGUAGE OverloadedStrings #-}
import qualified Data.Map as M
import qualified Data.Text as T
import Data.Text (Text)
import qualified Data.Text.IO as TIO
import Data.Text.Read (decimal)
import System.Environment (getArgs)
stats_map :: M.Map Text Int
stats_map = foldr (uncurry M.insert) M.empty [("Total file size", 1),
("Literal data", 2),
("Matched data", 3)]
getStatsMap :: Text -> M.Map Text Integer -> M.Map Text Integer
getStatsMap t rm = doMatch chunks rm
where
chunks = [ T.strip chunk | chunk <- T.splitOn ":" t ]
doMatch :: [Text] -> M.Map Text Integer -> M.Map Text Integer
doMatch (f1:f2:_) rm' =
case M.lookup f1 stats_map of
(Just _) -> case decimal . head . T.words $ f2 of
Left _ -> rm'
Right (x,_) -> M.insert f1 x rm'
Nothing -> rm'
doMatch _ rm' = rm'
parseStats :: [Text] -> M.Map Text Integer
parseStats ts = foldr getStatsMap M.empty ts
readStats :: FilePath -> IO [Text]
readStats filename = TIO.readFile filename >>= return . T.lines
main :: IO ()
main = do
[filename] <- getArgs
lines <- readStats filename
putStrLn . show . parseStats $ lines
Unlike in the imperative version I cannot break the foldr execution though.
Laziness cannot rescue me here. Parsec, attoparsec and friends are both overkill and not exactly what I am looking for this kind of task.
How can I approach this common imperative task in a more Haskell way?
I've gone for simple data structures to try to emphasise that the behaviour's there in the standard ones if you want it:
First version - using catMaybes and take to ignore irrelevant data and shortcut:
import Data.Maybe (catMaybes)
import Data.Char (isDigit)
import Control.Monad (msum)
-- maybe get an int if the key matches before :
get :: String -> String -> Maybe Int
get key input = let (l,r) = break (==':') input in
if l == key then Just . read . filter isDigit $ r
else Nothing
-- get any that match
getAny :: [String] -> String -> Maybe Int
getAny keys input = msum $ map (flip get input) keys
-- get all that match at least one
getThese :: [String] -> String -> [Int]
getThese keys = take (length keys) . catMaybes . map (getAny keys) . lines
This gives you the output you were after:
fmap (getThese ["Total file size","Literal data","Matched data"]) (readFile "example.txt") >>= print
[4953701,10,230]
and we can check that it's shortcutting by feeding it a bomb to eat:
> getThese ["a"] (unlines ["no","a: 5",undefined])
[5]
Sometimes recursion is simpler
Pick out one element for each predicate in order:
oneEach :: [(a->Bool)] -> [a] -> [a]
oneEach [] _ = []
oneEach _ [] = error "oneEach: run out of input while still looking"
oneEach qs#(p:ps) (i:is) | p i = i : oneEach ps is
| otherwise = oneEach qs is
Compose some functions to split the string and pull out the ones we wanted, then read the data. This assumes you want all the digits to the right of the : as your Int
getInOrder :: [String] -> String -> [Int]
getInOrder keys = map (read.filter isDigit.snd)
. oneEach (map ((.fst).(==)) keys)
. map (break (==':'))
. lines
which works:
main = fmap (getInOrder ["Total file size","Literal data","Matched data"]) (readFile "example.txt") >>= print
[4953701,10,230]
This version is primitive in some ways (hard codes some things, doesn't handle ordering), but may be more readable:
import System.Environment (getArgs)
import Data.List.Utils
import Data.Char
main = do
[filename] <- getArgs
txt <- readFile filename
let ls = lines txt
let ils = filter interestingLine ls
putStrLn $ show $ map fmt (filter (/="") ils)
interestingLine l = startswith "Literal data" l
|| startswith "Matched data" l
|| startswith "Total file size" l
fmt :: String -> (String,Int)
fmt l | startswith "Literal data" l = (take 14 l,(read $ filter isNumber l))
| startswith "Matched data" l = (take 14 l,(read $ filter isNumber l))
| startswith "Total file size" l = (take 17 l,(read $ filter isNumber l))
| otherwise = error "fmt: unmatched line, look also at interestingLine"

Non-exhaustive patterns in lambda

I am getting Non-exhaustive patterns in lambda. I am not sure of the cause yet. Please anyone how to fix it. The code is below:
import Control.Monad
import Data.List
time_spent h1 h2 = max (abs (fst h1 - fst h2)) (abs (snd h1 - snd h2))
meeting_point xs = foldl' (find_min_time) maxBound xs
where
time_to_point p = foldl' (\tacc p' -> tacc + (time_spent p p')) 0 xs
find_min_time min_time p = let x = time_to_point p in if x < min_time then x else min_time
main = do
n <- readLn :: IO Int
points <- fmap (map (\[x,y] -> (x,y)) . map (map (read :: String->Int)) . map words . lines) getContents
putStrLn $ show $ meeting_point points
This is the lambda with the non-exhaustive patterns: \[x,y] -> (x,y).
The non-exhaustive pattern is because the argument you've specified, [x,y] doesn't match any possible list - it only matches lists with precisely two elements.
I would suggest replacing it with a separate function with an error case to print out the unexpected data in an error message so you can debug further, e.g.:
f [x,y] = (x, y)
f l = error $ "Unexpected list: " ++ show l
...
points <- fmap (map f . map ...)
As an addition to #GaneshSittampalam's answer, you could also do this with more graceful error handling using the Maybe monad, the mapM function from Control.Monad, and readMaybe from Text.Read. I would also recommend refactoring your code so that the parsing is its own function, it makes your main function much cleaner and easier to debug.
import Control.Monad (mapM)
import Text.Read (readMaybe)
toPoint :: [a] -> Maybe (a, a)
toPoint [x, y] = Just (x, y)
toPoint _ = Nothing
This is just a simple pattern matching function that returns Nothing if it gets a list with length not 2. Otherwise it turns it into a 2-tuple and wraps it in Just.
parseData :: String -> Maybe [(Int, Int)]
parseData text = do
-- returns Nothing if a non-Int is encountered
values <- mapM (mapM readMaybe . words) . lines $ text
-- returns Nothing if a line doesn't have exactly 2 values
mapM toPoint values
Your parsing can actually be simplified significantly by using mapM and readMaybe. The type of readMaybe is Read a => String -> Maybe a, and in this case since we've specified the type of parseData to return Maybe [(Int, Int)], the compiler can infer that readMaybe should have the local type of String -> Maybe Int. We still use lines and words in the same way, but now since we use mapM the type of the right hand side of the <- is Maybe [[Int]], so the type of values is [[Int]]. What mapM also does for us is if any of those actions fails, the overall computation exits early with Nothing. Then we simply use mapM toPoint to convert values into a list of points, but also with the failure mechanism built in. We actually could use the more general signature of parseData :: Read a => String -> Maybe [(a, a)], but it isn't necessary.
main = do
n <- readLn :: IO Int
points <- fmap parseData getContents
case points of
Just ps -> print $ meeting_point ps
Nothing -> putStrLn "Invalid data!"
Now we just use fmap parseData on getContents, making points have the type Maybe [(Int, Int)]. Finally, we pattern match on points to print out the result of the meeting_point computation or print a helpful message if something went wrong.
If you wanted even better error handling, you could leverage the Either monad in a similar fashion:
toPoint :: [a] -> Either String (a, a)
toPoint [x, y] = Right (x, y)
toPoint _ = Left "Invalid number of points"
readEither :: Read a => String -> Either String a
readEither text = maybe (Left $ "Invalid parse: " ++ text) Right $ readMaybe text
-- default value ^ Wraps output on success ^
-- Same definition with different type signature and `readEither`
parseData :: String -> Either String [(Int, Int)]
parseData text = do
values <- mapM (mapM readEither . words) . lines $ text
mapM toPoint values
main = do
points <- fmap parseData getContents
case points of
Right ps -> print $ meeting_point ps
Left err -> putStrLn $ "Error: " ++ err

How do I parse a matrix of integers in Haskell?

So I've read the theory, now trying to parse a file in Haskell - but am not getting anywhere. This is just so weird...
Here is how my input file looks:
m n
k1, k2...
a11, ...., an
a21,.... a22
...
am1... amn
Where m,n are just intergers, K = [k1, k2...] is a list of integers, and a11..amn is a "matrix" (a list of lists): A=[[a11,...a1n], ... [am1... amn]]
Here is my quick python version:
def parse(filename):
"""
Input of the form:
m n
k1, k2...
a11, ...., an
a21,.... a22
...
am1... amn
"""
f = open(filename)
(m,n) = f.readline().split()
m = int(m)
n = int(n)
K = [int(k) for k in f.readline().split()]
# Matrix - list of lists
A = []
for i in range(m):
row = [float(el) for el in f.readline().split()]
A.append(row)
return (m, n, K, A)
And here is how (not very) far I got in Haskell:
import System.Environment
import Data.List
main = do
(fname:_) <- getArgs
putStrLn fname --since putStrLn goes to IO ()monad we can't just apply it
parsed <- parse fname
putStrLn parsed
parse fname = do
contents <- readFile fname
-- ,,,missing stuff... ??? how can I get first "element" and match on it?
return contents
I am getting confused by monads (and the context that the trap me into!), and the do statement. I really want to write something like this, but I know it's wrong:
firstLine <- contents.head
(m,n) <- map read (words firstLine)
because contents is not a list - but a monad.
Any help on the next step would be great.
So I've just discovered that you can do:
liftM lines . readFile
to get a list of lines from a file. However, still the example only only transforms the ENTIRE file, and doesn't use just the first, or the second lines...
The very simple version could be:
import Control.Monad (liftM)
-- this operates purely on list of strings
-- and also will fail horribly when passed something that doesn't
-- match the pattern
parse_lines :: [String] -> (Int, Int, [Int], [[Int]])
parse_lines (mn_line : ks_line : matrix_lines) = (m, n, ks, matrix)
where [m, n] = read_ints mn_line
ks = read_ints ks_line
matrix = parse_matrix matrix_lines
-- this here is to loop through remaining lines to form a matrix
parse_matrix :: [String] -> [[Int]]
parse_matrix lines = parse_matrix' lines []
where parse_matrix' [] acc = reverse acc
parse_matrix' (l : ls) acc = parse_matrix' ls $ (read_ints l) : acc
-- this here is to give proper signature for read
read_ints :: String -> [Int]
read_ints = map read . words
-- this reads the file contents and lifts the result into IO
parse_file :: FilePath -> IO (Int, Int, [Int], [[Int]])
parse_file filename = do
file_lines <- (liftM lines . readFile) filename
return $ parse_lines file_lines
You might want to look into Parsec for fancier parsing, with better error handling.
*Main Control.Monad> parse_file "test.txt"
(3,3,[1,2,3],[[1,2,3],[4,5,6],[7,8,9]])
An easy to write solution
import Control.Monad (replicateM)
-- Read space seperated words on a line from stdin
readMany :: Read a => IO [a]
readMany = fmap (map read . words) getLine
parse :: IO (Int, Int, [Int], [[Int]])
parse = do
[m, n] <- readMany
ks <- readMany
xss <- replicateM m readMany
return (m, n, ks, xss)
Let's try it:
*Main> parse
2 2
123 321
1 2
3 4
(2,2,[123,321],[[1,2],[3,4]])
While the code I presented is quite expressive. That is, you get work done quickly with little code, it has some bad properties. Though I think if you are still learning haskell and haven't started with parser libraries. This is the way to go.
Two bad properties of my solution:
All code is in IO, nothing is testable in isolation
The error handling is very bad, as you see the pattern matching is very aggressive in [m, n]. What happens if we have 3 elements on the first line of the input file?
liftM is not magic! You would think it does some arcane thing to lift a function f into a monad but it is actually just defined as:
liftM f x = do
y <- x
return (f y)
We could actually use liftM to do what you wanted to, that is:
[m,n] <- liftM (map read . words . head . lines) (readFile fname)
but what you are looking for are let statements:
parseLine = map read . words
parse fname = do
(x:y:xs) <- liftM lines (readFile fname)
let [m,n] = parseLine x
let ks = parseLine y
let matrix = map parseLine xs
return (m,n,ks,matrix)
As you can see we can use let to mean variable assignment rather then monadic computation. In fact let statements are you just let expressions when we desugar the do notation:
parse fname =
liftM lines (readFile fname) >>= (\(x:y:xs) ->
let [m,n] = parseLine x
ks = parseLine y
matrix = map parseLine xs
in return matrix )
A Solution Using a Parsing Library
Since you'll probably have a number of people responding with code that parses strings of Ints into [[Int]] (map (map read . words) . lines $ contents), I'll skip that and introduce one of the parsing libraries. If you were to do this task for real work you'd probably use such a library that parses ByteString (instead of String, which means your IO reads everything into a linked list of individual characters).
import System.Environment
import Control.Monad
import Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString as B
First, I imported the Attoparsec and bytestring libraries. You can see these libraries and their documentation on hackage and install them using the cabal tool.
main = do
(fname:_) <- getArgs
putStrLn fname
parsed <- parseX fname
print parsed
main is basically unchanged.
parseX :: FilePath -> IO (Int, Int, [Int], [[Int]])
parseX fname = do
bs <- B.readFile fname
let res = parseOnly parseDrozzy bs
-- We spew the error messages right here
either (error . show) return res
parseX (renamed from parse to avoid name collision) uses the bytestring library's readfile, which reads in the file packed, in contiguous bytes, instead of into cells of a linked list. After parsing I use a little shorthand to return the result if the parser returned Right result or print an error if the parser returned a value of Left someErrorMessage.
-- Helper functions, more basic than you might think, but lets ignore it
sint = skipSpace >> int
int = liftM floor number
parseDrozzy :: Parser (Int, Int, [Int], [[Int]])
parseDrozzy = do
m <- sint
n <- sint
skipSpace
ks <- manyTill sint endOfLine
arr <- count m (count n sint)
return (m,n,ks,arr)
The real work then happens in parseDrozzy. We get our m and n Int values using the above helper. In most Haskell parsing libraries we must explicitly handle whitespace - so I skip the newline after n to get to our ks. ks is just all the int values before the next newline. Now we can actually use the previously specified number of rows and columns to get our array.
Technically speaking, that final bit arr <- count m (count n sint) doesn't follow your format. It will grab n ints even if it means going to the next line. We could copy Python's behavior (not verifying the number of values in a row) using count m (manyTill sint endOfLine) or we could check for each end of line more explicitly and return an error if we are short on elements.
From Lists to a Matrix
Lists of lists are not 2 dimensional arrays - the space and performance characteristics are completely different. Let's pack our list into a real matrix using Data.Array.Repa (import Data.Array.Repa). This will allow us to access the elements of the array efficiently as well as perform operations on the entire matrix, optionally spreading the work among all the available CPUs.
Repa defines the dimensions of your array using a slightly odd syntax. If your row and column lengths are in variables m and n then Z :. n :. m is much like the C declaration int arr[m][n]. For the one dimensional example, ks, we have:
fromList (Z :. (length ks)) ks
Which changes our type from [Int] to Array DIM1 Int.
For the two dimensional array we have:
let matrix = fromList (Z :. m :. n) (concat arr)
And change our type from [[Int]] to Array DIM2 Int.
So there you have it. A parsing of your file format into an efficient Haskell data structure using production-oriented libraries.
What about something simple like this?
parse :: String -> (Int, Int, [Int], [[Int]])
parse stuff = (m, n, ks, xss)
where (line1:line2:rest) = lines stuff
readMany = map read . words
(m:n:_) = readMany line1
ks = readMany line2
xss = take m $ map (take n . readMany) rest
main :: IO ()
main = do
stuff <- getContents
let (m, n, ks, xss) = parse stuff
print m
print n
print ks
print xss

Pretty print ByteString to hex nibble-wise

What's an idiomatic way of treating a bytestring nibblewise and pretty printing its hexadecimal (0-F) representation?
putStrLn . show . B.unpack
-- [1,126]
Which, upon further work
putStrLn . show . map (\x -> N.showIntAtBase 16 (DC.intToDigit) x "") . B.unpack
["1","7e"]
But what I really want is
["1","7","e"]
Or better yet
['1','7','e']
I could munge up ["1","7e"] but that string manipulation whereas I'd rather do numeric manipulation. Do I need to drop down to shifting and masking numeric values?
You can now use Data.ByteString.Builder. To print a ByteString to its hex equivalent (with two hex digits per byte, in the right order, and efficiently), simply use:
toLazyByteString . byteStringHex
or
toLazyByteString . lazyByteStringHex
depending on which flavor of ByteString you have as input.
I'd like to elaborate on max taldykin's answer (that I have upvoted), which I think is over-complicated. There is no need for NoMonomorphismRestriction, printf or Data.List.
Here is my version:
import qualified Data.ByteString as B
import Numeric (showHex)
prettyPrint :: B.ByteString -> String
prettyPrint = concat . map (flip showHex "") . B.unpack
main :: IO ()
main = putStrLn . prettyPrint . B.pack $ [102, 117, 110]
Somethig like this:
{-# LANGUAGE NoMonomorphismRestriction #-}
import qualified Data.ByteString as B
import Text.Printf
import Data.List
import Numeric
hex = foldr showHex "" . B.unpack
list = printf "[%s]" . concat . intersperse "," . map show
Test:
> let x = B.pack [102,117,110]
> list . hex $ x
"['6','6','7','5','6','e']"
Upd Oh, there is a stupid memory leak: of course you should replace foldr with foldl' (because laziness is not required here):
hex = foldl' (flip showHex) "" . B.unpack
You have ["1","7e"] :: [String]
concat ["1", "7e"] is "17e" :: String which is equal to [Char] and equal to ['1','7','e'] :: [Char].
Than you may split that String into pieces:
> Data.List.Split.splitEvery 1 . concat $ ["1", "7e"]
["1","7","e"]
it :: [[Char]]
If you just want a regular hex en/decoding of ByteStrings, you can use the memory package. They call the hex encoding Base16.
>>> let input = "Is 3 > 2?" :: ByteString
>>> let convertedTo base = convertToBase base input :: ByteString
>>> convertedTo Base16
"49732033203e20323f"
Full documentation: https://hackage.haskell.org/package/memory-0.18.0/docs/Data-ByteArray-Encoding.html#t:Base

Resources