(연습문제) 행렬의 대각선의 차

대각선의 차이 구하기

N*N 크기의 행렬이 주어졌을 때, 이 행렬의 대각선상에 위치하는 요소들의 합끼리의 차를 구하는 프로그램을 작성하는 문제이다.

https://www.hackerrank.com/challenges/diagonal-difference

i 번째 행의 (i-1)번, 및 (N-i)번 인덱스의 값을 각각 뺀 다음 (i=1,2,3… 인덱스는 0, 1, 2 순) 이들을 합산하고 절대값을 취하면 된다.

module Main where

import Control.Monad
import Control.Applicative

-- i 와 i번 행을 받아 이를 처리한다. 
processLine :: Int -> [Int] -> Int
process i xs = let a = head $ drop (i-1) xs
                   b = head . drop (i-1) . reverse $ xs
                in a - b

main = do
  n <- readLn :: IO Int
  ts <- sum <$> forM [1..n] (\x -> processLine x . map read . words <$> getLine)
  print . abs $ ts

여기서도 <$> 연산자를 유용하게 썼고, forM 함수도 썼다.

forM, <$>

forM 함수는 (Monad m, Traversable t) => t a -> (a -> m b) -> m (t b)의 타입을 가진 함수로 리스트나 트리와 같은 순회 가능한 목록과 모나딕 값을 리턴하는 함수를 이용해서 리스트의 각 원소를 처리하고, 이를 모나딕 리스트나 모나딕 트리로 리턴하는 함수이다.

여기서 두 번째 인자로 쓰인 함수를 살펴보면

(\x -> processLine x . map read . words <$> getLine)

이게 나오는데 <$>는 좌변의 함수(a -> b)를 우변의 t a에 적용하여 t b 타입을 만들어주는 (여기서 t 는 applicative) 연산자이다. 즉 좌변의 함수를 Applicative 문맥으로 감싸고 이를 다시 Applicative 문맥에 들어있는 값에 적용하게 해준다. (인자가 2개 이상이면 <*> 연산자를 이용해서 언커리할 수 있다.)

여기서 왼쪽의 함수는 String -> Int가 되고, 우변의 getLineIO String이므로 이 함수 전체의 타입은 Int -> IO Int가 된다.

결국 forM 함수를 이용해서 처리한 결과는 IO [Int]가 되고 이는 (<$>) sum에 의해서 IO Int로 귀결된다.

원본

기본으로 주어지는 템플릿은 다음과 같은 식으로 표현하고 있다. (processLine은 제외) IO 와 관련하여 입력받은 값을 처리하는데 있어서 Applicative의 활용이 얼마나 중요한지를 보여주는 좋은 예라 하겠다.

import Control.Applicative
import Control.Monad
import System.IO


main :: IO ()
main = do
  n_temp <- getLine
  let n = read n_temp :: Int     -- 이 두 라인은 n <- readLn :: IO Int 로 대체할 수 있다.
  a_temp <- getMultipleLines n
  let a = map ( map ( read :: String -> Int ) . words ) a_temp

getMultipleLines :: Int -> IO [String]

getMultipleLines n
  | n <= 0 = return []
  | otherwise = do          
      x <- getLine         
      xs <- getMultipleLines (n-1)    
      let ret = (x:xs)    
      return ret