Шифр простой транспозиции строк
для класса Lisp нам дали простое домашнее задание по шифру транспозиции строк, которое я тоже пытался решить в Haskell. В принципе, просто разбивает строку на строки длины n, а затем закрепляет результат. Конкатенация результирующего списка списков символов является зашифрованной строкой. Декодирование немного сложнее, так как в последней строке ввода могут отсутствовать элементы (неполные столбцы в результате), о которых нужно позаботиться.
Это мой решение в Haskell:
import Data.List
import Data.Ratio
import Data.List.Split
encode :: String -> Int -> String
encode s n = concat . transpose $ chunk n s
decode :: String -> Int -> String
decode s n = take len $ encode s' rows
    where s'     = foldr (insertAt " ") s idxs
          rows   = ceiling (len % n)
          idxs   = take (n-filled) [n*rows-1,(n-1)*rows-1..]
          filled = len - n * (rows - 1)
          len    = length s
insertAt :: [a] -> Int -> [a] -> [a]
insertAt xs i ys = pre ++ xs ++ post
    where (pre,post) = splitAt i ys
это делает работу, но я не уверен, будет ли это считаться идиоматическим Haskell, так как моя возня с индексами не кажется слишком декларативной. Можно ли это улучшить, и если да, то как?
кстати: есть ли что-то сродни insertAt в Haskell 98? Т. е. функция, вставляющая элемент или список по заданному индексу в список.
Примечание: это не часть домашней работы, которая должна была быть сегодня в любом случае.
1 ответов
Я бы сделал это, посмотрев на encode и decode проблемы немного по-другому.  encode разбивает данные на n - матрица столбцов, которую она затем транспонирует (в n-матрица строк) и конкатенирует по строкам.  decode разбивает данные на n матрица строк, которую она затем транспонирует (в n columm matrix) и конкатенирует по строкам.
поэтому я бы начал с определения двух функций-одной, чтобы сделать массив в матрица:
chunk:: Int -> [a] -> [[a]]
chunk n as = chunk' n (length as) as
  where chunk' n l as | l <= n    = [as]
                      | otherwise = some : chunk' n (l-n) rest 
                          where (some, rest) = splitAt n as
и еще один, чтобы нарезать массив на n матрица строки:
slice :: Int -> [a] -> [[a]]
slice n as = chunk (q+1) front ++ chunk q back
  where (q,r) = length as `divMod` n
        (front, back) = splitAt (r*(q+1)) as
теперь кодирование и декодирование довольно легко:
encode :: Int -> [a] -> [a]
encode = ((concat . transpose) .). chunk
decode :: Int -> [a] -> [a]
decode = ((concat . transpose) .). slice
