Elixir

作成日 : 2020-09-21
最終更新日 :

Elixir は並行処理を得意とする関数型言語である。

書籍

インストール

WSL2 の Ubuntu 22.04.2 LTS で動かしている。インストールは、 http://elixir-lang.org/install.html の通りである。

平方数の和問題の解法

関数型言語のプログラミングは不慣れである。 平方数の和の問題を Elixir で解くにはどうすればいいか、 その考えをメモする。Scala におけるアプローチを参考にする。 関数は、 プログラミング Elixir の 92 ページから 94 ページに書かれている「 Enum―――コレクションの処理」を主に参照し、 下記にある Elixir の公式ドキュメントも参考にした。 https://hexdocs.pm/elixir/Enum.html

まず、基本となる数の列をどのように作るかが問題である。リストで作ることでいいだろう。

さて、平方数の和の問題であるが、まずは 1 から始まって n で終わる階差 1 の等差数列を作りたい。 わざわざリテラルで [1, 2, 3, ..., n] などと手で打ち込むのはばからしい。 ただ、不慣れな言語だとどうやってリストを作ったらいいのかがまずわからない。 調べてみると、Range という種類のコレクションがあり、初項が 1 、末項が n で公差 1 の数列を 1..n のように指定できる。これを List に変換すればよさそうだ。 当面は末項の n を 7 と決め打ちする。これは、平方数の和の上限 50 と決めた結果の逆算である。 Enum は List や Range などの上位概念である。

    $ iex
    Erlang/OTP 23 [erts-11.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [hipe]

    Interactive Elixir (1.10.4) - press Ctrl+C to exit (type h() ENTER for help)
    iex> Enum.to_list 1..7
    [1, 2, 3, 4, 5, 6, 7]

平方数にするには、map を使えばよい。map の使い方もプログラミング Elixir の 92 ページにある。

    iex> seq = Enum.to_list 1..7
    [1, 2, 3, 4, 5, 6, 7]
    iex> sq = Enum.map(sq, &(&1 * &1))
    [1, 4, 9, 16, 25, 36, 49]

次に、i2 + j2 のような和の形を作らないといけない。i と j が同じであれば、 上記 sq の要素をそのまま縫合(zip)して足せばよい。

  iex> Enum.zip sq, sq
  [{1, 1}, {4, 4}, {9, 9}, {16, 16}, {25, 25}, {36, 36}, {49, 49}]
  iex> Enum.map(Enum.zip(sq, sq), &(elem(&1,0) + elem(&1,1)))
  [2, 8, 18, 32, 50, 72, 98]

うまくいっているようだ。

では、i と j の差が k のときの和を求めたい。drop が使えるだろうか。 差が 5 の場合を実験してみる。

    iex> Enum.zip sq, (Enum.drop sq, 5)
    [{1, 36}, {4, 49}]	

ここでやりたいことをまとめる。i と j の差を k とする。k = 0 のときの平方和のリスト、 k = 1 のときの平方和のリスト、…… と順次作っていって、これらのリストのリスト、 すなわち入れ子になったリストを flatten してソーティングすれば答に近づくと思う。リストの入れ子構造は Elixir では当然できると思い込んでいる。 また flatten に相当することもできると思うので省略する。

ではリストの入れ子構造をプログラムで作ろう。 まず、i2 と i のタプルをリストで表現する。

    iex> sqpair = Enum.zip sq, seq
    [{1, 1}, {4, 2}, {9, 3}, {16, 4}, {25, 5}, {36, 6}, {49, 7}]

このタプルの第 2 要素 i を、後に drop (k) の k に相当する数として活用する。 さて、Elixir では drop(k) が適用できるか、試してみる。 ただし Elixir では、Enum.drop List number の形となる。

    iex> Enum.map sqpair &(Enum.drop sq, elem(&1,1)-1 )
    [
      [1, 4, 9, 16, 25, 36, 49],
      [4, 9, 16, 25, 36, 49],
      [9, 16, 25, 36, 49],
      [16, 25, 36, 49],
      [25, 36, 49],
      '$1',
      '1'
    ]	

'$1' と表示と '1' という表示が妙に見えるが、これは文字に無理に変換してしまったからだろう。 実質は問題ないと考えて先に進む。

ここでいよいよ s = i2 + j2 となる {s, i, j} をタプルで表現する。 {s, i, j} のタプルを作るにはどうするか。 sqpair と Enum.drop sqpair k を zip してみてはどうか。

    iex> sqpaircombi = Enum.map sqpair, &(Enum.zip sqpair, (Enum.drop sqpair, elem(&1,1)-1))
    [
      [
        {{1, 1}, {1, 1}},
        {{4, 2}, {4, 2}},
        {{9, 3}, {9, 3}},
        {{16, 4}, {16, 4}},
        {{25, 5}, {25, 5}},
        {{36, 6}, {36, 6}},
        {{49, 7}, {49, 7}}
      ],
      [
        {{1, 1}, {4, 2}},
        {{4, 2}, {9, 3}},
        {{9, 3}, {16, 4}},
        {{16, 4}, {25, 5}},
        {{25, 5}, {36, 6}},
        {{36, 6}, {49, 7}}
      ],
      [
        {{1, 1}, {9, 3}},
        {{4, 2}, {16, 4}},
        {{9, 3}, {25, 5}},
        {{16, 4}, {36, 6}},
        {{25, 5}, {49, 7}}
      ],
      [{{1, 1}, {16, 4}}, {{4, 2}, {25, 5}}, {{9, 3}, {36, 6}}, {{16, 4}, {49, 7}}],
      [{{1, 1}, {25, 5}}, {{4, 2}, {36, 6}}, {{9, 3}, {49, 7}}],
      [{{1, 1}, {36, 6}}, {{4, 2}, {49, 7}}],
      [{{1, 1}, {49, 7}}]
    ]

期待した出力のように見える。あとは、いくつか処理をすればよい。

    iex> flsq = :lists.flatten sqpaircombi
    [
      {{1, 1}, {1, 1}},
      {{4, 2}, {4, 2}},
      {{9, 3}, {9, 3}},
      {{16, 4}, {16, 4}},
      {{25, 5}, {25, 5}},
      {{36, 6}, {36, 6}},
      {{49, 7}, {49, 7}},
      {{1, 1}, {4, 2}},
      {{4, 2}, {9, 3}},
      {{9, 3}, {16, 4}},
      {{16, 4}, {25, 5}},
      {{25, 5}, {36, 6}},
      {{36, 6}, {49, 7}},
      {{1, 1}, {9, 3}},
      {{4, 2}, {16, 4}},
      {{9, 3}, {25, 5}},
      {{16, 4}, {36, 6}},
      {{25, 5}, {49, 7}},
      {{1, 1}, {16, 4}},
      {{4, 2}, {25, 5}},
      {{9, 3}, {36, 6}},
      {{16, 4}, {49, 7}},
      {{1, 1}, {25, 5}},
      {{4, 2}, {36, 6}},
      {{9, 3}, {49, 7}},
      {{1, 1}, {36, 6}},
      {{4, 2}, {49, 7}},
      {{1, 1}, {49, 7}}
    ]
    > sqsumraw = Enum.map(flsq, fn {{p,j},{q,i}} -> {p+q,i,j} end)
    [
      {2, 1, 1},
      {8, 2, 2},
      {18, 3, 3},
      {32, 4, 4},
      {50, 5, 5},
      {72, 6, 6},
      {98, 7, 7},
      {5, 2, 1},
      {13, 3, 2},
      {25, 4, 3},
      {41, 5, 4},
      {61, 6, 5},
      {85, 7, 6},
      {10, 3, 1},
      {20, 4, 2},
      {34, 5, 3},
      {52, 6, 4},
      {74, 7, 5},
      {17, 4, 1},
      {29, 5, 2},
      {45, 6, 3},
      {65, 7, 4},
      {26, 5, 1},
      {40, 6, 2},
      {58, 7, 3},
      {37, 6, 1},
      {53, 7, 2},
      {50, 7, 1}
    ]

残るは filter と sortBy である。Enum.sortBy を使う予定でいたが、なんと Enum.sort だけでも大丈夫だ。

    iex> sqsum = Enum.sort (Enum.filter sqsumraw, &elem(&1,0) <= 50)
    [
      {2, 1, 1},
      {5, 2, 1},
      {8, 2, 2},
      {10, 3, 1},
      {13, 3, 2},
      {17, 4, 1},
      {18, 3, 3},
      {20, 4, 2},
      {25, 4, 3},
      {26, 5, 1},
      {29, 5, 2},
      {32, 4, 4},
      {34, 5, 3},
      {37, 6, 1},
      {40, 6, 2},
      {41, 5, 4},
      {45, 6, 3},
      {50, 5, 5},
      {50, 7, 1}
    ]

関数の書き方も、カッコを使ったり使わなかったりでスタイルの統一ができていない。 追って直すことにする。

リンク


まりんきょ学問所コンピュータの部屋コンピュータの本 > Elixir


MARUYAMA Satosi