【发布时间】:2022-01-09 02:27:43
【问题描述】:
我想知道是否有办法找出一个数组是否按顺序排列和连续。例如
arr=[1,2,3,4,5]
Arr 会返回 true,因为它是连续的和连续的
arr2=[1,2,3,4,56]
这将返回 false,因为 56 不会在 4 之后立即出现。我尝试了很多方法,但很难,因为没有循环
【问题讨论】:
标签: elixir
我想知道是否有办法找出一个数组是否按顺序排列和连续。例如
arr=[1,2,3,4,5]
Arr 会返回 true,因为它是连续的和连续的
arr2=[1,2,3,4,56]
这将返回 false,因为 56 不会在 4 之后立即出现。我尝试了很多方法,但很难,因为没有循环
【问题讨论】:
标签: elixir
当助手不像普通的旧的良好裸递归那样简单时就是这种情况。此外,这是一项很棒的练习,因为如果没有清楚地了解如何处理递归带来的问题,就无法做到elixir。
defmodule Checkers do
def seq?(input), do: do_seq?(input, nil)
defp do_seq?([], _), do: true # we are done
defp do_seq?([h | t], nil),
do: do_seq?(t, h) # entry
defp do_seq?([h | t], ah) when ah == h - 1,
do: do_seq?(t, h) # success path
defp do_seq?(_, _), do: false # ouch! we failed
end
Checkers.seq?([1, 2, 3, 4])
#⇒ true
Checkers.seq?([1, 3, 3, 4])
#⇒ false
绝对正确的Enum.reduce_while/3,由@peaceful-james 建议基本上完全符合under the hood。这个解决方案更简洁的版本是
Enum.reduce_while(arr, true, fn
curr, prev when prev == true or curr == prev + 1 ->
{:cont, curr}
_curr, _prev ->
{:halt, false}
end) # returns truthy value if seq
【讨论】:
只为记录添加一个,这与 Aleksei 的递归相当接近,但没有辅助函数和额外的参数:
def seq?([]), do: true
def seq?([_]), do: true
def seq?([i1, i2 | rest]) when i2 - i1 == 1, do: seq?([i2 | rest])
def seq?([_ | _]) , do: false
【讨论】:
def seq?(_), do: false。
FunctionClauseError,但逻辑上没有必要。
[1,2,3,4,56]
|> Enum.chunk_every(2, 1, :discard)
|> Enum.all?(fn [x,y] -> y == x + 1 end)
应该使用几个标准库函数来满足您的要求。
chunk_every 为您提供一个滑动窗口,将您的列表转换为[[1, 2], [2, 3], [3, 4], [4, 56]],为下面的下一次检查提供顺序基础。
丢弃部分是删除最后的单个元素,即[1,2,3] |> Enum.chunk_every(2, 1) == [[1, 2], [2, 3], [3]],:discard 选项将省略最后一个(单个)项目(请参阅Enum.chunk_every/3 的文档。)
然后使用Enum.all? 和给定的过滤函数检查每一对是否连续。
【讨论】:
Enum.chunk_every 替换为Stream.chunk_every 以避免中间列表。
Stream.chunk_every、Enum.chunk_every 和 Enum.reduce_while)进行了基准测试。普通递归版本大约需要 670 微秒,这是最快的。 reduce_while 版本是第二个版本,大约 2550 微秒。其他 2 个与大约 22,000 微秒相当,这比普通递归要慢得多。
您可以使用Enum.reduce,其初始值为表示{"is the list still consecutive and sequential?", "the last element processed by reduce"} 的元组{true, nil}。
arr = [1, 2, 3, 4, 5]
{is_consecutive_and_sequential, _last_element} =
Enum.reduce(arr, {true, nil}, fn
current, {true, nil} when is_integer(current) -> {true, current}
current, {true, previous} when is_integer(current) -> {current == previous + 1, current}
current, {_anything, _previous} -> {false, current}
end)
如果您的列表很长,您可以使用Enum.reduce_while 以获得更有效的解决方案:
{is_consecutive_and_sequential, _last_element} =
Enum.reduce_while(arr, {true, nil}, fn
current, {true, nil} when is_integer(current) ->
{:cont, {true, current}}
current, {true, previous} when is_integer(current) ->
if current == previous + 1,
do: {:cont, {true, current}},
else: {:halt, {false, current}}
current, {_anything, _previous} ->
{:halt, {false, current}}
end)
【讨论】:
previous,你实际上不需要在累加器中保留任何东西;一旦序列中断,您手头上已经有 :halt 返回错误值。
更新:谦虚地包含了 Aleksei 的更正
这是使用Enum.with_index的另一种方式
myList = [2,3,4,5,56]
[head | _] = myList # gets first element in the 'array'
res= Enum.with_index( myList, head) |> # so [{2,2},{3,3},{4,4},{5,5},{56,6}]
Enum.all?(&match?({x,x}, &1))
if res == 1 do
"sequential"
else
"not sequential"
end
【讨论】:
myList |> Enum.with_index(head) |> Enum.all?(&match?({x,x}, &1))
"sequential" for myList = [1,2,4,3] :)
使用标准函数(虽然可能更昂贵)
defp consecutive?(list) do
[h | tl]= d = Enum.sort(list)
d == Enum.to_list(h..List.last(tl))
end
【讨论】: