我正在完成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)
答案 0 :(得分:6)
我首先要开始使用模式匹配。
areListsEqual :: Eq a => [a] -> [a] -> Bool
areListsEqual [ ] [ ] = True
areListsEqual [ ] _ = False
areListsEqual _ [ ] = False
areListsEqual (x:xs) (y:ys) = x == y && areListsEqual xs ys
请注意,当head
和tail
被取消时,这是多么可读。
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
我们使用模式匹配,这也允许我们命名
直接first
,second
,theRest
。 name @ pattern
模式
匹配pattern
的输入和将整个输入命名为name
。
现在,我想避免使用take
和drop
来提取这两个元素
modifiedPair
的{{1}},所以最后两行改为
[shorter,longer] = swapPairIfNeeded first second
wip = [shorter] ++ charlieSort ([longer] ++ theRest)
您可以将最后一行写为
wip = shorter : charlieSort (longer : theRest)
如果你愿意的话。但为什么swapPairIfNeeded
会返回shorter
和。{1}}
列表中longer
和first
列表的second
?为什么不使用
对像
swapPairIfNeeded a b
| length a >= length b = (second,first)
| otherwise = (first,second)
(shorter,longer) = swapPairIfNeeded first second
?在大多数情况下,最好使用元组作为固定数量的元组
值(可能是不同类型)和使用列表的变量数
值(必须是相同类型)。但这似乎很奇怪
swapPairIfNeeded
比较其参数a
和b
,但随后返回
无论如何first
和second
。在这种情况下,而不是让它返回a
和b
成对,我将完全删除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)
查理:我将仅限于一个批评:如果你可以使用模式匹配,那么永远不要使用head
,tail
或length
: / 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 head
和tail
的所有用法都可以使用模式匹配进行重写,初学者应该这样做。并非length
的所有用法都可以替换为模式匹配,但是很常见可以而且应该重写length xs == 0
或length 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
根据各种标准进行排序。尝试根据每个子列表的总和对列表进行排序。