Ahmed Zaidi
Ahmed Zaidi

Reputation: 15

Implementation of a program in which characters of a string repeated certain times in haskell

This is a question from my homework thus tips would be much likely appreciated.

I am learning Haskell this semester and my first assignment requires me to write a function that inputs 2 string (string1 and string2) and returns a string that is composed of (the repeated) characters of first string string1 until a string of same length as string2 has been created.

I am only allowed to use the Prelude function length.

For example: take as string1 "Key" and my name "Ahmed" as string2 the function should return "KeyKe".

Here is what I've got so far:

    makeString :: Int -> [a] -> [a]
    makeString val (x:xs)
        | val > 0 = x : makeString (val-1) xs
        | otherwise = x:xs

Instead of directly giving it two strings i am giving it an integer value (since i can subtitute it for length later on), but this is giving me a runtime-error:

*Main> makeString 8 "ahmed"

"ahmed*** Exception: FirstScript.hs: (21,1)-(23,21) : Non-exhaustive patterns in function makeString

I think it might have something to do my list running out and becoming an empty list(?).

A little help would be much appreciated.

Upvotes: 0

Views: 252

Answers (3)

Zeta
Zeta

Reputation: 105905

Note: This answer is written in literate Haskell. Save it as Filename.lhs and try it in GHCi.

I think that length is a red herring in this case. You can solve this solely with recursion and pattern matching, which will even work on very long lists. But first things first.

What type should our function have? We're taking two strings, and we will repeat the first string over and over again, which sounds like String -> String -> String. However, this "repeat over and over" thing isn't really unique to strings: you can do that with every kind of list, so we pick the following type:

> repeatFirst :: [a] -> [b] -> [a]
> repeatFirst as bs = go as bs

Ok, so far nothing fancy happened, right? We defined repeatFirst in terms of go, which is still missing. In go we want to exchange the items of bs with the corresponding items of as, so we already know a base case, namely what should happen if bs is empty:

>    where go  _     []     = []

What if bs isn't empty? In this case we want to use the right item from as. So we should traverse both at the same time:

>          go (x:xs) (_:ys) = x : go xs ys

We're currently handling the following cases: empty second argument list, and non-empty lists. We still need to handle the empty first argument list:

>          go []     ys     = 

What should happen in this case? Well, we need to start again with as. And indeed, this works:

>                              go as ys

Here's everything again at a single place:

repeatFirst :: [a] -> [b] -> [a]
repeatFirst as bs = go as bs
   where go  _     []     = []
         go (x:xs) (_:ys) = x : go xs ys
         go []     ys     =     go as ys

Note that you could use cycle, zipWith and const instead if you didn't have constraints:

repeatFirst :: [a] -> [b] -> [a]
repeatFirst = zipWith const . cycle

But that's probably for another question.

Upvotes: 0

Damien
Damien

Reputation: 300

Like Carsten said, you should

  • handle the case when the list is empty
  • push the first element at the end of the list when you drop it.
  • return an empty list when n is 0 or lower

For example:

makeString :: Int -> [a] -> [a]
makeString _ [] = []    -- makeString 10 "" should return ""
makeString n (x:xs)
    | n > 0 = x:makeString (n-1) (xs++[x])
    | otherwise = []    -- makeString 0 "key" should return ""

trying this in ghci :

>makeString (length "Ahmed") "Key"
"KeyKe"

Upvotes: 1

Gabriel Ciubotaru
Gabriel Ciubotaru

Reputation: 1092

I think this code is enough to solve your problem:

extend :: String -> String -> String
extend src dst = extend' src src (length dst)
    where
        extend' :: String -> String -> Int -> String
        extend' _ _ 0 = []
        extend' [] src size = extend' src src size
        extend' (x:xs) src size  = x : extend' xs src (size - 1)

The extend' function will cycle the first string until is is consumed then will begin to consume it again.

You can also make it using take and cycle like functions:

repeatString :: String -> String
repeatString x = x ++ repeatString x

firstN :: Int -> String -> String
firstN 0 _ = []
firstN n (x:xs) = x : firstN ( n - 1 ) xs

extend :: String -> String -> String
extend src dst = firstN (length dst) (repeatString src)

or a more generic version

repeatString :: [a] -> [a]
repeatString x = x ++ repeatString x

firstN :: (Num n, Eq n ) => n -> [a] -> [a]
firstN 0 _ = []
firstN n (x:xs) = x : firstN ( n - 1 ) xs

extend :: [a] -> [b] -> [a]
extend _ [] = error "Empty target"
extend [] _ = error "Empty source"
extend src dst = firstN (length dst) (repeatString src)

which is capable of taking any type of lists:

>extend [1,2,3,4] "foo bar"
[1,2,3,4,1,2,3]

Upvotes: 1

Related Questions