Elixir で文字列から数値に変換するときに String.to_integer/1
や String.to_float/1
を使いますが、前者は浮動小数点数値の文字列を与えると、後者は整数値の文字列を与えると例外を投げてしまいます。
iex(1)> String.to_integer("123") 123 iex(2)> String.to_integer("123.4") ** (ArgumentError) argument error :erlang.binary_to_integer("123.4") iex(2)> String.to_float("123.4") 123.4 iex(3)> String.to_float("123") ** (ArgumentError) argument error :erlang.binary_to_float("123")
例外を投げない関数として、Erlang の string
モジュールにそれぞれ同名の関数があります。こちらはパースに成功すると、成功した部分の数値とパースできなかった部分を残りの文字列のタプルを返します。
パースできる文字列がなかった場合には :error
とその理由のタプルを返します。
iex(1)> :string.to_integer("123+987") {123, "+987"} iex(2)> :string.to_float("123.4+987.6") {123.4, "+987.6"} iex(3)> :string.to_integer("abc") {:error, :no_integer} iex(4)> :string.to_float("abc") {:error, :no_float}
ここまではいいのですが。任意の数値の文字列を返す関数がなくて困りました。string:to_integer/1
に浮動小数点数値の文字列を与えると小数点の手前までしかパースしませんし、string:to_float/1
に整数値の文字列を与えるとエラーになります。
iex(1)> :string.to_integer("123.4") {123, ".4"} iex(2)> :string.to_float("123") {:error, :no_float}
対策としては。最初に string:to_float/1
でパースして失敗したら string:to_integer/1
でパースするという手があります。
defmodule MyParser do def to_number(source) do case :string.to_float(source) do {:error, _} -> :string.to_integer(source) result -> result end end end
これでだいたいうまくいきます。
iex(1)> MyParser.to_number("123") {123, ""} iex(2)> MyParser.to_number("123.4") {123.4, ""} iex(3)> MyParser.to_number("123+987") {123, "+987"} iex(4)> MyParser.to_number("123.4+987.6") {123.4, "+987.6"} iex(5) MyParser.to_number("abc") {:error, :no_integer}
ほぼ問題ないのですが、エラーのときに {:error, :no_integer}
と返ってくるのでこれを整えたい。
case
をもう一段重ねればそれなりに動きます。
defmodule MyParser do def to_number(source) do case :string.to_float(source) do {:error, _} -> case :string.to_integer(source) do {:error, _} -> {:error, :no_number} result -> result end result -> result end end end
iex(1)> MyParser.to_number("abc") {:error, :no_number}
もうちょっとどうにかならないかとライブラリを読んでいたら。Enum.reduce_while/3
という関数を見つけました。
名前の通り条件が満たされるまで第 3 引数で与える関数を適用し、満たされたら適用を中断するというものです。
第 3 引数で与える関数は 2 要素のタプルを返す必要があります。タプルの第 1 要素が :cont
だったばあいは処理を継続しタプルの第 2 要素が次の処理に引き継がれます。タプルの第 1 要素が :halt
だったばあいは処理を中断しタプルの第 2 要素が Enum.reduce_while/3
の戻り値になります。
それらをふまえて。
defmodule MyParser do def to_number(source) do [&:string.to_float/1, &:string.to_integer/1] |> Enum.reduce_while(nil, fn f, _ -> case f.(source) do {:error, _} -> {:cont, {:error, :no_number}} result -> {:halt, result} end end) end end
第 2 引数の値は利用していないので nil
を与え、適用する関数の第 2 引数も _
にしています。
実行。
iex(1)> MyParser.to_number("123") {123, ""} iex(2)> MyParser.to_number("123.4") {123.4, ""} iex(3)> MyParser.to_number("123+987") {123, "+987"} iex(4)> MyParser.to_number("123.4+987.6") {123.4, "+987.6"} iex(5)> MyParser.to_number("abc") {:error, :no_number}
今回は適用する関数がふたつだったので Enum.reduce_while/3
を利用する利点があまりありませんでしたが、適用する関数の数がもっと多くなったばあいには重宝しそうです。
いつか読むはずっと読まない:二十世紀前半のイギリスの赤毛の女性の物語
図らずしも。偶然に続けてよんだ小説が、どちらも二十世紀前半の(とはいえ一方は初頭、一方は第二次大戦時)のイギリスを舞台にした赤毛の女性が主人公の物語。
こちらは既刊が 6 巻。
- 作者: スーザン・イーリア・マクニール,圷香織
- 出版社/メーカー: 東京創元社
- 発売日: 2013/06/29
- メディア: 文庫
- この商品を含むブログ (12件) を見る
- エリザベス王女の家庭教師 (創元推理文庫)
- 国王陛下の新人スパイ (創元推理文庫)
- スパイ学校の新任教官 (創元推理文庫)
- ファーストレディの秘密のゲスト (創元推理文庫)
- バッキンガム宮殿のVIP (創元推理文庫)
こちらは全 3 巻。第 3 巻はもうじき刊行予定。
- 作者: チャーリー・N・ホームバーグ,原島文世
- 出版社/メーカー: 早川書房
- 発売日: 2017/11/07
- メディア: 文庫
- この商品を含むブログ (1件) を見る