はじめに
AtCoder で使える Elixir のバージョンが 1.10.2 から 1.15.2 に上がるようです
これで表現の幅が広がりますね
以前、 @torifukukaiou さんが「Elixir 1.12で増えた関数を調べたい」という記事を書いていたので、私も ELixir 1.10 より後に追加された関数を出してみました
もちろん、 Livebook で実装しました
セットアップ
データフレームでテーブル表示、 CSV エクスポートしたいので、 KinoExplorer をインストールします
Mix.install([
{:kino, "~> 0.10"},
{:kino_explorer, "~> 0.1"}
])
モジュール一覧の取得
:code.all_loaded
で読み込まれたモジュールを全て取得できます
見やすいようにアルファベット順に並べます
:code.all_loaded
|> Enum.sort_by(&elem(&1, 0))
実行結果
[
{Access, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Access.beam"},
{Agent, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Agent.beam"},
{Agent.Server, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Agent.Server.beam"},
{Application, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Application.beam"},
{ArgumentError, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.ArgumentError.beam"},
{Base, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Base.beam"},
{CAStore,
~c"/home/livebook/.cache/mix/installs/elixir-1.15.2-erts-14.0.2/a6f4be97ce5869ecd072e376fb58b81a/_build/dev/lib/castore/ebin/Elixir.CAStore.beam"},
{CAStore.MixProject, []},
{Code, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.beam"},
{Code.Formatter, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Formatter.beam"},
{Code.Identifier, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Identifier.beam"},
{Code.Normalizer, ~c"/usr/local/lib/elixir/bin/../lib/elixir/ebin/Elixir.Code.Normalizer.beam"},
{Collectable,
~c"/home/livebook/.cache/mix/installs/elixir-1.15.2-erts-14.0.2/a6f4be97ce5869ecd072e376fb58b81a/_build/dev/lib/mix_install/consolidated/Elixir.Collectable.beam"},
...
これらには Elixir 単体で使えるモジュールだけでなく、 Livebook や Kino 、 Explorer などのモジュールも含まれているため、それらを除いて取得します
modules =
:code.all_loaded
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(&elem(&1, 0))
|> Enum.filter(fn atom ->
atom
|> Atom.to_string()
|> then(&(
String.starts_with?(&1, "Elixir.")
&& not String.starts_with?(&1, "Elixir.Kino")
&& not String.starts_with?(&1, "Elixir.Explorer")
&& not String.starts_with?(&1, "Elixir.Livebook")
&& not String.starts_with?(&1, "Elixir.Logger")
))
end)
実行結果
[Access, Agent, Agent.Server, Application, ArgumentError, Base, CAStore, CAStore.MixProject,
Calendar.ISO, Code, Code.Formatter, Code.Fragment, Code.Identifier, Code.Normalizer, Code.Typespec,
Collectable, Collectable.BitString, Collectable.File.Stream, Collectable.HashDict,
Collectable.HashSet, Collectable.IO.Stream, Collectable.List, Collectable.Map, Collectable.MapSet,
Collectable.Mix.Shell, Config.Provider, Date, DateTime, DynamicSupervisor, Enum, Enumerable,
Enumerable.Date.Range, Enumerable.Explorer.Series.Iterator, Enumerable.File.Stream,
Enumerable.Function, Enumerable.GenEvent.Stream, Enumerable.HashDict, Enumerable.HashSet,
Enumerable.IO.Stream, Enumerable.Kino.Control, Enumerable.Kino.Input, Enumerable.Kino.JS.Live,
Enumerable.List, Enumerable.Map, Enumerable.MapSet, Enumerable.Range, Enumerable.Stream,
Enumerable.Table.Mapper, Enumerable.Table.Zipper, ExUnit.Assertions, ...]
ドキュメントからの関数取得
Code.fetch_docs
にモジュールを渡すことで、そのモジュールのドキュメントを取得できます
Code.fetch_docs(List)
実行結果
{:docs_v1, 2, :elixir, "text/markdown",
%{
"en" => "Linked lists hold zero, one, or more elements in the chosen order.\n\nLists in Elixir are specified between square brackets:\n\n..."
}, %{},
[
{{:function, :ascii_printable?, 2}, 657, ["ascii_printable?(list, limit \\\\ :infinity)"],
%{
"en" => "Checks if `list` is a charlist made only of printable ASCII characters.\n\nTakes an optional `limit` as a second argument. `ascii_printable?/2` only\nchecks the printability of the list up to the `limit`.\n\n..."
}, %{since: "1.6.0", defaults: 1}},
{{:function, :delete, 2}, 133, ["delete(list, element)"],
%{
"en" => "Deletes the given `element` from the `list`. Returns a new list without\nthe element.\n\nIf the `element` occurs more than once in the `list`, just\nthe first occurrence is removed.\n\n## Examples\n\n..."
}, %{}},
{{:function, :delete_at, 2}, 846, ["delete_at(list, index)"],
%{
"en" => "Produces a new list by removing the value at the specified `index`.\n\nNegative indices indicate an offset from the end of the `list`.\nIf `index` is out of bounds, the original `list` is returned.\n\n## Examples\n\n..."
}, %{}},
...
実行結果を見ると、タプルの最後(インデックス6)に関数の一覧が入っています
また、各関数のタプルの最後(インデックス4)に since: "1.6.0"
というように、どのバージョンから追加されたのか、という情報を持っています
これを利用し、 "1.10.2" よりも後に追加された関数だけを抽出します
Code.fetch_docs(List)
|> elem(6)
|> Enum.filter(fn function ->
function
|> elem(4)
|> Map.get(:since, "0.0.0")
|> Version.compare("1.10.2")
|> Kernel.==(:gt)
end)
|> Enum.map(fn function ->
%{
function: function |> elem(2) |> Enum.at(0),
since: function |> elem(4) |> Map.get(:since)
}
end)
実行結果
[
%{function: "keyfind!(list, key, position)", since: "1.13.0"},
%{function: "keysort(list, position, sorter \\\\ :asc)", since: "1.14.0"}
]
List.keyfind!
と List.keysort
が対象として抽出されました
確かに Hexdocs でも 1.10.2 よりも後に追加されたことが確認できます
ただし、 @torifukukaiou さんの記事にあるように、 List.first/2
と List.last/2
のように、ドキュメントの since に書いていないが、実は後から追加された関数については抽出できません
あくまでもドキュメントを信用して抽出します
ここまでの条件を踏まえ、 1.10.2 よりも後に追加された関数の一覧を取得します
modules
|> Enum.map(fn module ->
case Code.fetch_docs(module) do
{:error, _} -> []
docs ->
docs
|> elem(6)
|> Enum.filter(fn function ->
function
|> elem(4)
|> Map.get(:since, "0.0.0")
|> then(fn version ->
version_length =
version
|> String.split(".")
|> length()
cond do
version_length >= 3 -> version
version_length == 2 -> "#{version}.0"
true -> "0.0.0"
end
end)
|> Version.compare("1.10.2")
|> Kernel.==(:gt)
end)
|> Enum.map(fn function ->
%{
module: module |> Atom.to_string(),
function: function |> elem(2) |> Enum.at(0),
since: function |> elem(4) |> Map.get(:since)
}
end)
end
end)
|> List.flatten()
|> Explorer.DataFrame.new()
|> Explorer.DataFrame.select([:module, :function, :since])
いくつか、 AtCoder に影響しそうなものを挙げておきます
- Enum.count_uintil
- Enum.product
- Enum.slide
- Enum.zip_reduce
- Enum.zip_with
- Float.pow
- Integer.extended_gcd
- Integer.pow
- **
- binary_slice
- ..
- first..last//step
- then
- List.keyfind!
- List.keysort
- Range.new(first, last, step)
他にも色々ありますが、とにかく then
が嬉しいのと、 first..last//step
や Range.new(first, last, step)
でステップが使えるようになるのは良いですね
Float.pow
Integer.pow
**
で累乗が分かりやすくなるのも良いです
まとめ
今までは手元で OK でもバージョンの問題で AtCoder 上でエラーになることがありました
今後はそのケースが減るのでありがたいです
早く AtCoder でも 1.15.2 で書きたい!