我刚刚开始学习使用Haskel的函数式编程。
我正在慢慢地通过Erik Meijer's lectures on Channel 9(我看过目前为止的前4个),在第4个视频中,Erik解释了尾部是如何工作的,这让我很着迷。
我试过编写一个返回列表中间的函数(偶数长度为2项,奇数为1项)我想听听其他人如何实现它
如果你能解释你的选择,我将非常感激。
我的初学者代码如下所示:
middle as | length as > 2 = middle (drop 2 (reverse as))
| otherwise = as
答案 0 :(得分:17)
我没有做任何性能声明,虽然它只处理列表的元素一次(我的假设是计算length t
是一个O(N)操作,所以我避免它),但这里是我的溶液:
mid [] = [] -- Base case: the list is empty ==> no midpt
mid t = m t t -- The 1st t is the slow ptr, the 2nd is fast
where m (x:_) [_] = [x] -- Base case: list tracked by the fast ptr has
-- exactly one item left ==> the first item
-- pointed to by the slow ptr is the midpt.
m (x:y:_) [_,_] = [x,y] -- Base case: list tracked by the fast ptr has
-- exactly two items left ==> the first two
-- items pointed to by the slow ptr are the
-- midpts
m (_:t) (_:_:u) = m t u -- Recursive step: advance slow ptr by 1, and
-- advance fast ptr by 2.
这个想法是在列表中有两个“指针”,一个在递归中的每个点递增一步,另一个递增2。
(这基本上是Carl Smotricz所建议的)
答案 1 :(得分:16)
只是为了你的娱乐,一个不会说Haskell的人的解决方案:
编写一个递归函数,它接受两个参数a1和a2,并将列表作为两个参数传递。在每次递归时,从a2中删除2,从a1中删除1。如果你没有a2的元素,你将处于a1的中间。您可以处理a2中仅剩1个元素的情况,以回答您是否需要1个或2个元素作为“中间”。
答案 2 :(得分:15)
答案 3 :(得分:10)
我试过编写一个返回列表中间的函数(偶数长度为2项,奇数为1项)我想听听其他人如何实现它
正确问题的正确数据结构。在这种情况下,你已经指定了一些只在有限列表中有意义的东西,对吗?无限列表没有“中间”。因此,只需阅读说明,我们就知道默认的Haskell列表可能不是最佳解决方案:即使我们不需要它,我们也可能为懒惰付出代价。请注意有多少解决方案难以避免2*O(n)
或O(n)
。单链接的懒惰列表与准数组问题不太匹配。
幸运的是,我们在Haskell中有一个有限的列表:它被称为Data.Sequence。
让我们以最明显的方式解决它:'index(length / 2)'。
根据文档, Data.Seq.length为O(1)
。 Data.Seq.index是O(log(min(i,n-i)))
(我认为i = index,n = length)。我们只需称它为O(log n)
。非常好!
请注意,即使我们不是从Seq
开始并且必须将[a]
转换为Seq
,我们仍然可能获胜。 Data.Seq.fromList是O(n)
。因此,如果我们的竞争对手是像O(n)+O(n)
那样的xs !! (length xs)
解决方案,那就像
middle x = let x' = Seq.fromList x in Seq.index(Seq.length x' `div` 2)
会更好,因为它会O(1) + O(log n) + O(n)
,简化为O(log n) + O(n)
,明显优于O(n)+O(n)
。
(我作为练习留给读者修改中间,如果长度是偶数则返回2个项目,如果长度是奇数,则返回1个。毫无疑问,对于具有恒定时间长度和索引操作的数组,可以做得更好,但是我觉得数组不是列表。)
答案 4 :(得分:5)
受Carl's answer启发的Haskell解决方案。
middle = m =<< drop 1
where m [] = take 1
m [_] = take 2
m (_:_:ys) = m ys . drop 1
答案 5 :(得分:2)
如果序列是链表,则遍历此列表是效率的主要因素。由于我们需要知道总长度,我们必须至少遍历一次列表。获得中间元素有两种等效方法:
两者都需要相同数量的步骤。在我看来,第二个是不必要的复杂。
在Haskell中,它可能是这样的:
middle xs = take (2 - r) $ drop ((div l 2) + r - 1) xs
where l = length xs
r = rem l 2
答案 6 :(得分:1)
F#解决方案基于Carl的回答:
let halve_list l =
let rec loop acc1 = function
| x::xs, [] -> List.rev acc1, x::xs
| x::xs, [y] -> List.rev (x::acc1), xs
| x::xs, y::y'::ys -> loop (x::acc1) (xs, ys)
| [], _ -> [], []
loop [] (l, l)
修改以获得列表中的中间元素非常容易:
let median l =
let rec loop acc1 = function
| x::xs, [] -> [List.head acc1; x]
| x::xs, [y] -> [x]
| x::xs, y::y'::ys -> loop (x::acc1) (xs, ys)
| [], _ -> []
loop [] (l, l)
更直观的方法是使用计数器:
let halve_list2 l =
let rec loop acc = function
| (_, []) -> [], []
| (0, rest) -> List.rev acc, rest
| (n, x::xs) -> loop (x::acc) (n - 1, xs)
let count = (List.length l) / 2
loop [] (count, l)
获得中间元素的一个非常难看的修改:
let median2 l =
let rec loop acc = function
| (n, [], isEven) -> []
| (0, rest, isEven) ->
match rest, isEven with
| x::xs, true -> [List.head acc; x]
| x::xs, false -> [x]
| _, _ -> failwith "Should never happen"
| (n, x::xs, isEven) -> loop (x::acc) (n - 1, xs, isEven)
let len = List.length l
let count = len / 2
let isEven = if len % 2 = 0 then true else false
loop [] (count, l, isEven)
获取列表的长度需要至少遍历其整个内容一次。幸运的是,编写自己的列表数据结构非常容易,该结构保存每个节点中列表的长度,允许您获得O(1)中的长度。
答案 7 :(得分:1)
奇怪的是,这个非常明显的表述还没有出现:
middle [] = []
middle [x] = [x]
middle [x,y] = [x,y]
middle xs = middle $ init $ tail xs
答案 8 :(得分:1)
middle xs =
let (ms, len) = go xs 0 [] len
in ms
go (x:xs) i acc len =
let acc_ = case len `divMod` 2 of
(m, 0) -> if m == (i+1) then (take 2 (x:xs))
else acc
(m, 1) -> if m == i then [x]
else acc
in go xs (i+1) acc_ len
go [] i acc _ = (acc,i)
此解决方案仅使用延迟评估遍历列表一次。当它遍历列表时,它会计算长度,然后将其反馈给函数:
let (ms, len) = go xs 0 [] len
现在可以计算中间元素:
let acc' = case len `divMod` 2 of
...
答案 9 :(得分:0)
我自己并不是一个哈斯克勒,但我尝试过这个。
首先是测试(是的,你可以使用Haskell进行TDD)
module Main
where
import Test.HUnit
import Middle
main = do runTestTT tests
tests = TestList [ test1
, test2
, test3
, test4
, test_final1
, test_final2
]
test1 = [0] ~=? middle [0]
test2 = [0, 1] ~=? middle [0, 1]
test3 = [1] ~=? middle [0, 1, 2]
test4 = [1, 2] ~=? middle [0, 1, 2, 3]
test_final1 = [3] ~=? middle [0, 1, 2, 3, 4, 5, 6]
test_final2 = [3, 4] ~=? middle [0, 1, 2, 3, 4, 5, 6, 7]
我找到了解决方案:
module Middle
where
middle a = midlen a (length a)
midlen (a:xs) 1 = [a]
midlen (a:b:xs) 2 = [a, b]
midlen (a:xs) lg = midlen xs (lg - (2))
它将遍历列表两次,一次是为了获得长度而另一半是为了获得中间值,但我不在乎它仍然是O(n)(并且得到某些东西的中间意味着得到它的长度,所以没有理由避免它。)
答案 10 :(得分:0)
我住的是一个衬里,虽然这个例子只适用于奇数列表。我只想伸展自己的大脑!谢谢你的乐趣=)
foo d = map (\(Just a) -> a) $ filter (/=Nothing) $ zipWith (\a b -> if a == b then Just a else Nothing) (Data.List.nub d) (Data.List.nub $ reverse d)
答案 11 :(得分:0)
我的解决方案,我喜欢简单易懂:
middle [] = []
middle xs | odd (length xs) = [xs !! ((length xs) `div` 2)]
| otherwise = [(xs !! ((length xs) `div` 2)),(reverse $ xs) !! ((length xs)`div` 2)]
在Data.List中使用!!
作为获取给定索引值的函数,在这种情况下,该值是列表长度的一半。
编辑:它实际上现在有效
答案 12 :(得分:0)
这在列表上迭代两次。
mid xs = m where
l = length xs
m | l `elem` [0..2] = xs
m | odd l = drop (l `div` 2) $ take 1 $ xs
m | otherwise = drop (l `div` 2 - 1) $ take 2 $ xs
答案 13 :(得分:0)
这是我的版本。这只是一个快速的运行。我确定它不是很好。
middleList xs@(_:_:_:_) = take (if odd n then 1 else 2) $ drop en xs
where n = length xs
en = if n < 5 then 1 else 2 * (n `div` 4)
middleList xs = xs
我试过了。 :)
如果有人想表达评论并告诉我这个解决方案有多糟糕或好,我会非常感激。我不是非常精通Haskell。
编辑:改进了来自kmc的建议#haskell-blah
编辑2:现在可以接受长度小于5的输入列表。
答案 14 :(得分:0)
我喜欢Svante的回答。我的版本:
> middle :: [a] -> [a]
> middle [] = []
> middle xs = take (r+1) . drop d $ xs
> where
> (d,r) = (length xs - 1) `divMod` 2
答案 15 :(得分:0)
一个非常简单但又不那么简洁的解决方案可能是:
middle :: [a] -> Maybe [a]
middle xs
| len <= 2 = Nothing
| even len = Just $ take 2 . drop (half - 1) $ xs
| odd len = Just $ take 1 . drop (half) $ xs
where
len = length xs
half = len `div` 2
答案 16 :(得分:0)
另一个单行解决方案:
--
middle = ap (take . (1 +) . signum . (`mod` 2) . (1 +) . length) $ drop =<< (`div` 2) . subtract 1 . length
--