我如何看待Elixir中的这个序列生成器?

时间:2017-10-03 21:07:15

标签: elixir

我需要生成一个这样的序列:
[1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49, 57, 65, 73, 81]

(本例中为17个数字)

算法是:
 [1, (previous + 2), (previous + 2), (previous + 2), (previous + 2), (previous + 4), (previous + 4), (previous + 4), (previous + 4) ...

因此,对于4个第一项,它是+2,然后是接下来4的+4,接下来是4的+6。每四个项增加2。

我能够在Ruby中做一个快速和hacky版本:

def sequence
  incr = 0
  (0..16).each.inject([]) do |acc, counter|
    acc << (acc.last || 1) + incr
    incr += 2 if counter.modulo(4) == 0
    acc
  end
end

但是我在Elixir做同样的问题 - 结果却是超级跛脚。像这样:

def sequence do
  { sequence, _ } =
    0..16
    |> Enum.reduce({[], 0}, fn(counter, {result, incr}) ->
      last = List.last(result)
      if last do
        result = result ++ [last + incr]
      else
        result = [1]
      end
      if rem(counter, 4) == 0 do
        incr = incr + 2
      end
      {result, incr}
    end)
  sequence
end

显然我不应该在这里强制思考,但对于这个问题我不能:D 我也确信有一种管道更加原子化的方法。

如何以Elixir的方式解决这个问题?

3 个答案:

答案 0 :(得分:5)

我用{[1], 0}启动累加器,以删除函数体中的特殊情况。通常不建议使用List.last++,因为它们效率低(O(n))。 Elixir中的惯用方法是反向构建列表并在结尾处反转列表。这意味着现在可以通过匹配列表头部的模式处理您的List.last逻辑,这很便宜。您还应该收到警告,在if中分配incr。惯用的方法是做incr = if ..., do: incr + 2, else: incr

之类的事情

以下是我如何写这个:

(0..16)
|> Enum.reduce({[1], 0}, fn counter, {[h | _] = result, incr} ->
  incr = if rem(counter, 4) == 0, do: incr + 2, else: incr
  {[h + incr | result], incr}
end)
|> elem(0)
|> Enum.reverse
|> IO.inspect

输出:

[1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49, 57, 65, 73, 81, 91]

答案 1 :(得分:2)

在这种情况下,你也可以使用一个等式:

f(n) = 1 + 2*(k+1)*(2k+j), where k = div(n/4), j = rem(n/4) 

在elixir中会是这样的:

Enum.map((0..16), fn n -> 
  k = div(n,4)
  1 + 2*(k+1)*(2*k + rem(n,4))
end)
# => [1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49, 57, 65, 73, 81]

答案 2 :(得分:0)

这是一个有趣的问题:) https://stackoverflow.com/a/46601289/24105看起来是最好的解决方案。如果你能得到某个公式,那总是最快的。但是,在这种情况下,我想看看是否还有其他解决方案。这是我的看法:

defmodule S do
  #1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49, 57, 65, 73, 81
  #So it's +2 for 4 first items, then +4 for next 4, then +6 for next 4. Increment is increased by 2 each four items.
  def gen1(n) do
    0..(n-2)
    |> Enum.reduce([1], fn x, [prev | _] = acc ->
      incr = ((div(x, 4) + 1) * 2)
      [prev + incr | acc]
    end)
    |> Enum.reverse
  end

  def gen2(n) do
    (n - 1)
    |> incr_series
    |> Enum.reduce([1], fn incr, [prev | _] = acc ->
      [prev + incr | acc]
    end)
    |> Enum.reverse
  end

  defp incr_series(n) do
    1..(div(n, 4) + 1)
    |> Enum.flat_map(fn x -> List.duplicate(x*2, 4) end)
    |> Enum.take(n)
  end

  def generate(n, algorithm), do: apply(__MODULE__, algorithm, [n])
end

ExUnit.start

defmodule AccumSeqTest do
  use ExUnit.Case, async: true

  for fun <- [:gen1, :gen2] do

    describe to_string(fun) do
      test "first 4 should increment by 2" do
        assert S.generate(3, unquote(fun)) == [1, 3, 5]
        assert S.generate(5, unquote(fun)) == [1, 3, 5, 7, 9]
      end

      test "second 4 should increment by 4" do
        assert S.generate(6, unquote(fun)) == [1, 3, 5, 7, 9, 13]
        assert S.generate(9, unquote(fun)) == [1, 3, 5, 7, 9, 13, 17, 21, 25]
      end

      test "third 4 should increment by 4" do
        assert S.generate(10, unquote(fun)) == [1, 3, 5, 7, 9, 13, 17, 21, 25, 31]
        assert S.generate(13, unquote(fun)) == [1, 3, 5, 7, 9, 13, 17, 21, 25, 31, 37, 43, 49]
      end
    end

  end
end