LoginSignup
13
0

More than 1 year has passed since last update.

【Elixir】B - FizzBuzz Sum問題をEnum.map/2 |> Enum.filter/2 |> Enum.sum/1, Enum.reduce/3 を使って解く (2021/12/02)

Last updated at Posted at 2021-12-01

2021/12/02の回です。
前日は、Nerves界で大活躍の@mnishiguchiさんによる「ElixirでEnumを使わずEnumする」でした。
本日、私もEnumの話をします。

はじめに

  • Elixirを楽しんでいますか:bangbang::bangbang::bangbang:
  • この記事は、AtCoderB - FizzBuzz Sum問題を題材に、Elixirでどのように解くのかを説明します
    • AtCoderは、オンラインで参加できるプログラミングコンテスト(競技プログラミング)のサイトです。リアルタイムのコンテストで競い合ったり、約3000問のコンテストの過去問にいつでも挑戦することが出来ます。

対象

  • これからElixirをはじめてみよう:rocket::rocket::rocket:という方に向けて書いています
  • すでに、ぼくは/私は/俺は/アタイはAlchemistだよ、という方には簡単すぎる内容ですし、説明がくどいところが多々あります
  • そういう方は、初心のころを思い出していただいて、「初心者へ向けて書くのなら、○○の説明を追加したらいいとおもうよ」という編集リクエストにてご批正を賜われば幸甚です

Elixir

  • |>でスイスイ、プログラミングしていくことができる素敵なプログラミング言語です
  • さっそくプログラムの例を示します
  • Qiita APIを使わせていただいて、Elixirタグがついた最新の記事を20件取得しています
  • ここでは雰囲気をつかんでいただければ大丈夫です
  • どうでしょうか:interrobang:
Mix.install([{:jason, "~> 1.2"}, {:httpoison, "~> 1.8"}])

"https://qiita.com/api/v2/items?query=tag:Elixir"
|> URI.encode()
|> HTTPoison.get!()
|> Map.get(:body)
|> Jason.decode!()
|> Enum.map(& Map.take(&1, ["title", "url"]))

インストール

Run :rocket::rocket::rocket:

  • iexコマンドが使えるようになっています
    • Elixir's interactive shell.
  • Elixirは1.12以上を使ってください
