Haskell 99 Questions :: 0x03

archive time: 2024-11-04

还是要继续练习才行

之前已经完成了前二十题,今天我们继续写

题目

Question 21

Insert an element at a given position into a list.

插入元素到给定的列表中

qInsertAt :: [a] -> Int -> a -> Maybe [a]
答案
qInsertAt :: [a] -> Int -> a -> Maybe [a]
qInsertAt lst k e = if k < 1 then Nothing else qInsertAtI lst k e []
  where
    qInsertAtI :: [a] -> Int -> a -> [a] -> Maybe [a]
    qInsertAtI xs 1 x acc = Just (qReverse acc ++ (x : xs))
    qInsertAtI [] _ _ _ = Nothing
    qInsertAtI (h : t) n x acc = qInsertAtI t (n - 1) x (h : acc)

Question 22

Create a list containing all integers within a given range.

根据所给范围创建整数列表

qRange :: Int -> Int -> [Int]
答案
qRange :: Int -> Int -> [Int]
qRange a b = [a .. b]

Question 23

Extract a given number of randomly selected elements from a list.

从列表中随机选择 n 个元素

qRandomSelect :: [a] -> Int -> IO (Maybe [a])
答案
qRandomSelect :: [a] -> Int -> IO (Maybe [a])
qRandomSelect lst n = qRandomSelectI lst n []
  where
    qRandomSelectI :: [a] -> Int -> [a] -> IO (Maybe [a])
    qRandomSelectI (h : _) 1 acc = return (Just (h : acc))
    qRandomSelectI [] _ _ = return Nothing
    qRandomSelectI l k acc
      | k < 1 = return Nothing
      | otherwise = do
          idx <- randomRIO (1, qLength l)
          case qRemoveAt l idx of
            (Just e, rest) -> qRandomSelectI rest (k - 1) (e : acc)
            (Nothing, _) -> return Nothing

Question 24

Lotto: draw n different random numbers from the set [1 .. m].

[1 .. m] 中随机选择 n 个数字

qLotto :: Int -> Int -> IO (Maybe [Int])
答案
qLotto :: Int -> Int -> IO (Maybe [Int])
qLotto n m = qRandomSelect [1 .. m] n

Question 25

Generate a random permutation of the elements of a list.

给出所给列表的一个随机排列

qRandomPermutation :: [a] -> IO (Maybe [a])
答案
qRandomPermutation :: [a] -> IO (Maybe [a])
qRandomPermutation lst = qRandomSelect lst (qLength lst)

Question 26

Generate the combinations of k elements chosen from the n elements of a list.

生成所有的 的选择可能

qCombinantion :: [a] -> Int -> Maybe [[a]]
答案
qCombination :: [a] -> Int -> Maybe [[a]]
qCombination lst k
  | k < 1 || k > qLength lst = Nothing
  | otherwise = Just (qCombinationI lst k [] [])
  where
    qCombinationI :: [a] -> Int -> [a] -> [[a]] -> [[a]]
    qCombinationI l m c acc = case m of
      0 -> c : acc
      _ -> case l of
        [] -> acc
        h : t ->
          qCombinationI t m c (qCombinationI t (m - 1) (h : c) acc)

Question 27

Group the elements of a set into disjoint subsets.

按照所给组合来选择元素并分组

qGroup :: [a] -> [Int] -> Maybe [[[a]]]
答案
qGroup :: [a] -> [Int] -> Maybe [[[a]]]
qGroup lst g
  | sum g > qLength lst || any (< 1) g = Nothing
  | otherwise = mapM (`partList` g) (permu lst)
  where
    partList :: [a] -> [Int] -> Maybe [[a]]
    partList xs p
      | null xs || null p || sum p > qLength xs || any (< 1) p = Nothing
      | otherwise = Just (partListI xs g [] [])
      where
        partListI :: [a] -> [Int] -> [a] -> [[a]] -> [[a]]
        partListI [] _ _ acc = qReverse acc
        partListI _ [] _ acc = qReverse acc
        partListI (lh : lt) (ph : pt) c acc = case ph of
          0 -> partListI (lh : lt) pt [] (qReverse c : acc)
          _ -> partListI lt ((ph - 1) : pt) (lh : c) acc
    permu :: [a] -> [[a]]
    permu xs = permuI xs [] [] []
      where
        permuI :: [a] -> [a] -> [a] -> [[a]] -> [[a]]
        permuI [] [] select acc = select : acc
        permuI [] _ _ acc = acc
        permuI (h : t) cache select acc =
          permuI t (h : cache) select (permuI (t ++ cache) [] (h : select) acc)

Question 28

Sorting a list of lists according to length of sublists.

分别按照列表的长度和列表长度的频率来排序,其中长度或频率相同的元素保持原列表中顺序

qLengthSort :: (Eq a, Ord a) => [[a]] -> [[a]]

