Elixir Credo - 如何使用一个 Enum.reject/2 而不是 Enum.reject/2 |> Enum.reject/2

Elixir Credo - How to use one Enum.reject/2 instead of Enum.reject/2 |> Enum.reject/2

如果我收到像下面这样的 credo-refactoring opportunities 警告,我该怎么办?

One `Enum.reject/2` is more efficient than `Enum.reject/2 |> Enum.reject/2`

例如,如何使用 one Enum.reject/2 而不是 Enum.reject/2 |> Enum.reject/2 重新编码以下函数?

  def my_reject_test() do
    (1..10)
    |> Enum.reject(&(rem(&1, 2) == 0))
    |> Enum.reject(&(rem(&1, 3) == 0))
  end

一个简单的方法是:

(1..10)
|> Enum.reject(fn x -> rem(x, 2) == 0 || rem(x, 3) == 0 end)
|> IO.inspect()

对于更复杂的场景,我有时最终会使用 Enum.reduce/3,或者考虑添加一个命名的私有函数来执行逻辑(为了可读性)。

这是一个很好的提醒,遍历列表可能会很昂贵(对于长列表),因此您应该尽可能减少遍历它们的次数。使用 Enum.reject/2 两次使算法复杂度为 O(2n),而仅使用一次使其保持在 O(n) 复杂度。

可以用or/2组合条件:

iex> Enum.reject(1..10, &(rem(&1, 2) == 0 or rem(&1, 3) == 0))
[1, 5, 7]

credo 之所以指出,是因为管道两个 Enum.reject/2 会先生成一个中间列表,然后再遍历一遍:

iex> 1..10
1..10
iex> |> Enum.reject(&(rem(&1, 2) == 0))
[1, 3, 5, 7, 9]
iex> |> Enum.reject(&(rem(&1, 3) == 0))
[1, 5, 7]