【问题标题】:Break a comprehension打破理解
【发布时间】:2018-09-20 03:08:18
【问题描述】:

有没有办法打断理解?

我正在考虑类似 Python 的 for-break-else。 例如,假设我正在遍历一个整数列表,当我从中生成一个新列表时,我想检查它是否不包含 0

我可以这样做吗:

for x <- [1, 2, 0, 3] do
    case x do
        0 -> break
        _ -> x
    end
end
if broke do
    :error
else
    :ok
end

我想我想得太迫切了,正确的函数方法应该是这样的,带有递归和累加器:

def traverse_and_check(list, acc \\ []) do
    case list do
        [] -> {:ok, reverse(acc)}
        [0 | _tail] -> :error
        [x | tail] -> traverse_and_check(tail, [x | acc])
    end
end

Elixir 有这样的控制流程吗?

【问题讨论】:

  • 我能想到的唯一函数是 Enum.reduce_while 但 IMO 它会不如你的递归解决方案那么优雅。不过,我会将case 转换为traverse_and_check/2 声明中的3 个子句。
  • @Dogbert 啊,你说得对,我对 Elixir 还是很陌生。我也发现递归方法更优雅,但来自 Python 世界,for-else 起初在我看来是优雅的方法......虽然它似乎违背了 Elixir 的哲学,即错误不是控制流(好吧,从技术上讲,break 不是错误,但它是“例外情况”)。不过,感谢您指出Enum.reduce_while
  • 我会说你试图让理解做一些它不应该做的事情。理解旨在处理传递给它的集合中的所有元素。如果你需要跳出一个坏元素,那么要么 a.) 在开始理解之前过滤掉任何可能的坏元素,要么 b.) 编写递归代码,这是处理循环的 FP 方式。

标签: elixir list-comprehension control-flow


【解决方案1】:

如果您对返回列表中收集的部分不感兴趣,Elixir 正是为此目的提供了throw/catch

在 Elixir 中,一个值可以被抛出然后被捕获。 throwcatch 保留用于无法检索值的情况,除非使用 throwcatch

这些情况在实践中非常少见,除非与不提供适当 API 的库进行交互。

我认为这种情况是“不提供适当 API 的库 [这里-Elixir]”的完美示例 :)

所以你去:

try do
  for x <- [1, 2, 0, 3],
    do: if x == 0, do: throw(:break), else: x
catch
  :break -> :broken
end
#⇒ :broken

请注意,throw/catch 用于控制流量,与 raise/rescue 不同。

【讨论】:

  • 我会用这个。我绝对没有想过在这里使用 throw/catch!
  • @Dogbert 我不确定;使用reduce_while 可能会伪造ruby 的break acc 功能,返回已经收集的元素throwing 来自推导无法访问累加器。
  • 是的,但是如果 OP 不想要部分列表,这是一个很好的中断方式。
  • 谢谢,这正是我想要的。我刚刚提出了相同的解决方案,但使用了raise。当然,throw/catch 是正确的做法。
【解决方案2】:

您可以使用Enum.reduce_while/3,但在我看来,该解决方案比递归更糟糕,因为它需要一个case 后缀来减少非破坏案例的列表。下面的 sn-p 包括 reduce_while 实现和基于您的代码的改进递归解决方案:

defmodule A do
  def f(list) do
    Enum.reduce_while(list, [], fn x, acc ->
      if x == 0, do: {:halt, :break}, else: {:cont, [x | acc]}
    end)
    |> case do
      :break -> :break
      list -> Enum.reverse(list)
    end
  end

  def g(list, acc \\ [])

  def g([], acc), do: Enum.reverse(acc)
  def g([0 | _], _), do: :break
  def g([x | xs], acc), do: g(xs, [x | acc])
end

IO.inspect(A.f([1, 2, 3]))
IO.inspect(A.f([1, 2, 0, 3]))
IO.inspect(A.g([1, 2, 3]))
IO.inspect(A.g([1, 2, 0, 3]))

输出:

[1, 2, 3]
:break
[1, 2, 3]
:break

【讨论】:

    【解决方案3】:

    查看入门手册的comprehensions 页面。

    Comprehensions 有可选的过滤器,您可以使用这些过滤器来代替标准的过滤器/归约函数。试试这样的:

    for x <- [1, 2, 0, 3], x != 0, into: [] do: x
    

    该文档有一些更好的示例,但这应该适用于您从推导式列表中删除零的用例。

    【讨论】:

    • 谢谢,但问题不在于过滤掉某些元素,而是在实现某个条件时中断迭代,以及捕捉突破.
    • @Rightleg,糟糕,抱歉,我误解了这个问题。
    猜你喜欢
    • 2017-01-28
    • 1970-01-01
    • 2018-10-07
    • 2016-11-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多