Dominik
Dominik

Reputation: 21

Haskell: make int list of lists out of a single string

I've got some data in txt file which looks like this:

  [Just 3, Nothing, Just 1, Nothing] [Nothing, Nothing, Nothing, Nothing] [Nothing, Nothing, Just 4, Nothing] [Nothing, Just 3, Nothing, Nothing]

What I need is to have list of lists containing above values as integers e.g.

[[3,0,1,0],[0,0,0,0],....]

And so on. Do you have any idea how to do it properly? I can replace "nothing" by 0 and drop "Just" part but it will stay as a single string.

Upvotes: 2

Views: 520

Answers (2)

Alexandre Lucchesi
Alexandre Lucchesi

Reputation: 725

Just an alternate version that uses read:

import Data.List.Split (splitOn)
import Data.Maybe      (fromMaybe)

main :: IO ()
main = do
    s <- readFile "lists.txt"
    let xs = map (\s' -> map (fromMaybe 0) (read (s' ++ "]"))) . init . splitOn "]" $ s
    print xs

The idea is simple: first, the file contents are read as a String and bound to s. Then, s is parsed to a list of integers ([Int]) which is bound to xs. In order to do that, the following steps are taken:

  1. splitOn "]": splits the input string s generating a list of strings ([String]). Each element misses the character ']' in the end. The last element of this list is an undesirable "\n". The resulting list is:

    ["[Just 3, Nothing, Just 1, Nothing"," [Nothing, Nothing, Nothing, Nothing", ..., "\n"]
    
  2. init: removes the unwanted "\n".

  3. map (\s' -> map (fromMaybe 0) (read (s' ++ "]"))): applies to each string element the enclosed lambda function, which does:

    1. read (s' ++ "]"): append the missing ']' to the string and parses it to a list of Maybes (Num a => [Maybe a]). Note that we do not need to explicitly specify a type for read because GHC can infer it from the use of fromMaybe 0 (see below).
    2. map (fromMaybe 0): apply the function fromMaybe 0 on each element of the list, which returns v when Just v; and 0, otherwise.

That way you can get a list of integer lists using read.

Upvotes: 0

user2297560
user2297560

Reputation: 2983

The correct way to do this is probably to use a parser library like Text.Parsec. That being said, here's a quick and hacky way to do it not unlike what a python programmer might come up with. The idea is to massage the input string into a form that read will parse for us.

{-# LANGUAGE OverloadedStrings #-}

import Prelude hiding (null)
import Data.Text (snoc, pack, unpack, splitOn, strip, null)
import Data.Maybe (fromMaybe)
import System.IO

parse :: String -> [[Maybe Int]]
parse = map read . map unpack . map (flip snoc ']') . filter (not . null) . map strip . splitOn "]" . pack

main :: IO ()
main = do
  input <- readFile "myfile.txt"  -- input = "[Just 3, Nothing, Just 1, Nothing] [Nothing, Nothing, Nothing, Nothing] [Nothing, Nothing, Just 4, Nothing] [Nothing, Just 3, Nothing, Nothing]"
  putStrLn . show $ map (map (fromMaybe 0)) (parse input)

The parse method works like this:

  • pack converts a String value into a Text value
  • splitOn "]" does exactly what you think
  • map strip removes leading and trailing whitespace
  • filter (not . null) removes empty strings (there's one at the end)
  • map (flip snoc ']') append the "]" character back to the end of each string
  • map unpack converts each Text value back to a String value
  • map read parses each String value into a [Maybe Int]

Upvotes: 1

Related Questions