Elixir枚举分块:按5的倍数拆分严格递增整数列表
解决Elixir列表按5的倍数分组的问题
你遇到的情况很典型——Enum.chunk_by/2的工作逻辑和你的需求不匹配,所以才会得到不符合预期的拆分结果。咱们先拆解原因,再给出几种可行的实现方案。
为什么原方法达不到预期?
Enum.chunk_by/2是根据相邻元素的函数返回值是否变化来分割列表的。你用的判断函数fn(x) -> rem(x,5) == 0 end会让每个5的倍数单独成组:当遇到5的倍数时返回true,下一个非倍数元素返回false,触发分割;非倍数元素之间返回值都是false,会被分到同一组。这就导致每个5的倍数被单独拆出来,和你想要的“以5的倍数开头,包含到下一个5的倍数前所有元素”的分组逻辑完全相反。
方案一:用Enum.reduce/3手动构建分组
我们可以通过累加器跟踪当前分组,遇到5的倍数就新建分组,否则把元素加到当前最后一个分组里。为了优化效率(避免频繁拼接列表尾部),可以反向累加最后再反转:
list = [300, 301, 304, 305, 306, 309, 310, 311, 312, 313, 314, 315, 316, 317, 319, 320] result = list |> Enum.reduce([], fn x, acc -> if rem(x, 5) == 0 do # 遇到5的倍数,新建分组放到累加器头部(反向存储) [[x] | acc] else # 非5的倍数,加到当前第一个分组的头部 case acc do [] -> [[x]] # 兼容列表开头不是5的倍数的情况 [current_group | rest] -> [[x | current_group] | rest] end end end) # 先反转每个分组,再反转整个结果列表,恢复正确顺序 |> Enum.map(&Enum.reverse/1) |> Enum.reverse() IO.inspect(result) # 输出:[[300, 301, 304], [305, 306, 309], [310, 311, 312, 313, 314], [315, 316, 317, 319], [320]]
方案二:递归+Enum.split_while/2(逻辑更直观)
这种方式思路更清晰:每次找到以5的倍数开头的部分,把后面所有非5的倍数都包含进来,然后递归处理剩下的列表:
defmodule Chunker do def split_on_five_multiples([]), do: [] # 当列表头部是5的倍数时,拆分出后续所有非5的倍数组成当前组 def split_on_five_multiples([head | tail]) when rem(head, 5) == 0 do {group_elements, rest} = Enum.split_while(tail, fn x -> rem(x, 5) != 0 end) [[head | group_elements] | split_on_five_multiples(rest)] end # 兼容列表开头不是5的倍数的情况(你的示例里不需要,但代码更健壮) def split_on_five_multiples(list) do {prefix, rest} = Enum.split_while(list, fn x -> rem(x, 5) != 0 end) case rest do [] -> [prefix] [head | tail] -> {group_elements, remaining} = Enum.split_while(tail, fn x -> rem(x, 5) != 0 end) [[prefix | [head | group_elements]] |> List.flatten() | split_on_five_multiples(remaining)] end end end list = [300, 301, 304, 305, 306, 309, 310, 311, 312, 313, 314, 315, 316, 317, 319, 320] IO.inspect(Chunker.split_on_five_multiples(list)) # 输出和预期一致
总结
Enum.chunk_by/2适合基于相邻元素的共同属性分组,而你的需求是基于“起始标记(5的倍数)”来划分连续区间,这需要主动跟踪分组的起始点,所以用reduce或递归的方式更合适。
内容的提问来源于stack exchange,提问作者Thomas Browne
相关产品推荐
相关产品推荐

