Haskell - Printing a list of tuples - list

I have a list of tuples in the form:
[(String, Int)]
How can I print this to display like:
String : Int
String : Int
String : Int
...
I am very new to Haskell so please make it as clear as possible. Thank you!
Update: Here's how the code of my program now looks:
main = do
putStrLn "********* Haskell word frequency counter *********"
putStrLn ""
conts <- readFile "text.txt"
let lowConts = map toLower conts
let counted = countAllWords (lowConts)
let sorted = sortTuples (counted)
let reversed = reverse sorted
putStrLn "Word : Count"
mapM_ (printTuple) reversed
-- Counts all the words.
countAllWords :: String -> [(String, Int)]
countAllWords fileContents = wordsCount (toWords (noPunc fileContents))
-- Splits words and removes linking words.
toWords :: String -> [String]
toWords s = filter (\w -> w `notElem` ["and","the","for"]) (words s)
-- Remove punctuation from text String.
noPunc :: String -> String
noPunc xs = [ x | x <- xs, not (x `elem` ",.?!-:;\"\'") ]
-- Counts, how often each string in the given list appears.
wordsCount :: [String] -> [(String, Int)]
wordsCount xs = map (\xs -> (head xs, length xs)) . group . sort $ xs
-- Sort list in order of occurrences.
sortTuples :: [(String, Int)] -> [(String, Int)]
sortTuples sort = sortBy (comparing snd) sort
printTuple :: Show a => [(String, a)] -> IO ()
printTuple xs = forM_ xs (putStrLn . formatOne)
formatOne :: Show a => (String, a) -> String
formatOne (s,i) = s ++ " : " ++ show i
It returns this error to me:
fileToText.hs:18:28:
Couldn't match type ‘(String, Int)’ with ‘[(String, a0)]’
Expected type: [[(String, a0)]]
Actual type: [(String, Int)]
In the second argument of ‘mapM_’, namely ‘reversed’
In a stmt of a 'do' block: mapM_ (printTuple) reversed
Thanks for any help!

let's start by formatting one item:
formatOne :: Show a => (String, a) -> String
formatOne (s,i) = s ++ " : " ++ show i
now you can use this function (for example) with forM_ from Control.Monad to print it to the screen like this (forM_ because we want to be in the IO-Monad - because we are going to use putStrLn):
Prelude> let test = [("X1",4), ("X2",5)]
Prelude> import Control.Monad (forM_)
Prelude Control.Monad> forM_ test (putStrLn . formatOne)
X1 : 4
X2 : 5
in a file you would use it like this:
import Control.Monad (forM_)
printTuples :: Show a => [(String, a)] -> IO ()
printTuples xs = forM_ xs (putStrLn . formatOne)
formatOne :: Show a => (String, a) -> String
formatOne (s,i) = s ++ " : " ++ show i
compiling file
overall here is a version of your code that will at least compile (cannot test it without the text file ;) )
import Control.Monad (forM_)
import Data.Char (toLower)
import Data.List (sort, sortBy, group)
import Data.Ord (comparing)
main :: IO ()
main = do
putStrLn "********* Haskell word frequency counter *********"
putStrLn ""
conts <- readFile "text.txt"
let lowConts = map toLower conts
let counted = countAllWords lowConts
let sorted = sortTuples counted
let reversed = reverse sorted
putStrLn "Word : Count"
printTuples reversed
-- Counts all the words.
countAllWords :: String -> [(String, Int)]
countAllWords = wordsCount . toWords . noPunc
-- Splits words and removes linking words.
toWords :: String -> [String]
toWords = filter (\w -> w `notElem` ["and","the","for"]) . words
-- Remove punctuation from text String.
noPunc :: String -> String
noPunc xs = [ x | x <- xs, x `notElem` ",.?!-:;\"\'" ]
-- Counts, how often each string in the given list appears.
wordsCount :: [String] -> [(String, Int)]
wordsCount = map (\xs -> (head xs, length xs)) . group . sort
-- Sort list in order of occurrences.
sortTuples :: [(String, Int)] -> [(String, Int)]
sortTuples = sortBy $ comparing snd
-- print one tuple per line separated by " : "
printTuples :: Show a => [(String, a)] -> IO ()
printTuples = mapM_ (putStrLn . formatTuple)
where formatTuple (s,i) = s ++ " : " ++ show i
I also removed the compiler warnings and HLINTed it (but skipped the Control.Arrow stuff - I don't think head &&& length is more readable option here)

Related

Haskell - How do I write a tuple list to a text file

I get text from .txt, process it. I get vowels and their number from the text. I cannot write tuple list [(Char, Int)] to text file. I want to make each line to have a letter and its number, but I can't write it at all.
`
import Data.List
import Char
add :: Eq a => a -> [(a, Int)] -> [(a, Int)]
add x [] = [(x, 1)]
add x ((y, n):rest) = if x == y
then (y, n+1) : rest
else (y, n) : add x rest
count :: Eq a => [a] -> [(a, Int)]
count = sortBy f . foldr add [] where
f (_, x) (_, y) = compare y x
ff x = filter (\x->elem (fst x) "aeyioAEYIO") x
fff x = ff (count x)
main :: IO ()
main = do
src <- readFile "input.txt"
writeFile "output.txt" (operate src)
operate :: [(Char, Int)] -> String
operate = fff
It gives out an error:
*** Term : operate
*** Type : [Char] -> [(Char,Int)]
*** Does not match : [(Char,Int)] -> String
operate type is wrong because fff has type [Char] -> [(Char, Int)]
operate :: [(Char, Int)] -> String
operate = fff
type inferenece suggests [Char] -> [(Char, Int)],
good
but return type still needs to be [(String, Int)] if we want to feed output of this function to formatOne
formatOne :: Show a => (String, a) -> String
formatOne (s, i) = s ++ " : " ++ show i
operate :: String -> [(String, Int)]
operate = map (\(x,y) -> (x:"", y)) . fff
-- (\(x,y) -> (x:"", y)) this lambda turns first element of tuple from Char to String
-- unlines joins elements of list into string while separating elements with \n
formatAll = unlines . map formatOne
main :: IO ()
main = do
src <- readFile "input.txt"
writeFile "output.txt" (formatAll (operate src))

Split list of Strings and extrat elements to a new List

I have a list of Strings like ["1,2,3", "4,5,6", "7,8,9"] and I'm trying to split the Strings in [["1","2","3"], ["4","5","6"], ["7","8","9"]] and then i'm trying to from a new list where a only have like all the second elements (from example) from the lists in a new list like [2,5,8], and then calculate the average of this elements.
My last attempt was this:
import System.IO
import Data.List
removeComa :: [String] -> [String]
removeComa [] = []
removeComa (x:xs) = splitOn "," x : removeComa xs --ERROR HERE
contentsFromCol :: [String] -> Int -> [Int]
contentsFromCol [] _ = []
contentsFromCol (x:xs) c = read x !! c ++ contentsFromCol xs c
average' :: [String] -> Int -> IO()
average' content col = do
let contents = removeComa content
let contents' = contentsFromCol contents col
realToFrac (sum contents') / genericLength contents'
but I keep getting Variable not in scope: splitOn :: [Char] -> String -> String and i don't understand why?
but I keep getting Variable not in scope: splitOn :: [Char] -> String -> String and i don't understand why?
Because there is no splitOn function in the Prelude, in fact there is not a splitOn in the base package.
You need to install the split package, for example with cabal install split, or by adding split to the dependencies of the stack project, and then import it from Data.List.Split:
import System.IO
import Data.List
import Data.List.Split(splitOn)
removeComa :: [String] -> [[String]]
removeComa = map (splitOn &quot,")
This will also return a list of list of strings, so [[String]]. In your contentsFromCol, you should use !! c on the row, not on the result of read x:
contentsFromCol :: [[String]] -> Int -> [Int]
contentsFromCol [] _ = []
contentsFromCol (x:xs) c = read (x !! c) : contentsFromCol xs c
Your average' function however does not make much sense: realToFrac … … has not as type IO (), so that will raise an error as well.
You can implement this with:
average' :: Fractional a => [String] -> Int -> a
average' content col = fromIntegral (sum contents') / genericLength contents'
where contents' = contentsFromCol (removeComa content) col

Filter a list of tuples and return the first item based on a condition that applies to the second item

The task is as follows: I have a list of tuples containing country names, and the official language. I have another list with "3 item tuples" of translators in (first name, last name, language) format. I need to get a list of the countries, which official language is NOT spoken by any of the translators.
It has to be one call to a function of such form:
foo :: [(String, String)] -> [(String, String, String)] -> [String]
I have tried all sorts of map and filter combinations to no avail. The closest I came was using sets.
third :: (a, b, c) -> c
third (_, _, z) = z
translators x = Set.fromList (map third x)
languages x = Set.fromList (map snd x)
diff x y = Set.toList (Set.difference (languages x) (translators y))
This gives me all the languages that are in the first list but not spoken by the translators. However of course, the task is to give a list of the country names, not the languages. So I tried this as well, but it does not work:
foo x y = filter ((Set.notMember (translators y)).snd) x
I'm a beginner and I would much appreciate some help.
Your first function gives you the correct answer - except it gives you the list of languages. How about an assoc list so you can lookup country associated with the language?
To do that, you just have to swap the country and language in the (Country, Language) tuple
import Data.Tuple ( swap )
langMap :: [(String, String)] -> [(String, String)]
langMap = map swap
Now once you have the result of your own diff function, you just have to map a lookup function over the resulting list
diff :: [(String, String)] -> [(String, String, String)] -> [String]
diff x y = map (fromJust . (`lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
If you are concerned about performance, you can replace the assoc list by a Data.Map
import qualified Data.Map as Map
langMap :: [(String, String)] -> Map.Map String String
langMap = Map.fromList . map swap
And also change the lookup in diff to Map.lookup.
diff :: [(String, String)] -> [(String, String, String)] -> [String]
diff x y = map (fromJust . (`Map.lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
However, there's one caveat here - the lookup method will only work if you have only one country associated with each language.
If you want to support the situation when there are multiple countries associated with the same language. You need some extra batteries on the langMap-
langMap :: [(String, String)] -> Map.Map String [String]
langMap x = Map.fromListWith (++) [(v, [k]) | (k, v) <- x]
This is a slight modification of this answer. It creates a map of String and [String]. The key string is the language, and the value is a list of countries
Now, since the value of the map is a list of strings instead of just a string - we also need to change the diff a bit
diff :: [(String, String)] -> [(String, String)] -> [String]
diff x y = concatMap (fromJust . (`Map.lookup` langMap')) diffList
where
diffList = Set.toList $ Set.difference (languages x) (translators y)
langMap' = langMap x
Huh, that was simple. Just changing map to concatMap is enough - it'll flatten the list of list of strings, into just a list of strings.
Let's see it in action-
λ> xs = [("US", "English"), ("Mexico", "Spanish"), ("France", "French"), ("Spain", "Spanish"), ("UK", "English"), ("Italy", "Italian")]
λ> ys = [("Foo", "Spanish"), ("Bar", "French")]
λ> diff xs ys
["UK","US","Italy"]
Let's focus on correctness, on solving the problem, not on the solution being an efficient one. That we can take care of later. Correctness first, efficiency later!
Simplest and most visual way to code this is using List Comprehensions.
foo :: [(String, String)] -> [(String, String, String)] -> [String]
-- Country Language FirstN LastN Language
-- return: list of Contries with no translator
foo cls nnls = [ c | (c, ___) <- cls, -- for each country/language pair,
-- test the {list of ()s for each translator that ...}
___ [ () | (_, _, ___) <- nnls, ___ == ___] ]
Fill in the ___ blanks (you don't have to fill the _ blanks, those are Haskell's anonymous, throwaway variables).
Now you can make it efficient, treating the above as executable specification.

Breaking up a list into sublists with recursion

I'm trying to write a function with the type declaration [(Int, Bool)] -> [[Int]]. I want the function to only add Ints to the same nested sublist if the Boolean is True. However if the Boolean is False, I want the Int associated with the next True bool to be added to a new sublist. For example: An input of
[(1,True),(2,True),(3,False),(4,True),(5,False),(6,False),(7,True)]
should return
[[1,2],[4],[7]].
My code so far:
test:: [(Int, Bool)] -> [[Int]]
test xs = case xs of
[]->[]
x:xs
| snd x == True -> [(fst x)] : test xs
| snd x == False -> test xs
I'm currently having issues on adding concurrent Ints to the same list if their bools are both True.
You can break this problem into two sub-problems.
For any given list, take the head of this list and match it against the rest of list. There are two possibilities during this matching: i) You are successful i.e. you match, and if so, you collect the matched value and continue looking for more values, or ii) You fail, i.e. you don't match, and if so, you stop immediately and return the so far matched result with rest of, not-inspected, list.
collectF :: (Eq a) => (a -> Bool) -> [a] -> ([a], [a])
collectF f [] = ([], [])
collectF f (x : xs)
| f x = let (ys, zs) = collectF f xs in (x : ys, zs)
| otherwise = ([], x : xs)
Now that you have the collectF function, you can use it recursively on input list. In each call, you would get a successful list with rest of, not-inspected, list. Apply collectF again on rest of list until it is exhausted.
groupBy :: (Eq a) => (a -> a -> Bool) -> [a] -> [[a]]
groupBy _ [] = []
groupBy f (x : xs) =
let (ys, zs) = collectF (f x) xs in
(x : ys) : groupBy f zs
*Main> groupBy (\x y -> snd x == snd y) [(1,True),(2,True),(3,False),(4,True),(5,False),(6,False),(7,True)]
[[(1,True),(2,True)],[(3,False)],[(4,True)],[(5,False),(6,False)],[(7,True)]]
I am leaving it to you to remove the True and False values from List. Also, have a look at List library of Haskell [1]. Hope, I am clear enough, but let me know if you have any other question.
[1] http://hackage.haskell.org/package/base-4.12.0.0/docs/src/Data.OldList.html#groupBy
Repeatedly, drop the Falses, grab the Trues. With view patterns:
{-# LANGUAGE ViewPatterns #-}
test :: [(a, Bool)] -> [[a]]
test (span snd . dropWhile (not . snd) -> (a,b))
| null a = []
| otherwise = map fst a : test b
Works with infinite lists as well, inasmuch as possible.
Here's how I'd write this:
import Data.List.NonEmpty (NonEmpty(..), (<|))
import qualified Data.List.NonEmpty as NE
test :: [(Int, Bool)] -> [[Int]]
test = NE.filter (not . null) . foldr go ([]:|[])
where
go :: (Int, Bool) -> NonEmpty [Int] -> NonEmpty [Int]
go (n, True) ~(h:|t) = (n:h):|t
go (n, False) l = []<|l
Or with Will Ness's suggestion:
import Data.List.NonEmpty (NonEmpty(..))
test :: [(Int, Bool)] -> [[Int]]
test = removeHeadIfEmpty . foldr prependOrStartNewList ([]:|[])
where
prependOrStartNewList :: (Int, Bool) -> NonEmpty [Int] -> NonEmpty [Int]
prependOrStartNewList (n, True) ~(h:|t) = (n:h):|t
prependOrStartNewList (n, False) l = []:|removeHeadIfEmpty l
removeHeadIfEmpty :: NonEmpty [Int] -> [[Int]]
removeHeadIfEmpty (h:|t) = if null h then t else h:t

Ordering list of String trough other list of String

I have two lists ["a","b","c","d"] and ["b","d","a","c"]
How can I make a function that orders the first list with the same order of the second one?
In this example something like this:
> ord ["a","b","c","d"] ["b","d","a","c"]
["b","d","a","c"]
all the function I make give me an incomplete list:
ord :: [String] -> [String] -> [String]
ord [] _ = []
ord (h:t) (x:xs) | (h==x) = h:(ord t xs)
| otherwise = ord t (x:xs)
This is only an example; I can't simply present the second list.
Here's a quick and dirty solution that builds the result by grouping each string in the first list by the order in the second (I also renamed ord to orderThese):
orderThese :: [String] -> [String] -> [String]
orderThese _ [] = []
orderThese as (b:bs) = filter (\x -> x == b) as ++ orderThese as bs
As an example, orderThese ["a", "c", "a", "b"] ["b", "a", "c"] returns ["b","a","a","c"].
I think this is what you want:
import Data.Function (on)
import Data.List (elemIndex, sortBy)
ord :: Eq a => [a] -> [a] -> [a]
ord listToSort desiredOrder = sortBy (compare `on` (`elemIndex` desiredOrder)) listToSort
I would suggest that
you give the function a different name, as there is a function called ord in Data.Char; and
you swap the order of the parameters, as it seems more likely that you would want to partially apply the function with a desiredOrder than with a listToSort.