Elixir本のやってみようをやってみる（都度更新

# Elixir本

https://www.amazon.co.jp/gp/product/4274219151

## やってみよう

6.5より前のものは個人的には非常に簡単だと感じたので省略します。

### 6.5

```ex:guess.exs
defmodule Chop do
  def guess(actual, a..b) do
    _guess(actual, a..div(b+a, 2), div(b+a, 2))
  end
  defp _guess(actual, _, actual)
    IO.puts "Is it #{actual}"
    actual
  end
  defp _guess(actual, a.._b, expected)
  when actual < expected do
    IO.puts "Is it #{expected}"
    _guess(actual, a..expected, div(expected+a, 2))
  end
  defp _guess(actual, _a..b, expected)
  when actual > expected do
    IO.puts "Is it #{expected}"
    _guess(actual, expected..b, div(b+expected, 2))
  end
end
```

hintにあるように現在の予測値をパターンマッチするhelperが必要かな？

###  6.12

```
:io_lib.print(3.14) # floatを文字列(code point?)にする
System.get_env() # 環境変数を取得する
System.cwd(),  File.cwd() # current directoryを取得する。後者は実行結果とのタプル
System.cmd("ls", ["-l"]) # シェルのコマンドを実行する
Path.extname("image.jpg") # ファイルの拡張子を取得する
https://github.com/devinus/poison # JSONをElixirの構造に変換する
```

### 7.5

```ex:MyList.exs
defmodule MyList do
  def sum([]), do: 0
  def sum([head|tail]) do
    head + sum(tail)
  end

  def mapsum([], _), do: 0
  def mapsum([head|tail], func) do
    func.(head) + mapsum(tail, func)
  end

  def max(list), do: _max(list, 0)
  defp _max([], value), do: value
  defp _max([head|tail], value)
  when head > value do
    _max(tail, head)
  end
  defp _max([head|tail], value)
  when head <= value do
    _max(tail, value)
  end

  def ceasar([], _), do: []
  def ceasar([head|tail], n)
  when head + n < 123 do
    [head + n|ceasar(tail, n)]
  end
  def ceasar([head|tail], n) do
    [head + n - 26|ceasar(tail, n)]
  end

  def span(to, to), do: [to]
  def span(from, to) when from < to, do: [from|span(from+1, to)]
  def span(from, to), do: [from|span(from-1, to)]
end
```

code pointの循環はもっといい方法がないものか・・

### 10.1

enumerableの定義方法がここまででわからないので、とりあえずrangeとlist分だけ書いたが、同じような処理なのでだるい・・

```ex:MyEnum.exs
defmodule MyEnum do
  def all?(a..b, func) when a <= b, do: func.(a) && all?(a+1..b, func)
  def all?([head|tail], func), do: func.(head) && all?(tail, func)
  def all?(_, _), do: true  

  def each(a..b, func)
  when a <= b do
    func.(a)
    each(a+1..b, func)
  end
  def each([head|tail], func) do
    func.(head)
    each(tail, func)
  end
  def each(_, _), do: :ok

  def filter(a..b, func)
  when a <= b do
    if func.(a) do
      [a | filter(a+1..b, func)]
    else
      filter(a+1..b, func)
    end
  end
  def filter([head|tail], func) do
    if func.(head) do
      [head | filter(tail, func)]
    else
      filter(tail, func)
    end
  end
  def filter(_, _), do: []

  def split(a..b, n), do: _split(a..b, n, 0, [], [])
  def split([head|tail], n), do: _split([head|tail], n, 0, [], [])

  defp _split(a..b, _, _, l1, l2) when a > b, do: [l1, l2]
  defp _split(a..b, n, i, l1, l2)
  when n > i do
    _split(a+1..b, n, i+1, l1 ++ [a], l2)
  end

  defp _split(a..b, n, i, l1, l2)
  when n <= i do
    _split(a+1..b, n, i+1, l1, l2 ++ [a])
  end

  defp _split([head|tail], n, i, l1, l2)
  when n > i do
    _split(tail, n, i+1, l1 ++ [head], l2)
  end

  defp _split([head|tail], n, i, l1, l2)
  when n <= i do
    _split(tail, n, i+1, l1, l2 ++ [head])
  end
  
  defp _split(_, _, _, l1, l2), do: [l1, l2]

  def take(a..b, n), do: _take(a..b, n, 0, [])
  def take([head|tail], n), do: _take([head|tail], n, 0, [])

  defp _take(a..b, n, i, l) when n > i, do: _take(a+1..b, n, i+1, l ++ [a])
  defp _take([head|tail], n, i, l) when n > i, do: _take(tail, n, i+1, l ++ [head])
  defp _take(_, n, i, l) when n <= i, do: l

  def flatten(list), do: _flatten(list, [])
  defp _flatten([head|tail], l), do: _flatten(head, l) ++ _flatten(tail, [])
  defp _flatten([], l), do: l
  defp _flatten(value, l), do: [value] ++ l
end
```