qLengthFrequencySort :: (Eq a, Ord a) => [[a]] -> [[a]]
答案
qLengthSort :: (Ord a) => [[a]] -> [[a]]
qLengthSort lst =
  Data.List.foldl' (++) [] $
    map
      (map (\(_, _, e) -> e) . Data.List.sortBy aux3')
      ( Data.List.groupBy (\(lenA, _, _) (lenB, _, _) -> lenA == lenB) $
          Data.List.sortBy aux3 $
            zipWith (\idx l -> (qLength l, idx, l)) [1 :: Int ..] lst
      )
  where
    aux3 :: (Ord a) => (a, b, c) -> (a, b, c) -> Ordering
    aux3 (a, _, _) (b, _, _)
      | a > b = GT
      | a < b = LT
      | otherwise = EQ
    aux3' :: (Ord b) => (a, b, c) -> (a, b, c) -> Ordering
    aux3' (_, a, _) (_, b, _)
      | a > b = GT
      | a < b = LT
      | otherwise = EQ

qLengthFrequencySort :: [[a]] -> [[a]]
qLengthFrequencySort lst =
  Data.List.foldl' (++) [] $
    map
      (((map (\(_, _, e) -> e) . Data.List.sortBy aux3') . Data.List.foldl' (++) []) . map snd)
      ( Data.List.groupBy (\(lenA, _) (lenB, _) -> lenA == lenB) $
          Data.List.sortBy aux2 $
            map (\g -> (qLength g, g)) $
              Data.List.groupBy (\(lenA, _, _) (lenB, _, _) -> lenA == lenB) $
                Data.List.sortBy aux3 $
                  zipWith (\idx l -> (qLength l, idx, l)) [1 :: Int ..] lst
      )
  where
    aux3 :: (Ord a) => (a, b, c) -> (a, b, c) -> Ordering
    aux3 (a, _, _) (b, _, _)
      | a > b = GT
      | a < b = LT
      | otherwise = EQ
    aux3' :: (Ord b) => (a, b, c) -> (a, b, c) -> Ordering
    aux3' (_, a, _) (_, b, _)
      | a > b = GT
      | a < b = LT
      | otherwise = EQ
    aux2 :: (Ord a) => (a, b) -> (a, b) -> Ordering
    aux2 (a, _) (b, _)
      | a > b = GT
      | a < b = LT
      | otherwise = EQ

测试用例

这一部分的测试代码如下:

测试代码
module Part03 (part03) where

import Qarks.Nnp
  ( qCombination,
    qGroup,
    qInsertAt,
    qLotto,
    qRandomPermutation,
    qRandomSelect,
    qRange,
  )
import Test.Hspec
  ( context,
    describe,
    it,
    shouldBe,
    shouldSatisfy,
  )
import Test.Hspec.Runner (SpecWith)

maybeAllElem :: (Eq a) => [a] -> Maybe [a] -> Bool
maybeAllElem xs lst = case lst of
  Nothing -> False
  Just l -> all (`elem` xs) l

maybeLength :: Maybe [a] -> Int
maybeLength Nothing = 0
maybeLength (Just lst) = length lst

part03 :: SpecWith ()
part03 = describe "Part03" $ do
  context "Qarks.Nnp.qInsertAt" $ do
    it "insert 'X' to \"abcd\" at 2" $ do
      qInsertAt "abcd" 2 'X' `shouldBe` Just "aXbcd"
  context "Qarks.Nnp.qRange" $ do
    it "range from 4 to 9" $ do
      qRange 4 9 `shouldBe` [4 .. 9]
  context "Qarks.Nnp.qRandomSelect" $ do
    it "randomly select 3 from ['a' .. 'h']" $ do
      res <- qRandomSelect ['a' .. 'h'] 3
      res
        `shouldSatisfy` \lst ->
          maybeAllElem ['a' .. 'h'] lst
            && maybeLength lst == 3
  context "Qarks.Nnp.qLotto" $ do
    it "randomly select 6 from [1 .. 49]" $
      do
        res <- qLotto 6 49
        res
          `shouldSatisfy` \lst ->
            maybeAllElem [1 .. 49] lst
              && maybeLength lst == 6
  context "Qarks.Nnp.qRandomPermutation" $ do
    it "random permutation of ['a' .. 'f']" $ do
      res <- qRandomPermutation ['a' .. 'f']
      res
        `shouldSatisfy` \lst ->
          maybeAllElem ['a' .. 'f'] lst
            && maybeLength lst == length ['a' .. 'f']
  context "Qarks.Nnp.qCombination" $ do
    let res = qCombination [1 .. 4 :: Int] 2
    it "combinan of [1 .. 4] with 2 :: length" $ do
      length <$> res `shouldBe` Just 6
    it "combinan of [1 .. 4] with 2 :: sublength" $ do
      all (\l -> length l == 2) <$> res `shouldBe` Just True
    it "combinan of [1 .. 4] with 2 :: subelem" $ do
      all (all (\e -> e `elem` [1 .. 4])) <$> res
        `shouldBe` Just True
  context "Qarks.Nnp.qGroup" $ do
    let res = qGroup [1 .. 3 :: Int] [1, 1]
    it "group [1 .. 3] by [1, 1] :: length" $ do
      length <$> res `shouldBe` Just 6
    it "group [1 .. 3] by [1, 1] :: sublength" $ do
      all (\l -> length l == 2) <$> res `shouldBe` Just True
    it "group [1 .. 3] by [1, 1] :: subelem" $ do
      all (all (\e -> length e == 1 && head e `elem` [1 .. 4])) <$> res
        `shouldBe` Just True
  context "Qarks.Nnp.qLengthSort" $ do
    it "sort [\"abc\", \"de\", \"fgh\", \"de\", \"ijkl\", \"mn\", \"o\"] by length" $ do
      qLengthSort ["abc", "de", "fgh", "de", "ijkl", "mn", "o"]
        `shouldBe` ["o", "de", "de", "mn", "abc", "fgh", "ijkl"]
  context "Qarks.Nnp.qLengthFrequencySort" $ do
    it "sort [\"abc\", \"de\", \"fgh\", \"de\", \"ijkl\", \"mn\", \"o\"] by frequency" $ do
      qLengthFrequencySort ["abc", "de", "fgh", "de", "ijkl", "mn", "o"]
        `shouldBe` ["ijkl", "o", "abc", "fgh", "de", "de", "mn"]