我最近一直在尝试学习一些函数式编程(使用Haskell和Erlang),我总是惊讶于人们在递归思考并了解工具时可以提出的简洁解决方案。
我想要一个函数将已排序的,唯一的,非连续的整数列表转换为连续列表的列表,即:
[1,2,3,6,7,8,10,11]
为:
[[1,2,3], [6,7,8], [10,11]
这是我在Haskell中提出的最好的(两个函数)::
make_ranges :: [[Int]] -> [Int] -> [[Int]]
make_ranges ranges [] = ranges
make_ranges [] (x:xs)
| null xs = [[x]]
| otherwise = make_ranges [[x]] xs
make_ranges ranges (x:xs)
| (last (last ranges)) + 1 == x =
make_ranges ((init ranges) ++ [(last ranges ++ [x])]) xs
| otherwise = make_ranges (ranges ++ [[x]]) xs
rangify :: [Int] -> [[Int]]
rangify lst = make_ranges [] lst
这可能有点主观,但我有兴趣在Erlang或Haskell中看到更好,更优雅的解决方案(其他功能语言也是如此,但我可能不理解它。)否则,只需修复点我蹩脚的初学者的Haskell风格!
答案 0 :(得分:10)
在我看来,最直接的方式是折叠:
ranges = foldr step []
where step x [] = [[x]]
step x acc@((y:ys):zs) | y == x + 1 = (x:y:ys):zs
| otherwise = [x]:acc
或者,更简洁:
ranges = foldr step []
where step x ((y:ys):zs) | y == x + 1 = (x:y:ys):zs
step x acc = [x]:acc
但等等,还有更多!
abstractRanges f = foldr step []
where step x ((y:ys):zs) | f x y = (x:y:ys):zs
step x acc = [x]:acc
ranges = abstractRanges (\x y -> y == x + 1)
powerRanges = abstractRanges (\x y -> y == x*x) -- mighty morphin
通过将保护功能转换为参数,您可以将更多有趣的事物分组,而不仅仅是+1序列。
*Main> powerRanges [1,1,1,2,4,16,3,9,81,5,25]
[[1,1,1],[2,4,16],[3,9,81],[5,25]]
这个特殊功能的效用值得怀疑......但很有趣!
答案 1 :(得分:9)
我无法相信我得到了最短的解决方案。我知道这不是高尔夫代码,但我认为它仍然具有可读性:
import GHC.Exts
range xs = map (map fst) $ groupWith snd $ zipWith (\a b -> (a, a-b)) xs [0..]
或pointfree
range = map (map snd) . groupWith fst . zipWith (\a b -> (b-a, b)) [0..]
顺便说一下,如果您希望groupWith snd
超过groupBy (\a b -> snd a == snd b)
Data.List
可以替换为GHC.Exts
<强> [编辑] 强>
BTW:是否有一种更好的方法来摆脱lambda (\a b -> (b-a, b))
而不是(curry $ (,) <$> ((-) <$> snd <*> fst) <*> snd)
?
[编辑2]
是的,我忘了(,)
是一个算符。所以这是混淆版本:
range = map (map fst) . groupWith snd . (flip $ zipWith $ curry $ fmap <$> (-).fst <*> id) [0..]
欢迎提出意见......
答案 2 :(得分:4)
import Data.List (groupBy)
ranges xs = (map.map) snd
. groupBy (const fst)
. zip (True : zipWith ((==) . succ) xs (tail xs))
$ xs
至于如何提出这样的事情:我开始使用zipWith f xs (tail xs)
,当你想对列表的连续元素做某事时,这是一个常见的习惯用法。同样,zip
ping一个列表,其中包含有关列表的信息,然后对其执行操作(groupBy
)。其余的是管道。
然后,当然,您可以通过@pl提供它并获取:
import Data.List (groupBy)
import Control.Monad (ap)
import Control.Monad.Instances()
ranges = (((map.map) snd)
. groupBy (const fst))
.) =<< zip
. (True:)
. ((zipWith ((==) . succ)) `ap` tail)
,根据我的权威定义,由Mondad ((->) a)
引起的是邪恶的。 两次,甚至。数据流蜿蜒得太多,无法以任何合理的方式进行布局。 zipaptail
是阿兹特克人的神,阿兹台克人的神不会被混淆。
答案 3 :(得分:4)
Erlang中的另一个版本:
part(List) -> part(List,[]).
part([H1,H2|T],Acc) when H1 =:= H2 - 1 ->
part([H2|T],[H1|Acc]);
part([H1|T],Acc) ->
[lists:reverse([H1|Acc]) | part(T,[])];
part([],Acc) -> Acc.
答案 4 :(得分:3)
尝试重复使用标准功能。
import Data.List (groupBy)
rangeify :: (Num a) => [a] -> [[a]]
rangeify l = map (map fst) $ groupBy (const snd) $ zip l contigPoints
where contigPoints = False : zipWith (==) (map (+1) l) (drop 1 l)
或者,遵循(混合)建议使用unfoldr
,停止滥用groupBy
,并且在无关紧要时使用部分功能感到高兴:
import Control.Arrow ((***))
import Data.List (unfoldr)
spanContig :: (Num a) => [a] -> [[a]]
spanContig l =
map fst *** map fst $ span (\(a, b) -> a == b + 1) $ zip l (head l - 1 : l)
rangeify :: (Num a) => [a] -> [[a]]
rangeify = unfoldr $ \l -> if null l then Nothing else Just $ spanContig l
答案 5 :(得分:3)
k z = map (fst <$>) . groupBy (const snd) .
zip z . (False:) . (zipWith ((==) . succ) <*> tail) $ z
答案 6 :(得分:3)
使用foldr的Erlang:
ranges(List) ->
lists:foldr(fun (X, [[Y | Ys], Acc]) when Y == X + 1 ->
[[X, Y | Ys], Acc];
(X, Acc) ->
[[X] | Acc]
end, [], List).
答案 7 :(得分:2)
这是我的v0.1,我可能会做得更好:
makeCont :: [Int] -> [[Int]]
makeCont [] = []
makeCont [a] = [[a]]
makeCont (a:b:xs) = if b - a == 1
then (a : head next) : tail next
else [a] : next
where
next :: [[Int]]
next = makeCont (b:xs)
我会努力让它变得更好。编辑即将到来。
答案 8 :(得分:2)
作为比较,这是Erlang中的一个实现:
partition(L) -> [lists:reverse(T) || T <- lists:reverse(partition(L, {[], []}))].
partition([E|L], {R, [EL|_] = T}) when E == EL + 1 -> partition(L, {R, [E|T]});
partition([E|L], {R, []}) -> partition(L, {R, [E]});
partition([E|L], {R, T}) -> partition(L, {[T|R], [E]});
partition([], {R, []}) -> R;
partition([], {R, T}) -> [T|R].
答案 9 :(得分:2)
在Erlang中可以非常清楚和简单:
partition([]) -> [];
partition([A|T]) -> partition(T, [A]).
partition([A|T], [B|_]=R) when A =:= B+1 -> partition(T, [A|R]);
partition(L, P) -> [lists:reverse(P)|partition(L)].
编辑:为了好奇,我已经比较了我和Lukas的version,而且在本机上或者在字节码版本中测试设置的内容看起来快了大约10%我在我的笔记本电脑上的R14B01 64b版本上由lists:usort([random:uniform(1000000)||_<-lists:seq(1,1000000)])
生成。 (测试集长度为669462,已分区为232451个子列表。)
Edit2 :另一个测试数据lists:usort([random:uniform(1000000)||_<-lists:seq(1,10000000)])
,长度999963和38个分区在本机代码中产生更大的差异。我们的版本在不到一半的时间内完成。字节码版本仅快了约20%。
Edit3 :一些微观优化提供了额外的性能,但会导致代码更难看且维护更少:
part4([]) -> [];
part4([A|T]) -> part4(T, A, []).
part4([A|T], B, R) when A =:= B+1 -> part4(T, A, [B|R]);
part4([A|T], B, []) -> [[B]|part4(T, A, [])];
part4([A|T], B, R) -> [lists:reverse(R, [B])|part4(T, A, [])];
part4([], B, R) -> [lists:reverse(R,[B])].
答案 10 :(得分:2)
标准的paramorphism递归方案不在Haskell的Data.List模块中,尽管我认为它应该是。这是一个使用paramorphism的解决方案,因为你正在从列表中构建列表列表,这个问题有点棘手:
contig :: (Eq a, Num a) => [a] -> [[a]]
contig = para phi [] where
phi x ((y:_),(a:acc)) | x + 1 == y = (x:a):acc
phi x (_, acc) = [x]:acc
Paramorphism是一般递归或 lookahead 的折叠:
para :: (a -> ([a], b) -> b) -> b -> [a] -> b
para phi b [] = b
para phi b (x:xs) = phi x (xs, para phi b xs)
答案 11 :(得分:1)
这是来自haskell noob的尝试
ranges ls = let (a, r) = foldl (\(r, a@(h:t)) e -> if h + 1 == e then (r, e:a) else (a:r, [e])) ([], [head ls]) (tail ls)
in reverse . map reverse $ r : a