表示用のlistを用意するのが良いのかよくわからないがとりあえず期待値は取得できた
flattenでEnum.reverse使わなかったんだけどhelper使っちゃいけない感じなのかな・・？

### 10.4

素数は`is_prime`とか用意するのか全て内包表記でやるのかよくわからないから飛ばす。
内包表記だけでの実現は思いつかなかった・・・

```ex:comprehension.exs
defmodule Tax do
  @tax_rates [ NC: 0.075, TX: 0.08 ]
  @orders [
    [ id: 123, ship_to: :NC, net_amount: 100.00 ],
    [ id: 124, ship_to: :OK, net_amount: 35.50 ],
    [ id: 125, ship_to: :TX, net_amount: 24.00 ],
    [ id: 126, ship_to: :TX, net_amount: 44.80 ],
    [ id: 127, ship_to: :NC, net_amount: 25.00 ],
    [ id: 128, ship_to: :MA, net_amount: 10.00 ],
    [ id: 129, ship_to: :CA, net_amount: 102.00 ],
    [ id: 130, ship_to: :NC, net_amount: 50.00 ] ]
  def new_list() do
    for order <- @orders, do: _add_tax(order)
  end
  import Keyword, only: [ put: 3 ]
  defp _add_tax(order = [_, ship_to: :NC, net_amount: net]) do
    put(order, :total_amount, net * (1 + @tax_rates[:NC]))
  end
  defp _add_tax(order = [_, ship_to: :TX, net_amount: net]) do
    put(order, :total_amount, net * (1 + @tax_rates[:TX]))
  end
  defp _add_tax(order = [_, _, net_amount: net]), do: put(order, :total_amount, net)
end
```

あんまり内包表記使ってないけど良いのだろうか・・・
なんか何でもhelper病で良くない気はしているのだが。。。

### 11.3

```ex:binary.exs
defmodule Binary do
  def is_ascii([]), do: true
  def is_ascii([digit|tail])
  when 32 <= digit and digit <= 126 do
    is_ascii(tail)
  end
  def is_ascii(_), do: false

  def anagram?(word1, word2) when (length word1 !== length word2), do: false
  def anagram?(word1, word2), do: _anagram(word1, word2)
  defp _anagram([], _), do: true
  defp _anagram([head|tail], word2) do
    if _contain(head, word2) do
      _anagram(tail, word2)
    else
      false
    end 
  end

  defp _contain(char, [head|tail]) do
    if char === head do
      true
    else
      _contain(char, tail)
    end
  end
  defp _contain(_, _), do: false

  def calculate(statement), do: _calculate(statement, 0, '', 0)
  defp _calculate([head|tail], d1, op, d2)
  when head in '0123456789' and op in '+*-/' do
    _calculate(tail, d1, op, d2*10 + head - ?0)
  end
  defp _calculate([head|tail], d1, op, d2)
  when head in '0123456789' do
    _calculate(tail, d1*10 + head - ?0, op, d2)
  end
  defp _calculate([head|tail], d1, _, d2)
  when head in '+*-/' do
    _calculate(tail, d1, head, d2)
  end
  defp _calculate([head|tail], d1, op, d2) when head === 32, do: _calculate(tail, d1, op, d2)
  defp _calculate([], d1, op, d2)
  when op === 43 do
      d1 + d2
  end
  defp _calculate([], d1, op, d2)
  when op === 45 do
      d1 - d2
  end
  defp _calculate([], d1, op, d2)
  when op === 42 do
      d1 * d2
  end
  defp _calculate([], d1, op, d2)
  when op === 47 do
      d1 / d2
  end
end
```

helper関数満載！果たしてこれが良いのかはわからない・・・

### 11.5

```ex:string.exs
defmodule MyString do
  def center(list), do: _center(list, list, 0)
  defp _center([head|tail], list, max) do
    if (byte_size head) > max do 
      _center(tail, list, (byte_size head))
    else
      _center(tail, list, max)
    end
  end
  import String, only: [ pad_leading: 2, pad_trailing: 2 ]
  defp _center([], [head|tail], max) do
    if (byte_size head) === max do
      IO.puts head
    else
      IO.puts pad_trailing(pad_leading(head, (String.length head) + div(max-(byte_size head), 2)), max)
    end
    _center([], tail, max)
  end
  defp _center([], [], _), do: :ok
end
```

TODO: マルチバイト時の表示の乱れを調査

### 11.6

```ex:cap.exs
defmodule Cap do
  def capitalize_sentence(sentence) when is_binary(sentence), do: _to_upper(sentence, "")

  import String, only: [ upcase: 1 ]
  defp _to_upper(<< head :: utf8, tail :: binary >>, to_upper) do
    _capitalize_sentence(tail, (to_upper <> upcase(<<head>>)))
  end
  defp _to_upper(<<>>, to_upper), do: to_upper

  defp _capitalize_sentence(<< ".\s" :: binary, tail :: binary >>, to_upper) do
    _to_upper(tail, (to_upper <> ".\s"))
  end
  defp _capitalize_sentence(<< head :: utf8, tail :: binary >>, to_upper) do 
    _capitalize_sentence(tail, (to_upper <> <<head>>))
  end
  defp _capitalize_sentence(<<>>, to_upper), do: to_upper

  def parse_sales(fileName) do
    import String, only: [ rstrip: 1, split: 2 ]
    {:ok , file} = File.open(fileName, [:read, :utf8])
    key = IO.read(file, :line)
    keys = split(rstrip(key), ",")
    list = for line <- IO.stream(file, :line), do: rstrip(line) |> split(",") |> Enum.zip(keys) |> _process
    Tax.sales_tax(list)
  end
  import String, only: [ to_integer: 1, to_float: 1, to_atom: 1, trim_leading: 2 ]
  defp _process([{k, "id"} | tail]), do: [id: to_integer(k)] ++ _process(tail)
  defp _process([{k, "net_amount"} | tail]), do: [net_amount: to_float(k)] ++ _process(tail)
  defp _process([{k, "ship_to"} | tail]), do: [ship_to: to_atom(trim_leading(k, ":"))] ++ _process(tail)
  defp _process([]), do: []
end
```

```item.txt
id,ship_to,net_amount
123,:NC,100.00
124,:OK,35.50
125,:TX,24.00
126,:TX,44.80
127,:NC,25.00
128,:MA,10.00
129,:CA,102.00
130,:NC,50.00
```

`<< head >> <> _capitalize_sentence(tail)`がargument errorですごくハマった・・
しょうがないので新しい入れ物を用意してそれを返すようにした

