他言語でのforループをElixirの Enum.reduce()で書いてみる Elixirで解くLeetCode - 2951. Find the Peaks

forループを Elixir の Enum.reduce()で書き換える例でも
紹介する記事を書こうと思っておりましたが、
最近の LeetCode の最近の問題で簡単な例があったので、回答例をご紹介します。

ちなみに、Enum.reduce_while()の例は、また別の記事にしよっかな...

### 2951. Find the Peaks

https://leetcode.com/problems/find-the-peaks/

問題の内容は、厳密に要件を説明すると長くなる（直訳すると長くなる）ので簡単に書きますが、配列の中でピークとなっている（隣接する要素と比べて値が大きくなっている）箇所があれば、その要素番号を配列もしくはリストに格納して返しなさい。というものです。

ちなみに、私の回答例は下記に配置しています。

https://github.com/NobuyukiInoue/LeetCode/tree/master/Problems/2900_2999/2951_Find_the_Peaks

### 1. Javaでの回答例

Elixirとの比較のために、まずはJavaやPython3での回答例

```Java:Solution.java
public class Solution {
    public List<Integer> findPeaks(int[] mountain) {
        // 1ms
        List<Integer> res = new ArrayList<>();
        for (int i = 1; i < mountain.length - 1; i++) {
            if (mountain[i - 1] < mountain[i] && mountain[i] > mountain[i + 1]) {
                res.add(i);
            }
        }
        return res;
    }
}
```

次にPython3

```Python:Find_the_Peaks.py
class Solution:
    def findPeaks(self, mountain: List[int]) -> List[int]:
        ans = []
        for i in range(1, len(mountain) - 1):
            if mountain[i - 1] < mountain[i] > mountain[i + 1]:
                ans.append(i)
        return ans
```

内包表記を使えば、１行で書けますね。

```Python:Find_the_Peaks.py
class Solution:
    def findPeaks(self, mountain: List[int]) -> List[int]:
        return [i for i in range(1, len(mountain) - 1) if mountain[i - 1] < mountain[i] > mountain[i + 1]]
```


### 2. Elixirで書いてみる１（ListをMapに変換して処理）

つぎに、Elixirに移植してみます。

基本的に、forループは Elixirでは Enum.reduce()や Enum.reduce_while()で書き換えることができますが、考慮が必要な箇所があります。

それは、ループの終了条件です。

Java など、他の言語では
```
for (int i = 1; i < 1; i++)
```

は、初回から条件式が偽になりますので、forループ内の処理は実行されませんが、

Enum.reduce()に与える第一引数はあくまでリストですので、
1..1 や、1..0 といったリストが渡される可能性を考慮する必要があります。
（今回の処理では、i-1 といったインデックス番号の参照がありますが、インデックス番号が -1 になるわけにはいかない）

```
Enum.reduce(1..n, [], fn i, res ->
```

の処理は、n = 0 の場合、

```
Enum.reduce(1..0, [], fn i, res ->
```

となってしまいます。

ですので、Enum.reduce()の処理に入る前に、
リストの終端値については先に判定しておく必要があります。

今回は、別関数を作り、Enum.reduce()の処理に入る前に、
事前にnの値（リストの要素数）をwhen句で判定させています。

ちなみに、Elixirでは Mapは
Map.get()の他に、要素名[index番号]で参照できますので、
ListよりMapの方が配列っぽく書けますね。

とりあえず、Mapを使った私の回答例です。

なお、Enum.reduce()の末尾の

```Elixir:
>| Enum.reverse()
```
は、今回の問題では順序は問われないのでやらなくても Accepted になりましたが、
逆順でリストに連結しているので、念のため付加してます。
（正順で連結しても、今回の問題では数ms程度しか遅くないようです。）

```Elixir:slution_Map.ex
# 263ms - 360ms
defmodule SolutionMap do
  @spec find_peaks(mountain :: [integer]) :: [integer]
  def find_peaks(mountain) do
    n = mountain |> Enum.count()
    map = Enum.reduce(mountain, {0, Map.new()}, fn cur, {i, map} ->
      {i + 1, Map.put(map, i, cur)}
    end) |> elem(2)
    find_peaks(map, n)
  end

  @spec find_peaks(map :: %{}, n :: integer) :: [integer]
  def find_peaks(_map, n) when n < 3 do
    []
  end

  def find_peaks(map, n) do
    Enum.reduce(1..n-2, [], fn i, res ->
      if map[i - 1] < map[i] and map[i] > map[i + 1] do
        [i] ++ res
      else
        res
      end
    end) |> Enum.reverse()
  end
end
```

### 3. Elixirで書いてみる２（ListのままEnum.at()を使用して処理）

次に、Mapに変換せずにListのまま処理した場合の回答例です。
Enum.at()の記述が冗長ですね。

今回の問題のテストケースでは、処理速度は問題にはならず、
MapでもListでもそれほど処理時間は変わりませんでしたが、

Enum.at()を多用する処理では、処理時間に差が開いてきます。

```Elixir:solution_List.ex
# 263ms - 351ms
defmodule Solution do
  @spec find_peaks(mountain :: [integer]) :: [integer]
  def find_peaks(mountain) do
    find_peaks(mountain, Enum.count(mountain))
  end

  @spec find_peaks(mountain :: [integer], n :: integer) :: [integer]
  def find_peaks(_mountain, n) when n < 3 do
    []
  end

  def find_peaks(mountain, n) do
    Enum.reduce(1..n-2, [], fn i, res ->
      if Enum.at(mountain, i - 1) < Enum.at(mountain, i) and Enum.at(mountain, i) > Enum.at(mountain, i + 1) do
        [i] ++ res
      else
        res
      end
    end) |> Enum.reverse()
  end
end
```



### 関連記事
Elixir Enum.at()がどれだけ遅いか。そしてMap
https://qiita.com/gx3n-inue/items/6f04bbc260c70c5730a2