批评这个深夜,noob Haskell代码

时间:2009-04-25 06:31:04

标签: sorting functional-programming haskell

我正在完成Real World Haskell,目前正在进行第3章末尾的练习。

我采取了一种不同寻常的方法:即使我知道有些语言功能尚未涵盖,但这对我有帮助,我正在尝试使用进行这些练习他们明确涵盖的事情。为什么?真的很有趣。感觉它强迫我给我的大脑一些额外的递归练习。

所以我刚刚完成了如下所示的练习:“创建一个函数,根据每个子列表的长度对列表列表进行排序。(您可能需要查看Data.List模块中的sortBy函数) 。)“

现在,他们提出了有关Data.List模块的提示。但是他们没有说出可以找到参考文档的地方,关于如何导入内容等等。所以我决定滚动我自己的排序只是为了看看我是否可以做到。我使用冒泡排序,因为它是最简单的算法。

结果如下。我想让你的Haskell专家对它进行评论......但请注意以下几点: 如果你建议改进,请根据第3章所述的语言功能进行修改。真实的世界Haskell(或者你猜这些功能可能没有遇到麻烦的查找)。我知道有很多令人敬畏的语言功能等着我,这会让我更好地编写代码,但是现在具体的挑战是使用到目前为止所涵盖的“原始”功能。

我确信有些情况下我会绕到我的肩膀以划伤我的肘部,当递归和模式匹配可以为我做更多事情时,我使用显式控制流程的情况等等。我确定代码可以更短,更可读。我打赌有一些我不知道的好习惯,可以用于我限制自己的原始语言特征。这些都是我喜欢的提示。

这可能是我用任何语言引以为傲的最丑陋的代码(至少,我记得)。我用函数式语言首次尝试了“Hello,world”类型的东西。现在你要打败它了:)。请温柔,但我期待着一些有趣的见解。感谢。

areListsEqual :: (Eq a) => [a] -> [a] -> Bool

areListsEqual [] [] = True
areListsEqual [] _  = False
areListsEqual _ []  = False

areListsEqual xs ys = (head xs == head ys)  && (areListsEqual (tail xs) (tail ys))

charlieSort :: (Eq a) => [[a]] -> [[a]]

charlieSort [] = []
charlieSort (x:xs) | null xs = [x]
charlieSort xs | (length xs) >= 2 = if(not (areListsEqual xs wip))
                    then charlieSort wip 
                    else wip
                    where
                      first = head xs
                      second = head (tail xs)
                      theRest = drop 2 xs
                      swapPairIfNeeded a b = if(length a >= length b) 
                          then [second, first]
                          else [first, second]
                      modifiedPair = swapPairIfNeeded first second
                      wip = (take 1 modifiedPair) ++ charlieSort ( (drop 1 modifiedPair) ++ theRest)

5 个答案:

答案 0 :(得分:6)

我首先要开始使用模式匹配。

areListsEqual :: Eq a => [a] -> [a] -> Bool
areListsEqual [    ] [    ] = True
areListsEqual [    ] _      = False
areListsEqual _      [    ] = False
areListsEqual (x:xs) (y:ys) = x == y && areListsEqual xs ys

请注意,当headtail被取消时,这是多么可读。

charlieSort :: Eq a => [[a]] -> [[a]]
charlieSort    [                    ] = []
charlieSort    [x                   ] = [x]
charlieSort xs@(first:second:theRest)
  | areListsEqual xs wip              = wip
  | otherwise                         = charlieSort wip
  where
  swapPairIfNeeded a b
    | length a >= length b = [second,first]
    | otherwise            = [first,second]
  modifiedPair = swapPairIfNeeded first second
  wip = take 1 modifiedPair ++ charlieSort (drop 1 modifiedPair ++ theRest)

我将if - then - else更改为警卫,以提高可读性 (因人而异)。而不是检查列表是否至少有两个元素 调用length我们使用模式匹配,这也允许我们命名 直接firstsecondtheRestname @ pattern模式 匹配pattern 的输入和将整个输入命名为name

现在,我想避免使用takedrop来提取这两个元素 modifiedPair的{​​{1}},所以最后两行改为

  [shorter,longer] = swapPairIfNeeded first second
  wip = [shorter] ++ charlieSort ([longer] ++ theRest)

您可以将最后一行写为

  wip = shorter : charlieSort (longer : theRest)

如果你愿意的话。但为什么swapPairIfNeeded会返回shorter和。{1}} 列表longerfirst列表的second?为什么不使用 对像

  swapPairIfNeeded a b
    | length a >= length b = (second,first)
    | otherwise            = (first,second)
  (shorter,longer) = swapPairIfNeeded first second

?在大多数情况下,最好使用元组作为固定数量的元组 值(可能是不同类型)和使用列表的变量数 值(必须是相同类型)。但这似乎很奇怪 swapPairIfNeeded比较其参数ab,但随后返回 无论如何firstsecond。在这种情况下,而不是让它返回ab成对,我将完全删除swapPairIfNeeded

  (shorter,longer)
    | length first >= length second = (second,first)
    | otherwise                     = (first,second)

swapPairIfNeeded的主体“展开”到定义中 (shorter,longer)

所以现在charlieSort的代码看起来像

charlieSort :: Eq a => [[a]] -> [[a]]
charlieSort    [                    ] = []
charlieSort    [x                   ] = [x]
charlieSort xs@(first:second:theRest)
  | areListsEqual xs wip              = wip
  | otherwise                         = charlieSort wip
  where
  (shorter,longer)
    | length first >= length second = (second,first)
    | otherwise                     = (first,second)
  wip = shorter : charlieSort (longer : theRest)

最后,我应该注意charlieSort并没有真正实现 冒泡,因为递归调用charlieSort不仅会 沿着列表进行一次“冒泡”传递,但也完全对列表进行排序 longer : theRest,以便在递归调用之后必须完成所有操作 (在返回一个“升级”之前)可能会将shorter渗透到其中 合法的地方。

答案 1 :(得分:4)

查理:我将仅限于一个批评:如果你可以使用模式匹配,那么永远不要使用headtaillength : / p>

areListsEqual [] [] = True
areListsEqual (x:xs) (y:ys) = x == y && areListsEqual xs ys
areListsEqual _ _   = False

我无法遵循你的排序算法(你可以重新格式化你的问题以消除水平滚动条),但是我会重写前三行:

charlieSort []  = []
charlieSort [x] = x
charlieSort (x1:x2:xs) = if ...

(PS headtail的所有用法都可以使用模式匹配进行重写,初学者应该这样做。并非length的所有用法都可以替换为模式匹配,但是很常见可以而且应该重写length xs == 0length xs >= 2等noob代码。)

(PPS即使是经验丰富的Haskell程序员也很少使用'head'。格拉斯哥Haskell编译器中不到百分之二十的源代码行提到'head',并且提到这些提及的眼睛,大约一半是字符串文字或注释。对于每1500行代码,这大约是一次使用'head'。)

答案 2 :(得分:2)

您不需要areListsEqual功能。您可以将列表与(==)函数进行比较。而且我会使用quicksort而不是bubblesort。这是一个解决方案,我认为只使用到目前为止应该学到的东西。

charlieSort :: (Eq a) => [[a]] -> [[a]]
charlieSort []     = []
charlieSort (x:xs) = charlieSort (filter (cmpLen (>) x) xs) ++ [x] ++
                     charlieSort (filter (cmpLen (<=) x) xs)
   where filter _ [] = []
         filter p (x:xs) = (if (p x) then (x:) else id) (filter p xs)
         cmpLen f x y = f (length x) (length y)

答案 3 :(得分:1)

我在第8章,所以我不老手,但我更喜欢

areListsEqual x:xs y:ys = (x == y) && (areListsEqual xs ys)
areListsEqual [] [] = True
areListsEqual _ _ = False

似乎更符合Haskell风格。

类似地,

charlieSort [] = []
charlieSort (x:[]) = [x]
charlieSort (x1:x2:xs) = blah blah

swapPairIfNeed按原样工作,因为你只用第一个和第二个作为参数调用它(按顺序),但你可能意味着

swapPairIfNeed a b = if (length a >= length b)
    then [b, a]
    else [a, b]

事实上,我更喜欢charlieSort的第三种情况看起来像

charlieSort (x1:x2:xs) = if not (areListsEqual x1:x2:xs wip)
                         then charlieSort wip
                         else wip
    where swapPairIfNeeded a b = if (length a >= length b)
                                 then (b, a)
                                 else (a, b)
          wip = f (swapPairIfNeeded first second)
          f (a, b) = a : (charlieSort b:xs)

我认为第3章已经涵盖了这一点。

现在,我们来看看算法。即使我们自己进行冒泡排序,也无需在排序后检查整个列表。相反,如果需要,我们可以交换前两个元素,然后对列表的尾部进行排序。如果头部比排序尾部的头部短,我们就完成了。

charlieSort (x1:x2:xs) = if (length a <= length (head sortedTail))
                         then a : sortedTail
                         else charlieSort (a : sortedTail)
    where sortedTail = charlieSort (b:xs)
          (a, b) = if (length x1 >= length x2)
                   then (x2, x1)
                   else (x1, x2)

答案 4 :(得分:1)

您声称冒泡排序是最简单的排序算法,但这并非如此。冒泡排序非常适合数组,您可以在其中线性索引。对于Haskell的链表,插入排序实际上看起来更漂亮。

让我们从insert函数开始:

winsert :: [a] -> [[a]] -> [[a]]
winsert x [] = [x]
winsert x (y:ys)
    | length x < length y = x : y : ys
    | otherwise = y : winsert x ys
  • 如果列表为空,请将x放入其中
  • 如果列表不为空:
    • 如果x < y,则x属于列表的前面
    • 否则,列表的标题为y,尾部由x向上插入ys

接下来,我们有实际的排序功能:

wsort :: [[a]] -> [[a]]
wsort [] = []
wsort [x] = [x]
wsort (x:xs) = winsert x (wsort xs)
  • 如果列表为空,请将其返回
  • 如果列表只有一个项目,则不需要对其进行排序
  • 如果列表长于该列表,请对xs进行排序,然后将x插入现已排序的xs

有趣的是,通过修改winsert以将函数作为参数(代替length),可以使用wsort根据各种标准进行排序。尝试根据每个子列表的总和对列表进行排序。