atomはバイナリになると`":a"`のようになってこれを`String.to_atom`に入れると`:":a"`となる。
`String.to_atom("a")`は`:a`と期待どおりになるので、仕方なく`:`をtrimingした。
本で説明されていないことを積極的に使用していかないといけないので勉強になるな・・！

### 12.6

```ex:control.exs
defmodule Control do
  def upto(n) when n > 0 do
    1..n |> Enum.map(&fizzbazz/1)
  end
  defp fizzbazz(n) do
    case rem(n, 3) do
      0 -> case rem(n, 5) do
        0 -> "FizzBuzz"
        _ -> "Fizz"
      end
      _ -> case rem(n, 5) do
        0 -> "Buzz"
        _ -> n
      end
    end
  end

  def ok!(some) do
    case some do
      { :ok, data } -> data
      << s :: binary >> -> raise s
      [head | tail] -> raise List.to_string([head | tail])
    end
  end

  def return_tuple(n) when n > 7, do: { :ok, "success" }
  def return_tuple(n) when n <= 3 and n <= 5, do: 'error'
  def return_tuple(n), do: "error"
end
```

parameterの例外がよく分からず・・・。raizeはString(binary)しか引数に取れないのでどうするんだろ？

### 13.9

どうしてもデータ内の最大文字数が必要なので、パースと表示で2回リストを走破してしまう。
1回で両方を実現できる手段はあるのだろうか・・？

```ex:formatter.ex
defmodule Issues.TableFormatter do
  import String, only: [ pad_leading: 2, pad_trailing: 2, duplicate: 2 ]
  import Enum, only: [ join: 2, each: 2 ]
  import Integer, only: [ to_string: 2 ]

  def convert_to_table(map_lists) do 
    _convert_to_table(map_lists, [], 0, 0, 0)
    |> _print_label
    |> _print_line
    |> _print_data
  end

  defp _convert_to_table([head = %{"number" => number, "created_at" => created_at, "title" => title} | tail], list, nmax, cmax, tmax) do
    nmax = _max((String.length(to_string(number, 10))), nmax)
    cmax = _max((String.length(created_at)), cmax)
    tmax = _max((String.length(title)), tmax)
    _convert_to_table(tail, list ++ [{number, created_at, title}], nmax, cmax, tmax)
  end

  defp _convert_to_table([], list, nmax, cmax, tmax) do
    [list, [nmax: nmax+1, cmax: cmax+1, tmax: tmax+1]]
  end

  defp _max(n, candidate) when n > candidate, do: n

  defp _max(n, candidate), do: candidate

  defp _print_label([list, len]) do
    nmax = len[:nmax]
    cmax = len[:cmax]
    tmax = len[:tmax]
    nlabel = pad_trailing(pad_leading("#", div(nmax-1, 2)), nmax)
    clabel = pad_trailing("created_at", cmax)
    tlabel = pad_trailing("title", tmax)
    IO.puts join([nlabel, clabel, tlabel], "|")
    [list, len]
  end

  defp _print_line([list, len]) do
    line = for max <- Keyword.values(len), do: duplicate("-", max)
    IO.puts join(line, "+")
    [list, len]
  end

  defp _print_data([list, len]) do
    each(list, &(_print_row(&1, len)))
  end

  defp _print_row(tuple, len) do
    nmax = len[:nmax]
    cmax = len[:cmax]
    tmax = len[:tmax]
    { number, created_at, title } = tuple
    number = pad_trailing(to_string(number, 10), nmax)
    created_at = pad_trailing(created_at, cmax)
    title = pad_trailing(title, tmax)
    IO.puts join([number, created_at, title], "|")
  end
end
```

本の実装を見たら、汎用的なものを作らないといけなかったようだ・・、なるほどｗ
`with`で変数使い回すのは気づかなかった。まだまだ全然身についてないなぁ。
あとは、リストを何回も走破するのは別に悪くないんだな、という学び。