List 尾部递归排序和合并两个列表的方法
我正在尝试编写一种尾部递归方法,将两个排序列表合并为一个排序列表 这是我的。首先我有无尾递归的方法List 尾部递归排序和合并两个列表的方法,list,haskell,List,Haskell,我正在尝试编写一种尾部递归方法,将两个排序列表合并为一个排序列表 这是我的。首先我有无尾递归的方法 merge2 :: Ord a => [a] -> [a] -> [a] merge2 l1 [] = l1 merge2 [] l2 = l2 merge2 (x:xs) (y:ys) | x > y = y : merge2 (x:xs) ys | x < y = x : merge2 xs (y:ys)
merge2 :: Ord a => [a] -> [a] -> [a]
merge2 l1 [] = l1
merge2 [] l2 = l2
merge2 (x:xs) (y:ys) | x > y = y : merge2 (x:xs) ys
| x < y = x : merge2 xs (y:ys)
| otherwise = x : merge2 (x:xs) ys
mergeTail :: Ord a => [a] -> [a] -> [a]
mergeTail accum [] = accum
mergeTail accum (x:xs) = mergeTail (x:accum) xs
当我输入像merge2Tail[1,2][2,3,4]这样的东西时,我希望得到[1,2,2,3,4]作为输出,但我得到的是随机顺序。我不确定在何处或如何实现在保持尾部递归的同时检查顺序的案例。首先,让我们看看非尾部递归是什么样子:
merge :: Ord a => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge fullXs@(x:xs) fullYs@(y:ys) | x <= y = x : merge xs fullYs
| x > y = y : merge fullXs ys
因此,功能将是:
mergeTail :: Ord a => [a] -> [a] -> [a]
mergeTail xs [] = xs
mergeTail [] ys = ys
mergeTail xs ys = mergeAccum [] xs ys
mergeAccum :: Ord a => [a] -> [a] -> [a] -> [a]
mergeAccum acc [] [] = acc
mergeAccum acc [] (y:ys) = mergeAccum (acc ++ [y]) [] ys
mergeAccum acc (x:xs) [] = mergeAccum (acc ++ [x]) xs []
mergeAccum acc (x:xs) (y:ys) | x <= y = mergeAccum (acc ++ [x]) xs (y:ys)
| x > y = mergeAccum (acc ++ [y]) (x:xs) ys
注:
在这种情况下,此函数的效率很低,因为每次递归调用都必须转到accum列表的末尾 首先,让我们看看非尾部递归的样子:
merge :: Ord a => [a] -> [a] -> [a]
merge xs [] = xs
merge [] ys = ys
merge fullXs@(x:xs) fullYs@(y:ys) | x <= y = x : merge xs fullYs
| x > y = y : merge fullXs ys
因此,功能将是:
mergeTail :: Ord a => [a] -> [a] -> [a]
mergeTail xs [] = xs
mergeTail [] ys = ys
mergeTail xs ys = mergeAccum [] xs ys
mergeAccum :: Ord a => [a] -> [a] -> [a] -> [a]
mergeAccum acc [] [] = acc
mergeAccum acc [] (y:ys) = mergeAccum (acc ++ [y]) [] ys
mergeAccum acc (x:xs) [] = mergeAccum (acc ++ [x]) xs []
mergeAccum acc (x:xs) (y:ys) | x <= y = mergeAccum (acc ++ [x]) xs (y:ys)
| x > y = mergeAccum (acc ++ [y]) (x:xs) ys
注:
在这种情况下,此函数的效率很低,因为每次递归调用都必须转到accum列表的末尾 正如其他人所指出的,这是一个高度人工的练习:在Haskell中不应该递归地合并两个排序列表以形成一个排序列表。但是,如果您在一个调用堆栈大小非常有限且不支持尾部递归模cons的语言环境中构建一个严格的spined列表,那么这样做可能是合理的。在这种环境下,通常最好将此类问题分为两部分: 浏览列表,反向构建一个列表作为累加器 使用累加器生成最终结果 让我们从这个开始
merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
where
go acc [] ys = reverse acc ++ ys
go acc xs [] = reverse acc ++ xs
go acc xss@(x : xs) yss@(y : ys)
| x <= y = go (x : acc) xs yss
| otherwise = go (y : acc) xss ys
最后,
merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
where
go acc [] ys = acc `reverseOnto` ys
go acc xs [] = acc `reverseOnto` xs
go acc xss@(x : xs) yss@(y : ys)
| x <= y = go (x : acc) xs yss
| otherwise = go (y : acc) xss ys
我相信这是在你的约束下所能做的最好的事情。正如其他人所指出的,这是一个高度人工的练习:在Haskell中不应该递归地合并两个排序列表以形成一个排序列表。但是,如果您在一个调用堆栈大小非常有限且不支持尾部递归模cons的语言环境中构建一个严格的spined列表,那么这样做可能是合理的。在这种环境下,通常最好将此类问题分为两部分: 浏览列表,反向构建一个列表作为累加器 使用累加器生成最终结果 让我们从这个开始
merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
where
go acc [] ys = reverse acc ++ ys
go acc xs [] = reverse acc ++ xs
go acc xss@(x : xs) yss@(y : ys)
| x <= y = go (x : acc) xs yss
| otherwise = go (y : acc) xss ys
最后,
merge :: Ord a => [a] -> [a] -> [a]
merge = \xs ys -> go [] xs ys
where
go acc [] ys = acc `reverseOnto` ys
go acc xs [] = acc `reverseOnto` xs
go acc xss@(x : xs) yss@(y : ys)
| x <= y = go (x : acc) xs yss
| otherwise = go (y : acc) xss ys
我相信这是在你的约束下你能做的最好的了。你知道如何以非尾部递归的方式来做吗?是的,我应该编辑这个并包含它吗?这听起来相当低效。为什么希望它是尾部递归的?@Larry.Fish那么您需要三个参数,l1和l2以及累加器。是的,基于累加器的解决方案使用带有附加参数的辅助函数。最初,它可以是一个空列表。递归时,将要输出到累加器的元素前置。最后,累加器将以相反的顺序包含所需列表。您知道如何以非尾部递归的方式进行此操作吗?是的,我应该编辑此列表并将其包括在内吗?这听起来相当低效。为什么希望它是尾部递归的?@Larry.Fish那么您需要三个参数,l1和l2以及累加器。是的,基于累加器的解决方案使用带有附加参数的辅助函数。最初,它可以是一个空列表。递归时,将要输出到累加器的元素前置。最后,累加器将以相反的顺序包含通缉名单。对于这种情况,标准的补救方法是反向构建累加器,使用有效:,并在返回之前在基本情况下反转累积的结果。@Willenss我不太理解您的概念,它应该是什么?我不知道你改变了mergeAccum acc++[y]。。。在mergeAccum y:acc的递归子句中。。。而您更改了基本case子句…=acc进入…=逆向:这种情况下的标准补救方法是反向构建累加器,使用有效:,并在返回之前反向基本情况下的累积结果。@Willenss我不太理解你的概念,它应该是什么?我不知道你改变了mergeAccum acc++[y]。。。在mergeAccum y:acc的递归子句中。。。而您更改了基本case子句…=acc进入…=reverse acc:顺便说一句,reverseOnto是Lisps中非常常见的一种范例,我一直认为它甚至有一个内置的,revappend之类的;或者应该是,如果没有的话。当然,我们也可以在Haskell中使用flip$foldl’flip:顺便说一句,reverseOnto是Lisps中非常常见的一个范例,我一直认为它甚至有一个内置的,revappend之类的;或者应该是,如果没有的话。当然,我们也可以在Haskell中使用flip$foldl’flip: