LoginSignup
12
0

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

Last updated at Posted at 2023-12-13

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

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

2951. Find the Peaks

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

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

1. Javaでの回答例

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

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

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

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

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で書いてみる1(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()の末尾の

>| Enum.reverse()

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

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で書いてみる2(ListのままEnum.at()を使用して処理)

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

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

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

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

12
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
0