$ iex
Erlang/OTP 24 [erts-12.1.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [jit]

Interactive Elixir (1.12.3) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
  • さきほどのプログラム例をコピペしてみてください
  • きっと素敵な記事たちと出会えることでしょう :clap::clap_tone1::clap_tone2::clap_tone3::clap_tone4::clap_tone5:

B - FizzBuzz Sum問題

  • さていよいよ本題です
  • AtCoderB - FizzBuzz Sum問題を解いてみます
  • 問題文はリンク先をご参照ください
    • 例: 入力が15の場合、FizzBuzzの列は、1,2,Fizz,4,Buzz,Fizz,7,8,Fizz,Buzz,11,Fizz,13,14,FizzBuzzとなり、数字だけを足し算して、60が答え

プロジェクトをつくる

$ mix new fizz_buzz_sum
  • どぅわーっとファイルがたくさんできます
  • 最初は面食らうかもしれませんが、そのうち景色にみえてきます
  • そういうものだとおもってください
  • さっそくテストをしてみましょう :tada::tada::tada:
$ cd fizz_buzz_sum
$ mix test

Compiling 1 file (.ex)
Generated fizz_buzz_sum app
..

Finished in 0.06 seconds (0.00s async, 0.06s sync)
1 doctest, 1 test, 0 failures
lib/fizz_buzz_sum.ex
  @doc """
  Hello world.

  ## Examples

      iex> FizzBuzzSum.hello()
      :world

  """
  def hello do
    :world
  end

このコメントに見えるところが実は、Doctestsと呼ばれるもので、テストになっています。これを使ってプログラムを書いていきます。

fizz_buzz/1関数

  • まずはfizz_buzz/1関数を作ります
  • /1は引数が1個の意味です
lib/fizz_buzz_sum.ex
  @doc """
  fizz_buzz

  ## Examples

      iex> FizzBuzzSum.fizz_buzz(1)
      1
      iex> FizzBuzzSum.fizz_buzz(2)
      2
      iex> FizzBuzzSum.fizz_buzz(3)
      "Fizz"
      iex> FizzBuzzSum.fizz_buzz(4)
      4
      iex> FizzBuzzSum.fizz_buzz(5)
      "Buzz"
      iex> FizzBuzzSum.fizz_buzz(15)
      "FizzBuzz"

  """
  def fizz_buzz(n) do
    if rem(n, 3) == 0 and rem(n, 5) == 0 do
      "FizzBuzz"
    else
      if rem(n, 3) == 0 do
        "Fizz"
      else
        if rem(n, 5) == 0 do
          "Buzz"
        else
          n
        end
      end
    end
  end
  • rem/2は、整数同士の割り算の余りを返してくれます
  • mix testしてみましょう
    • パスします :tada::tada::tada:
  • イゴいてはいますが、実はElixirっぽくないです
  • はじめての方にはなんのことだかわからないとおもいますのでElixirっぽい書き方とはどういうことなのかを実際に書き換えたものを示します
  • ここからというかちょっと前からの説明はギアがチェンジしたというか、説明が大雑把になってきたというか、いろいろ説明を端折っています
  • 細かいことはいいんです、四の五の言わずにまずはイゴかしてみましょう
  • 感じてください
    • 君はコスモを感じたことがあるか:interrobang::interrobang::interrobang:
lib/fizz_buzz_sum.ex
  def fizz_buzz(n) do
    do_fizz_buzz(n, rem(n, 3), rem(n, 5))
  end

  defp do_fizz_buzz(_n, 0, 0), do: "FizzBuzz"

  defp do_fizz_buzz(_n, 0, _), do: "Fizz"

  defp do_fizz_buzz(_n, _, 0), do: "Buzz"

  defp do_fizz_buzz(n, _, _), do: n
  • テストがあることで安心して書き換えることができます
  • Pattern matchingと呼ばれる書き方を使い、if/2を排除すると、グッとElixirっぽい書き方になります

fizz_buzz_sum/1関数 -- その1

  • 続いて与えたれたN項目までに含まれる数の和を計算するfizz_buzz_sum/1関数を作ります
  • ListやRangeなどEnumerablesを操作する関数が集まったモジュールをElixirでは、Enumモジュールと言います
  • AtCoderの問題は、このEnumモジュールを使うか前日の@mnishiguchi さんの記事のように再帰を使うかすると解答を導けることが多いです
  • この記事では、Enumモジュールを使って解く方法を説明します
lib/fizz_buzz_sum.ex
  @doc """
  fizz_buzz_sum

  ## Examples

      iex> FizzBuzzSum.fizz_buzz_sum(15)
      60
      iex> FizzBuzzSum.fizz_buzz_sum(1000000)
      266666333332

  """
  def fizz_buzz_sum(n) do
    1..n
    |> Enum.map(&fizz_buzz/1)
    |> Enum.filter(&is_integer/1)
    |> Enum.sum()
  end
  • 1..nRangeです
    • [1,2,3,...,n]みたいな〜 ものだとおもってください
  • |>
    • 前の計算結果を次の関数の第1引数に入れてくれます
  • Enum.map/2
    • 与えたれたenumerable(リストやRangeやMap)の各要素に、第2引数で指定した関数を用いて演算を施して結果を返してくれます
    • 与えたれたenumerableの要素数と同じ要素数のenumerableが返ってきます
  • Enum.filter/2
    • 与えたれたenumerable(リストやRangeやMap)の各要素に、第2引数で指定した関数を用いて、truthyな要素のみを残したenumerableを返します
    • truthyとは、nilfalse以外です
    • 与えられたenumerableの要素数がNだとすると、結果は0個〜N個のenumerableになります
  • Enum.sum/1
    • 与えられたenumerableの各要素を足し算した結果を返します

もし|>が、Elixirになかったらこんな感じになります。

lib/fizz_buzz_sum.ex
  def fizz_buzz_sum(n) do
    Enum.sum(Enum.filter(Enum.map(1..n, &fizz_buzz/1), &is_integer/1))
  end

どちらがお好みでしょうか。
人の好みはそれぞれですしあなたの好みまでかえる気はありませんが、|>を使った書き方の方を美しいと感じる方は、きっとよき$\huge{Alchemist}$になれるでしょう

fizz_buzz_sum/1関数 -- その2

  • B - FizzBuzz Sum問題は、その1のプログラムでパスできます
  • mapして、filterして、足し算してという3ステップを踏んでいることが気になる方がいらっしゃるでしょう
    • きっとよき$\huge{Alchemist}$になれるでしょう
  • Enum.reduce/3を使った書き方をご紹介しておきます
lib/fizz_buzz_sum.ex
  def fizz_buzz_sum(n) do
    1..n
    |> Enum.reduce(0, fn i, acc ->
      fizz_buzz(i)
      |> to_i()
      |> Kernel.+(acc)
    end)
  end

  defp to_i("Fizz"), do: 0

  defp to_i("Buzz"), do: 0

  defp to_i("FizzBuzz"), do: 0

  defp to_i(i), do: i
  • Enum.reduce/3を使ったほうがループの回数が減るので、AtCoderの提出環境においては、約100ms弱速くなります :race_car: :fire:

main/0関数

  • 入力を読み取って |> 計算して |> 出力する というのが競技プログラミングのスタイルです
  • その方法については、「AtCoderをElixirでやってみる」 に詳しく書いています
  • プログラム全体を示します
lib/fizz_buzz_sum.ex
defmodule FizzBuzzSum do
  def main do
    IO.read(:line)
    |> String.trim()
    |> String.to_integer()
    |> fizz_buzz_sum()
    |> IO.puts()
  end

  @doc """
  fizz_buzz

  ## Examples

      iex> FizzBuzzSum.fizz_buzz(1)
      1
      iex> FizzBuzzSum.fizz_buzz(2)
      2
      iex> FizzBuzzSum.fizz_buzz(3)
      "Fizz"
      iex> FizzBuzzSum.fizz_buzz(4)
      4
      iex> FizzBuzzSum.fizz_buzz(5)
      "Buzz"
      iex> FizzBuzzSum.fizz_buzz(15)
      "FizzBuzz"

  """
  def fizz_buzz(n) do
    do_fizz_buzz(n, rem(n, 3), rem(n, 5))
  end

  defp do_fizz_buzz(_n, 0, 0), do: "FizzBuzz"

  defp do_fizz_buzz(_n, 0, _), do: "Fizz"

  defp do_fizz_buzz(_n, _, 0), do: "Buzz"

  defp do_fizz_buzz(n, _, _), do: n

  @doc """
  fizz_buzz_sum

  ## Examples

      iex> FizzBuzzSum.fizz_buzz_sum(15)
      60
      iex> FizzBuzzSum.fizz_buzz_sum(1000000)
      266666333332

  """
  def fizz_buzz_sum(n) do
    1..n
    |> Enum.reduce(0, fn i, acc ->
      fizz_buzz(i)
      |> to_i()
      |> Kernel.+(acc)
    end)
  end

  defp to_i("Fizz"), do: 0

  defp to_i("Buzz"), do: 0

  defp to_i("FizzBuzz"), do: 0

  defp to_i(i), do: i
end
  • 提出の際には、モジュール名をMainとして提出してください
  • パスします :tada::tada::tada:

整形

  • ソースコードはキレイにしておきましょう
  • 整形してくれます
$ mix format

Wrapping up :qiitan::lgtm::xmas-tree::santa::santa_tone1::santa_tone2::santa_tone3::santa_tone4::santa_tone5::lgtm::xmas-tree::qiitan:

  • Enjoy Elixir :bangbang::bangbang:
  • Elixirのプログラムには、|>がよくでてきます
  • |>を使ってスイスイ、プログラミングしていくことができます
  • Elixirをはじめたばかりの方は、Enumモジュールを眺めておくとよいです
    • EnumモジュールはElixirのプログラムでよく使います
  • この記事を読んでくださったあなたは、きっとよき$\huge{Alchemist}$になれるでしょう

コミュニティ

FCOvBkAUYAE6mL8.jpeg

(@piacerex さん作 :pray::pray_tone1::pray_tone2::pray_tone3::pray_tone4::pray_tone5:)

もっとElixirのことを知りたい方へオススメの書籍 :books:

この記事を最後まで読んでくださったあなたは、きっとよき$\huge{Alchemist}$になれるでしょう


明日は、@iyanayatudazeさんによる「iexで関数のドキュメントを調べる方法 他3本」です。
お楽しみに〜〜〜:tada::tada::tada:


  1. 「早速動かしてみましょう」の意。おそらく、西日本の方言、たぶん。NervesJPではおなじみ。少し古いオートレースの映像ですが、実況アナウンサーが「針2イゴきます」とはっきり言っています。https://autorace.jp/netstadium/SearchMovie/Movie/iizuka?date=2017-01-04&p=5&race_number=11&pg=  

  2. 大時計の針のこと。針がイゴいてある地点まで到達すると選手はスタートを切って良い発走の合図。針がイゴきはじめると(おそらく)選手は緊張するし、スタートはその後のレース展開に大きく影響するので、車券を握りしめている観客たちがもっとも緊張する瞬間であるため、先の尖った鋭いものを連想させる針は緊張の暗喩としても言い得て妙。 

13
0
1

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
13
0