Elixirでmap／structの変換、たまに忘れませんか？

【本コラムは、2分で読めて、30分くらいで理解できます】
[piacere](https://twitter.com/piacere_ex)です、ご覧いただいてありがとございます :bow:

Elixir書いてると、ふと忘れてしまう構文って無いですか？

自分は、map／structの変換をちょくちょく忘れますｗ

…という訳で、そういう忘れやすいものをメモしておきます

ついでに、頻出する変換パターンもコピペ用にメモしておきます

# 〇map → struct

```elixir
struct(Range, %{first: 1, last: 10, step: 2})
struct(Range, %{})
```

# 〇struct → map

```elixir
%Range{first: 1, last: 10, step: 2} |> Map.from_struct
```

# 〇Map replace/add key-value

```elixir
%{first: 1, last: 10, step: 2} |> Map.put(:first, 2)
```

# 〇Map replace value only

```elixir
%{first: 1, last: 10, step: 2} |> Map.update!(:first, & &1 * 3)
```

# 〇String-key map → Atom-key map

```elixir:方法その1
%{"first" => 1, "last" => 10, "step" => 2} |> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
%{"first" => 1, "last" => 10, "step" => 2} |> Map.new(&{String.to_atom(elem(&1, 0)), elem(&1, 1)})
```

```elixir:方法その2
for {k, v} <- %{"first" => 1, "last" => 10, "step" => 2}, into: %{}, do: {String.to_atom(k), v}
```

```elixir:方法その3
%{"first" => 1, "last" => 10, "step" => 2} |> Jason.encode! |> Jason.decode!(keys: :atoms)
```

# 〇Atom-key map → String-key map

```elixir:方法その1
%{first: 1, last: 10, step: 2} |> Map.new(fn {k, v} -> {Atom.to_string(k), v} end)
%{first: 1, last: 10, step: 2} |> Map.new(&{Atom.to_string(elem(&1, 0)), elem(&1, 1)})
```

```elixir:方法その2
for {k, v} <- %{first: 1, last: 10, step: 2}, into: %{}, do: {Atom.to_string(k), v}
```

```elixir:方法その3
%{first: 1, last: 10, step: 2} |> Jason.encode! |> Jason.decode!
```

# 〇String-key map-list → Atom-key map-list

```elixir
[
  %{"first" => 1, "last" => 10, "step" => 2}, 
  %{"first" => 9, "last" => 4, "step" => -1}, 
] |> Enum.map(& Map.new(&1, fn {k, v} -> {String.to_atom(k), v} end))
```

# 〇Atom-key map-list → String-key map-list

```elixir
[
  %{first: 1, last: 10, step: 2}, 
  %{first: 9, last: 4, step: -1}, 
] |> Enum.map(& Map.new(&1, fn {k, v} -> {Atom.to_string(k), v} end))
```

# 〇Transpose list-list

```elixir
[
  [nil, 2.0, 3.0],
  [0.9, nil, 3.3]
] |> List.zip |> Enum.map(fn n -> Tuple.to_list(n) end)
```

# map-value-list to list-map (String-key & Atom-key)

```elixir
%{
  x: [nil, 2.0, 3.0],
  y: [0.9, nil, 3.3]
} |> then(& Enum.map(Map.values(&1) |> List.zip |> Enum.map(fn n -> Tuple.to_list(n) end), fn row -> Enum.into(List.zip([Map.keys(&1), row]), %{}) end))
```

# 〇Keyword-list list → Atom-key map-list

```elixir
[
  [first: 1, last: 10, step: 2], 
  [first: 9, last: 4, step: -1], 
] |> Enum.map(& Map.new(&1, fn {k, v} -> {k, v} end))
```

# 〇Keyword-list list → String-key map-list

```elixir
[
  [first: 1, last: 10, step: 2], 
  [first: 9, last: 4, step: -1], 
] |> Enum.map(& Map.new(&1, fn {k, v} -> {Atom.to_string(k), v} end))
```

# 〇Tuple-list → Atom-key map

```elixir
[
  {:first, 1}, 
  {:last, 10}, 
  {:step, 2}, 
] |> Enum.into(%{})
```

# Tuple-list → String-key map-list

```elixir
[
  {:first, 1}, 
  {:last, 10}, 
  {:step, 2}, 
] |> Enum.map(fn {k, v} -> {Atom.to_string(k), v} end) |> Enum.into(%{})
```

# Tuple-set-list → Atom-key map-list

```elixir
[
  {:first, 1, :last, 10, :step, 2}, 
  {:first, 9, :last, 4, :step, -1}, 
] |> Enum.map(& &1 |> Tuple.to_list |> Enum.chunk_every(2) |> Enum.map(fn n -> List.to_tuple(n) end) |> Enum.into(%{}))
```

# Tuple-set-list → String-key map-list

```elixir
[
  {:first, 1, :last, 10, :step, 2}, 
  {:first, 9, :last, 4, :step, -1}, 
] |> Enum.map(& &1 |> Tuple.to_list |> Enum.chunk_every(2) |> Enum.map(fn n -> List.to_tuple(n) end) |> Enum.into(%{}, fn {k, v} -> {Atom.to_string(k), v} end))
```

# Fill-missings from float/nil in map-value-list

```elixir
%{
  x: [nil, 2.0, 3.0],
  y: [0.9, nil, 3.3]
} |> Enum.map(fn {k, v} -> {k, v |> Enum.map(& if is_nil(&1), do: 0.0, else: &1)} end) |> Enum.into(%{})
```

# Fill-missings from string in map-value-list

```elixir:方法その1
%{
  x: ["", "hoge", "foo"],
  y: ["abc", "", "xyz"]
} |> Enum.map(fn {k, v} -> {k, v |> Enum.map(& String.replace(&1, ~r/^$/, "ddd"))} end) |> Enum.into(%{})
```

```elixir:方法その2
%{
  x: ["", "hoge", "foo"],
  y: ["abc", "", "xyz"]
} |> Enum.map(fn {k, v} -> {k, v |> Enum.map(& if &1 == "", do: "ddd", else: &1)} end) |> Enum.into(%{})
```

# Fill-missings from string/nil in map-value-list

```elixir
%{
  x: [nil, "hoge", "foo"],
  y: ["abc", "", "xyz"]
} |> Enum.map(fn {k, v} -> {k, v |> Enum.map(& if is_nil(&1) || &1 == "", do: "ddd", else: &1)} end) |> Enum.into(%{})
```

# Fill-missings from float in list-map

```elixir
[
  %{x: nil, y: 0.9},
  %{x: 2.0, y: nil},
  %{x: 3.0, y: 3.3}
] |> Enum.map(& &1 |> Enum.map(fn {k, v} -> {k, (if is_nil(v), do: 0.0, else: v)} end) |> Enum.into(%{}))
```

# Fill-missings from string in list-map

```elixir
[
  %{x: "", y: "abc"},
  %{x: "hoge", y: ""},
  %{x: "foo", y: "xyz"}
] |> Enum.map(& &1 |> Enum.map(fn {k, v} -> {k, String.replace(v, ~r/^$/, "ddd")} end) |> Enum.into(%{}))
```

# Fill-missings from string/nil in list-map

```elixir
[
  %{x: nil, y: "abc"},
  %{x: "hoge", y: ""},
  %{x: "foo", y: "xyz"}
] |> Enum.map(& &1 |> Enum.map(fn {k, v} -> {k, (if is_nil(v) || v == "", do: "ddd", else: v)} end) |> Enum.into(%{}))
```

# Float-string to float

```elixir
"12.34" |> String.replace(~r/^(?!.*\.).*$/, "\\0\.0") |> String.to_float
```

# Integer-string to float

```elixir
"123" |> String.replace(~r/^(?!.*\.).*$/, "\\0\.0") |> String.to_float
```

# Integer/Float-string to float or keep String

```elixir
v = "'abc'"
v = "\"abc\""
v = "abc"
v = "12.34"
v = "123"
v = "12ab34"
case String.match?(v, ~r/^([0-9]|\.)+$/) do
  true -> 
    case String.match?(v, ~r/\./) do
      true -> String.to_float(v)
      _    -> String.to_integer(v)
    end
  _ -> v |> String.trim("'") |> String.trim("\"")
end
```

# Fill-missings from float (mean～zscore) in list-map

```elixir
Mix.install([
  {:statistics, "~> 0.6"}
])

map_list = [
  %{x: nil, y: 0.9},
  %{x: 2.0, y: nil},
  %{x: 3.0, y: 3.3}
]
op = :mean
op = :median
op = :max
op = :min
op = :sum
op = :mode
op = :stdev
op = :skew
op = :variance
op = :zscore
fill_columns = [:y]

fill_columns
|> Enum.reduce(map_list, fn column, acc -> 
	fill = acc
		|> Enum.map(& Map.get(&1, column)) 
		|> Enum.reject(& &1 == nil) 
		|> then(& Function.capture(Statistics, op, 1).(&1))
	Enum.map(acc, & Map.update!(
		&1, column, fn v -> if v == nil, do: fill, else: v end)) end)
```

# Fill-missings in list-map by type of value

```elixir
Mix.install([
  {:statistics, "~> 0.6"}
])

map_list = [
  %{x: nil, y: 0.9},
  %{x: 2.0, y: nil},
  %{x: 3.0, y: 3.3}
]
op = :mean
op = :median
op = :max
op = :min
op = :sum
op = :mode
op = :stdev
op = :skew
op = :variance
op = :zscore
fill_columns = [:y]

fill_columns
|> Enum.reduce(map_list, fn column, acc -> 
	fill = acc
		|> Enum.map(& Map.get(&1, column)) 
		|> Enum.reject(& &1 == nil) 
		|> then(& Function.capture(Statistics, op, 1).(&1))
	Enum.map(acc, & Map.update!(
		&1, column, fn v -> if v == nil, do: fill, else: v end)) end)
```