문자열의 각 글자를 교대로 뒤섞는다.
출처 : https://www.hackerrank.com/challenges/string-mingling/problem
두 줄의 문자열을 입력받아서 글자가 교대로 나타나도록 두 문자열을 하나로 합성한다.
예: hello world -> hweolrllod
문자열을 합성하는 것은 간단하다. 문자열은 [Char]
타입의 별칭이므로 zipWith
를 이용해서 간단히 합성할 수 있다.
mingle :: String -> String -> String
mingle s1 s2 = concat . zipWith (\x y -> [x,y]) s1 $ s2
그리고 main
함수는 다음과 같이 쓰면 된다.
main :: IO ()
main = do
s1 <- getLine
s2 <- getLine
putStrLn $ mingle s1 s2
그런데 이전의 문제에서처럼 do 블럭을 사용하지 않고 getContents >>= ...
를 쓰는 것 처럼 한줄로 이 함수를 표현할 방법이 없을까? 방법을 찾기 위해서 일단 관련되는 각 함수의 타입을 보자. 타입을 살펴보는 것만으로 충분히 많은 힌트를 얻을 수 있다.
먼저 getLine
의 타입은 IO String
이고, mingle
은 String -> String -> String
이다. 따라서 mingle을 getLine
에 맵핑하면 IO (String -> String)
타입의 적용성 펑터가 하나 나오게 된다. (IO 액션은 모나드인 동시에 적용성 펑터이기도 하다)
mingle <$> getLine :: IO (String -> String)
적용성 펑터는 <*>
를 통해서 다른 적용성 펑터에 적용될 수 있다. (f (a -> b) -> f a -> f b
) 따라서 mingle <$> getLine <*> getLine
하면 IO String
이 된다.
다시 >>=
는 m a -> (a -> m b) -> m b
가 되므로 보통 IO String을 String -> IO ()로 만드는 일에 쓸 수 있다. 즉, putStrLn
에 붙일 수 있다.
따라서 다음과 같이 쓰면
main = mingle <$> getLine <*> getLine >>= putStrLn
뭔가 완전 말도 안되는 것 같지만 우변은 완벽하게 IO ()
가 되고 실제로도 잘 동작한다!!!!!
응용 문제
https://www.hackerrank.com/challenges/string-o-permute/problem
n을 입력받은 후 n회 입력되는 문자열에 대해서 이웃한 두 문자의 순서를 서로 바꾼다.
import Control.Monad
odd_indexed_chars = map snd . filter (odd . fst) . zip [1..]
even_indexed_chars = map snd . filter (even . fst) . zip [1..]
process cs = let l = odd_indexed_chars cs
r = even_indexed_chars cs
in concat . zipWith (\x y -> [y,x]) l r
main = do
n <- readLn :: IO Int
forM_ [1..n] $ \_ -> getLine >>= process