Elixir本
やってみよう
6.5より前のものは個人的には非常に簡単だと感じたので省略します。
6.5
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
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分だけ書いたが、同じような処理なのでだるい・・
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
とか用意するのか全て内包表記でやるのかよくわからないから飛ばす。
内包表記だけでの実現は思いつかなかった・・・
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
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
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
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
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
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回で両方を実現できる手段はあるのだろうか・・?
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
本の実装を見たら、汎用的なものを作らないといけなかったようだ・・、なるほどw
with
で変数使い回すのは気づかなかった。まだまだ全然身についてないなぁ。
あとは、リストを何回も走破するのは別に悪くないんだな、という学び。