View Source Selecting Objects From Tables
Mix.install(
[
{:matcha, github: "christhekeele/matcha", tag: "stable"},
{:jason, ">= 0.0.1"},
{:finch, ">= 0.0.1"}
],
force: true
)
Finch.start_link(name: Pokemon.Data)
IO.puts("Installed matcha version: #{Application.spec(:matcha, :vsn)}")
Resolving Hex dependencies...
Dependency resolution completed:
New:
castore 0.1.19
finch 0.13.0
hpax 0.1.2
jason 1.4.0
mime 2.0.3
mint 1.4.2
nimble_options 0.4.0
nimble_pool 0.2.6
recon 2.5.2
telemetry 1.1.0
* Getting jason (Hex package)
* Getting finch (Hex package)
* Getting castore (Hex package)
* Getting mime (Hex package)
* Getting mint (Hex package)
* Getting nimble_options (Hex package)
* Getting nimble_pool (Hex package)
* Getting telemetry (Hex package)
* Getting hpax (Hex package)
* Getting recon (Hex package)
==> mime
Compiling 1 file (.ex)
Generated mime app
==> nimble_options
Compiling 3 files (.ex)
Generated nimble_options app
===> Analyzing applications...
===> Compiling telemetry
==> jason
Compiling 10 files (.ex)
Generated jason app
==> recon
Compiling 6 files (.erl)
Generated recon app
==> hpax
Compiling 4 files (.ex)
Generated hpax app
==> nimble_pool
Compiling 2 files (.ex)
Generated nimble_pool app
==> matcha
Compiling 18 files (.ex)
Generated matcha app
==> castore
Compiling 1 file (.ex)
Generated castore app
==> mint
Compiling 1 file (.erl)
Compiling 19 files (.ex)
Generated mint app
==> finch
Compiling 13 files (.ex)
Generated finch app
Installed matcha version: 0.1.5
:ok
what-are-tables
What Are Tables?
seeding-some-data
Seeding Some Data
Before we can really get cooking using Matcha
to extract data from an :ets
table, we're going to need to put some data in a table!
For fun example datasets, I like to use data about Pokémon. There's a lot of them, and they're fairly well-known — Pokémon being one of the top highest-grossing franchises in human history.
We've already created an :ets
table called Pokemon.Data
in our setup. We've also nabbed the Finch
library to make HTTP requests; and Jason
to parse JSON datasets into Elixir datastructures.
Now we just need a dataset — we'll use @fanzeyi
's data here.
{:ok, response} =
Finch.build(:get, "https://raw.githubusercontent.com/fanzeyi/pokemon.json/master/pokedex.json")
|> Finch.request(Pokemon.Data)
data =
response
|> Map.fetch!(:body)
|> Jason.decode!()
[
%{
"base" => %{
"Attack" => 49,
"Defense" => 49,
"HP" => 45,
"Sp. Attack" => 65,
"Sp. Defense" => 65,
"Speed" => 45
},
"id" => 1,
"name" => %{
"chinese" => "妙蛙种子",
"english" => "Bulbasaur",
"french" => "Bulbizarre",
"japanese" => "フシギダネ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 62,
"Defense" => 63,
"HP" => 60,
"Sp. Attack" => 80,
"Sp. Defense" => 80,
"Speed" => 60
},
"id" => 2,
"name" => %{
"chinese" => "妙蛙草",
"english" => "Ivysaur",
"french" => "Herbizarre",
"japanese" => "フシギソウ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 82,
"Defense" => 83,
"HP" => 80,
"Sp. Attack" => 100,
"Sp. Defense" => 100,
"Speed" => 80
},
"id" => 3,
"name" => %{
"chinese" => "妙蛙花",
"english" => "Venusaur",
"french" => "Florizarre",
"japanese" => "フシギバナ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 52,
"Defense" => 43,
"HP" => 39,
"Sp. Attack" => 60,
"Sp. Defense" => 50,
"Speed" => 65
},
"id" => 4,
"name" => %{
"chinese" => "小火龙",
"english" => "Charmander",
"french" => "Salamèche",
"japanese" => "ヒトカゲ"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 64,
"Defense" => 58,
"HP" => 58,
"Sp. Attack" => 80,
"Sp. Defense" => 65,
"Speed" => 80
},
"id" => 5,
"name" => %{
"chinese" => "火恐龙",
"english" => "Charmeleon",
"french" => "Reptincel",
"japanese" => "リザード"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 84,
"Defense" => 78,
"HP" => 78,
"Sp. Attack" => 109,
"Sp. Defense" => 85,
"Speed" => 100
},
"id" => 6,
"name" => %{
"chinese" => "喷火龙",
"english" => "Charizard",
"french" => "Dracaufeu",
"japanese" => "リザードン"
},
"type" => ["Fire", "Flying"]
},
%{
"base" => %{
"Attack" => 48,
"Defense" => 65,
"HP" => 44,
"Sp. Attack" => 50,
"Sp. Defense" => 64,
"Speed" => 43
},
"id" => 7,
"name" => %{
"chinese" => "杰尼龟",
"english" => "Squirtle",
"french" => "Carapuce",
"japanese" => "ゼニガメ"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 63,
"Defense" => 80,
"HP" => 59,
"Sp. Attack" => 65,
"Sp. Defense" => 80,
"Speed" => 58
},
"id" => 8,
"name" => %{
"chinese" => "卡咪龟",
"english" => "Wartortle",
"french" => "Carabaffe",
"japanese" => "カメール"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 83,
"Defense" => 100,
"HP" => 79,
"Sp. Attack" => 85,
"Sp. Defense" => 105,
"Speed" => 78
},
"id" => 9,
"name" => %{
"chinese" => "水箭龟",
"english" => "Blastoise",
"french" => "Tortank",
"japanese" => "カメックス"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 30,
"Defense" => 35,
"HP" => 45,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 45
},
"id" => 10,
"name" => %{
"chinese" => "绿毛虫",
"english" => "Caterpie",
"french" => "Chenipan",
"japanese" => "キャタピー"
},
"type" => ["Bug"]
},
%{
"base" => %{
"Attack" => 20,
"Defense" => 55,
"HP" => 50,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 30
},
"id" => 11,
"name" => %{
"chinese" => "铁甲蛹",
"english" => "Metapod",
"french" => "Chrysacier",
"japanese" => "トランセル"
},
"type" => ["Bug"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 50,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 70
},
"id" => 12,
"name" => %{
"chinese" => "巴大蝶",
"english" => "Butterfree",
"french" => "Papilusion",
"japanese" => "バタフリー"
},
"type" => ["Bug", "Flying"]
},
%{
"base" => %{
"Attack" => 35,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 50
},
"id" => 13,
"name" => %{
"chinese" => "独角虫",
"english" => "Weedle",
"french" => "Aspicot",
"japanese" => "ビードル"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 25,
"Defense" => 50,
"HP" => 45,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 35
},
"id" => 14,
"name" => %{
"chinese" => "铁壳蛹",
"english" => "Kakuna",
"french" => "Coconfort",
"japanese" => "コクーン"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 40,
"HP" => 65,
"Sp. Attack" => 45,
"Sp. Defense" => 80,
"Speed" => 75
},
"id" => 15,
"name" => %{
"chinese" => "大针蜂",
"english" => "Beedrill",
"french" => "Dardargnan",
"japanese" => "スピアー"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 40,
"HP" => 40,
"Sp. Attack" => 35,
"Sp. Defense" => 35,
"Speed" => 56
},
"id" => 16,
"name" => %{
"chinese" => "波波",
"english" => "Pidgey",
"french" => "Roucool",
"japanese" => "ポッポ"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 55,
"HP" => 63,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 71
},
"id" => 17,
"name" => %{
"chinese" => "比比鸟",
"english" => "Pidgeotto",
"french" => "Roucoups",
"japanese" => "ピジョン"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 80,
"Defense" => 75,
"HP" => 83,
"Sp. Attack" => 70,
"Sp. Defense" => 70,
"Speed" => 101
},
"id" => 18,
"name" => %{
"chinese" => "大比鸟",
"english" => "Pidgeot",
"french" => "Roucarnage",
"japanese" => "ピジョット"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 56,
"Defense" => 35,
"HP" => 30,
"Sp. Attack" => 25,
"Sp. Defense" => 35,
"Speed" => 72
},
"id" => 19,
"name" => %{
"chinese" => "小拉达",
"english" => "Rattata",
"french" => "Rattata",
"japanese" => "コラッタ"
},
"type" => ["Normal"]
},
%{
"base" => %{
"Attack" => 81,
"Defense" => 60,
"HP" => 55,
"Sp. Attack" => 50,
"Sp. Defense" => 70,
"Speed" => 97
},
"id" => 20,
"name" => %{
"chinese" => "拉达",
"english" => "Raticate",
"french" => "Rattatac",
"japanese" => "ラッタ"
},
"type" => ["Normal"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 31,
"Sp. Defense" => 31,
"Speed" => 70
},
"id" => 21,
"name" => %{
"chinese" => "烈雀",
"english" => "Spearow",
"french" => "Piafabec",
"japanese" => "オニスズメ"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 65,
"HP" => 65,
"Sp. Attack" => 61,
"Sp. Defense" => 61,
"Speed" => 100
},
"id" => 22,
"name" => %{
"chinese" => "大嘴雀",
"english" => "Fearow",
"french" => "Rapasdepic",
"japanese" => "オニドリル"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 44,
"HP" => 35,
"Sp. Attack" => 40,
"Sp. Defense" => 54,
"Speed" => 55
},
"id" => 23,
"name" => %{
"chinese" => "阿柏蛇",
"english" => "Ekans",
"french" => "Abo",
"japanese" => "アーボ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 95,
"Defense" => 69,
"HP" => 60,
"Sp. Attack" => 65,
"Sp. Defense" => 79,
"Speed" => 80
},
"id" => 24,
"name" => %{
"chinese" => "阿柏怪",
"english" => "Arbok",
"french" => "Arbok",
"japanese" => "アーボック"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 55,
"Defense" => 40,
"HP" => 35,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 90
},
"id" => 25,
"name" => %{
"chinese" => "皮卡丘",
"english" => "Pikachu",
"french" => "Pikachu",
"japanese" => "ピカチュウ"
},
"type" => ["Electric"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 55,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 110
},
"id" => 26,
"name" => %{
"chinese" => "雷丘",
"english" => "Raichu",
"french" => "Raichu",
"japanese" => "ライチュウ"
},
"type" => ["Electric"]
},
%{
"base" => %{
"Attack" => 75,
"Defense" => 85,
"HP" => 50,
"Sp. Attack" => 20,
"Sp. Defense" => 30,
"Speed" => 40
},
"id" => 27,
"name" => %{
"chinese" => "穿山鼠",
"english" => "Sandshrew",
"french" => "Sabelette",
"japanese" => "サンド"
},
"type" => ["Ground"]
},
%{
"base" => %{
"Attack" => 100,
"Defense" => 110,
"HP" => 75,
"Sp. Attack" => 45,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 28,
"name" => %{
"chinese" => "穿山王",
"english" => "Sandslash",
"french" => "Sablaireau",
"japanese" => "サンドパン"
},
"type" => ["Ground"]
},
%{
"base" => %{
"Attack" => 47,
"Defense" => 52,
"HP" => 55,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 41
},
"id" => 29,
"name" => %{
"chinese" => "尼多兰",
"english" => "Nidoran♀",
"french" => "Nidoran♀",
"japanese" => "ニドラン♀"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 62,
"Defense" => 67,
"HP" => 70,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 56
},
"id" => 30,
"name" => %{
"chinese" => "尼多娜",
"english" => "Nidorina",
"french" => "Nidorina",
"japanese" => "ニドリーナ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 92,
"Defense" => 87,
"HP" => 90,
"Sp. Attack" => 75,
"Sp. Defense" => 85,
"Speed" => 76
},
"id" => 31,
"name" => %{
"chinese" => "尼多后",
"english" => "Nidoqueen",
"french" => "Nidoqueen",
"japanese" => "ニドクイン"
},
"type" => ["Poison", "Ground"]
},
%{
"base" => %{
"Attack" => 57,
"Defense" => 40,
"HP" => 46,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 50
},
"id" => 32,
"name" => %{
"chinese" => "尼多朗",
"english" => "Nidoran♂",
"french" => "Nidoran♂",
"japanese" => "ニドラン♂"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 72,
"Defense" => 57,
"HP" => 61,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 33,
"name" => %{
"chinese" => "尼多力诺",
"english" => "Nidorino",
"french" => "Nidorino",
"japanese" => "ニドリーノ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 102,
"Defense" => 77,
"HP" => 81,
"Sp. Attack" => 85,
"Sp. Defense" => 75,
"Speed" => 85
},
"id" => 34,
"name" => %{
"chinese" => "尼多王",
"english" => "Nidoking",
"french" => "Nidoking",
"japanese" => "ニドキング"
},
"type" => ["Poison", "Ground"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 48,
"HP" => 70,
"Sp. Attack" => 60,
"Sp. Defense" => 65,
"Speed" => 35
},
"id" => 35,
"name" => %{
"chinese" => "皮皮",
"english" => "Clefairy",
"french" => "Mélofée",
"japanese" => "ピッピ"
},
"type" => ["Fairy"]
},
%{
"base" => %{
"Attack" => 70,
"Defense" => 73,
"HP" => 95,
"Sp. Attack" => 95,
"Sp. Defense" => 90,
"Speed" => 60
},
"id" => 36,
"name" => %{
"chinese" => "皮可西",
"english" => "Clefable",
"french" => "Mélodelfe",
"japanese" => "ピクシー"
},
"type" => ["Fairy"]
},
%{
"base" => %{
"Attack" => 41,
"Defense" => 40,
"HP" => 38,
"Sp. Attack" => 50,
"Sp. Defense" => 65,
"Speed" => 65
},
"id" => 37,
"name" => %{
"chinese" => "六尾",
"english" => "Vulpix",
"french" => "Goupix",
"japanese" => "ロコン"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 76,
"Defense" => 75,
"HP" => 73,
"Sp. Attack" => 81,
"Sp. Defense" => 100,
"Speed" => 100
},
"id" => 38,
"name" => %{
"chinese" => "九尾",
"english" => "Ninetales",
"french" => "Feunard",
"japanese" => "キュウコン"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 20,
"HP" => 115,
"Sp. Attack" => 45,
"Sp. Defense" => 25,
"Speed" => 20
},
"id" => 39,
"name" => %{
"chinese" => "胖丁",
"english" => "Jigglypuff",
"french" => "Rondoudou",
"japanese" => "プリン"
},
"type" => ["Normal", "Fairy"]
},
%{
"base" => %{
"Attack" => 70,
"Defense" => 45,
"HP" => 140,
"Sp. Attack" => 85,
"Sp. Defense" => 50,
"Speed" => 45
},
"id" => 40,
"name" => %{
"chinese" => "胖可丁",
"english" => "Wigglytuff",
"french" => "Grodoudou",
"japanese" => "プクリン"
},
"type" => ["Normal", "Fairy"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 35,
"HP" => 40,
"Sp. Attack" => 30,
"Sp. Defense" => 40,
"Speed" => 55
},
"id" => 41,
"name" => %{
"chinese" => "超音蝠",
"english" => "Zubat",
"french" => "Nosferapti",
"japanese" => "ズバット"
},
"type" => ["Poison", "Flying"]
},
%{
"base" => %{
"Attack" => 80,
"Defense" => 70,
"HP" => 75,
"Sp. Attack" => 65,
"Sp. Defense" => 75,
"Speed" => 90
},
"id" => 42,
"name" => %{
"chinese" => "大嘴蝠",
"english" => "Golbat",
"french" => "Nosferalto",
"japanese" => "ゴルバット"
},
"type" => ["Poison", "Flying"]
},
%{
"base" => %{
"Attack" => 50,
"Defense" => 55,
"HP" => 45,
"Sp. Attack" => 75,
"Sp. Defense" => 65,
"Speed" => 30
},
"id" => 43,
"name" => %{
"chinese" => "走路草",
"english" => "Oddish",
"french" => "Mystherbe",
"japanese" => "ナゾノクサ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 65,
"Defense" => 70,
"HP" => 60,
"Sp. Attack" => 85,
"Sp. Defense" => 75,
...
},
"id" => 44,
"name" => %{"chinese" => "臭臭花", "english" => "Gloom", "french" => "Ortide", ...},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{"Attack" => 80, "Defense" => 85, "HP" => 75, "Sp. Attack" => 110, ...},
"id" => 45,
"name" => %{"chinese" => "霸王花", "english" => "Vileplume", ...},
"type" => ["Grass", ...]
},
%{
"base" => %{"Attack" => 70, "Defense" => 55, "HP" => 35, ...},
"id" => 46,
"name" => %{"chinese" => "派拉斯", ...},
"type" => [...]
},
%{"base" => %{"Attack" => 95, "Defense" => 80, ...}, "id" => 47, "name" => %{...}, ...},
%{"base" => %{"Attack" => 55, ...}, "id" => 48, ...},
%{"base" => %{...}, ...},
%{...},
...
]
:ets
objects take the form of an n-tuple, with (usually) the first item in the tuple being the "key" we recognize the object by. We'll massage our dataset to fit this structure: in our case, {id, pokemon_data}
, extracting each pokemon's numeric "id" as our key from the pokemon_data
to index it by:
pokedex_objects =
data
|> Enum.map(fn pokemon_data -> {Map.fetch!(pokemon_data, "id"), pokemon_data} end)
|> Enum.sort_by(&elem(&1, 0))
[
{1,
%{
"base" => %{
"Attack" => 49,
"Defense" => 49,
"HP" => 45,
"Sp. Attack" => 65,
"Sp. Defense" => 65,
"Speed" => 45
},
"id" => 1,
"name" => %{
"chinese" => "妙蛙种子",
"english" => "Bulbasaur",
"french" => "Bulbizarre",
"japanese" => "フシギダネ"
},
"type" => ["Grass", "Poison"]
}},
{2,
%{
"base" => %{
"Attack" => 62,
"Defense" => 63,
"HP" => 60,
"Sp. Attack" => 80,
"Sp. Defense" => 80,
"Speed" => 60
},
"id" => 2,
"name" => %{
"chinese" => "妙蛙草",
"english" => "Ivysaur",
"french" => "Herbizarre",
"japanese" => "フシギソウ"
},
"type" => ["Grass", "Poison"]
}},
{3,
%{
"base" => %{
"Attack" => 82,
"Defense" => 83,
"HP" => 80,
"Sp. Attack" => 100,
"Sp. Defense" => 100,
"Speed" => 80
},
"id" => 3,
"name" => %{
"chinese" => "妙蛙花",
"english" => "Venusaur",
"french" => "Florizarre",
"japanese" => "フシギバナ"
},
"type" => ["Grass", "Poison"]
}},
{4,
%{
"base" => %{
"Attack" => 52,
"Defense" => 43,
"HP" => 39,
"Sp. Attack" => 60,
"Sp. Defense" => 50,
"Speed" => 65
},
"id" => 4,
"name" => %{
"chinese" => "小火龙",
"english" => "Charmander",
"french" => "Salamèche",
"japanese" => "ヒトカゲ"
},
"type" => ["Fire"]
}},
{5,
%{
"base" => %{
"Attack" => 64,
"Defense" => 58,
"HP" => 58,
"Sp. Attack" => 80,
"Sp. Defense" => 65,
"Speed" => 80
},
"id" => 5,
"name" => %{
"chinese" => "火恐龙",
"english" => "Charmeleon",
"french" => "Reptincel",
"japanese" => "リザード"
},
"type" => ["Fire"]
}},
{6,
%{
"base" => %{
"Attack" => 84,
"Defense" => 78,
"HP" => 78,
"Sp. Attack" => 109,
"Sp. Defense" => 85,
"Speed" => 100
},
"id" => 6,
"name" => %{
"chinese" => "喷火龙",
"english" => "Charizard",
"french" => "Dracaufeu",
"japanese" => "リザードン"
},
"type" => ["Fire", "Flying"]
}},
{7,
%{
"base" => %{
"Attack" => 48,
"Defense" => 65,
"HP" => 44,
"Sp. Attack" => 50,
"Sp. Defense" => 64,
"Speed" => 43
},
"id" => 7,
"name" => %{
"chinese" => "杰尼龟",
"english" => "Squirtle",
"french" => "Carapuce",
"japanese" => "ゼニガメ"
},
"type" => ["Water"]
}},
{8,
%{
"base" => %{
"Attack" => 63,
"Defense" => 80,
"HP" => 59,
"Sp. Attack" => 65,
"Sp. Defense" => 80,
"Speed" => 58
},
"id" => 8,
"name" => %{
"chinese" => "卡咪龟",
"english" => "Wartortle",
"french" => "Carabaffe",
"japanese" => "カメール"
},
"type" => ["Water"]
}},
{9,
%{
"base" => %{
"Attack" => 83,
"Defense" => 100,
"HP" => 79,
"Sp. Attack" => 85,
"Sp. Defense" => 105,
"Speed" => 78
},
"id" => 9,
"name" => %{
"chinese" => "水箭龟",
"english" => "Blastoise",
"french" => "Tortank",
"japanese" => "カメックス"
},
"type" => ["Water"]
}},
{10,
%{
"base" => %{
"Attack" => 30,
"Defense" => 35,
"HP" => 45,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 45
},
"id" => 10,
"name" => %{
"chinese" => "绿毛虫",
"english" => "Caterpie",
"french" => "Chenipan",
"japanese" => "キャタピー"
},
"type" => ["Bug"]
}},
{11,
%{
"base" => %{
"Attack" => 20,
"Defense" => 55,
"HP" => 50,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 30
},
"id" => 11,
"name" => %{
"chinese" => "铁甲蛹",
"english" => "Metapod",
"french" => "Chrysacier",
"japanese" => "トランセル"
},
"type" => ["Bug"]
}},
{12,
%{
"base" => %{
"Attack" => 45,
"Defense" => 50,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 70
},
"id" => 12,
"name" => %{
"chinese" => "巴大蝶",
"english" => "Butterfree",
"french" => "Papilusion",
"japanese" => "バタフリー"
},
"type" => ["Bug", "Flying"]
}},
{13,
%{
"base" => %{
"Attack" => 35,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 50
},
"id" => 13,
"name" => %{
"chinese" => "独角虫",
"english" => "Weedle",
"french" => "Aspicot",
"japanese" => "ビードル"
},
"type" => ["Bug", "Poison"]
}},
{14,
%{
"base" => %{
"Attack" => 25,
"Defense" => 50,
"HP" => 45,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 35
},
"id" => 14,
"name" => %{
"chinese" => "铁壳蛹",
"english" => "Kakuna",
"french" => "Coconfort",
"japanese" => "コクーン"
},
"type" => ["Bug", "Poison"]
}},
{15,
%{
"base" => %{
"Attack" => 90,
"Defense" => 40,
"HP" => 65,
"Sp. Attack" => 45,
"Sp. Defense" => 80,
"Speed" => 75
},
"id" => 15,
"name" => %{
"chinese" => "大针蜂",
"english" => "Beedrill",
"french" => "Dardargnan",
"japanese" => "スピアー"
},
"type" => ["Bug", "Poison"]
}},
{16,
%{
"base" => %{
"Attack" => 45,
"Defense" => 40,
"HP" => 40,
"Sp. Attack" => 35,
"Sp. Defense" => 35,
"Speed" => 56
},
"id" => 16,
"name" => %{
"chinese" => "波波",
"english" => "Pidgey",
"french" => "Roucool",
"japanese" => "ポッポ"
},
"type" => ["Normal", "Flying"]
}},
{17,
%{
"base" => %{
"Attack" => 60,
"Defense" => 55,
"HP" => 63,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 71
},
"id" => 17,
"name" => %{
"chinese" => "比比鸟",
"english" => "Pidgeotto",
"french" => "Roucoups",
"japanese" => "ピジョン"
},
"type" => ["Normal", "Flying"]
}},
{18,
%{
"base" => %{
"Attack" => 80,
"Defense" => 75,
"HP" => 83,
"Sp. Attack" => 70,
"Sp. Defense" => 70,
"Speed" => 101
},
"id" => 18,
"name" => %{
"chinese" => "大比鸟",
"english" => "Pidgeot",
"french" => "Roucarnage",
"japanese" => "ピジョット"
},
"type" => ["Normal", "Flying"]
}},
{19,
%{
"base" => %{
"Attack" => 56,
"Defense" => 35,
"HP" => 30,
"Sp. Attack" => 25,
"Sp. Defense" => 35,
"Speed" => 72
},
"id" => 19,
"name" => %{
"chinese" => "小拉达",
"english" => "Rattata",
"french" => "Rattata",
"japanese" => "コラッタ"
},
"type" => ["Normal"]
}},
{20,
%{
"base" => %{
"Attack" => 81,
"Defense" => 60,
"HP" => 55,
"Sp. Attack" => 50,
"Sp. Defense" => 70,
"Speed" => 97
},
"id" => 20,
"name" => %{
"chinese" => "拉达",
"english" => "Raticate",
"french" => "Rattatac",
"japanese" => "ラッタ"
},
"type" => ["Normal"]
}},
{21,
%{
"base" => %{
"Attack" => 60,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 31,
"Sp. Defense" => 31,
"Speed" => 70
},
"id" => 21,
"name" => %{
"chinese" => "烈雀",
"english" => "Spearow",
"french" => "Piafabec",
"japanese" => "オニスズメ"
},
"type" => ["Normal", "Flying"]
}},
{22,
%{
"base" => %{
"Attack" => 90,
"Defense" => 65,
"HP" => 65,
"Sp. Attack" => 61,
"Sp. Defense" => 61,
"Speed" => 100
},
"id" => 22,
"name" => %{
"chinese" => "大嘴雀",
"english" => "Fearow",
"french" => "Rapasdepic",
"japanese" => "オニドリル"
},
"type" => ["Normal", "Flying"]
}},
{23,
%{
"base" => %{
"Attack" => 60,
"Defense" => 44,
"HP" => 35,
"Sp. Attack" => 40,
"Sp. Defense" => 54,
"Speed" => 55
},
"id" => 23,
"name" => %{
"chinese" => "阿柏蛇",
"english" => "Ekans",
"french" => "Abo",
"japanese" => "アーボ"
},
"type" => ["Poison"]
}},
{24,
%{
"base" => %{
"Attack" => 95,
"Defense" => 69,
"HP" => 60,
"Sp. Attack" => 65,
"Sp. Defense" => 79,
"Speed" => 80
},
"id" => 24,
"name" => %{
"chinese" => "阿柏怪",
"english" => "Arbok",
"french" => "Arbok",
"japanese" => "アーボック"
},
"type" => ["Poison"]
}},
{25,
%{
"base" => %{
"Attack" => 55,
"Defense" => 40,
"HP" => 35,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 90
},
"id" => 25,
"name" => %{
"chinese" => "皮卡丘",
"english" => "Pikachu",
"french" => "Pikachu",
"japanese" => "ピカチュウ"
},
"type" => ["Electric"]
}},
{26,
%{
"base" => %{
"Attack" => 90,
"Defense" => 55,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 110
},
"id" => 26,
"name" => %{
"chinese" => "雷丘",
"english" => "Raichu",
"french" => "Raichu",
"japanese" => "ライチュウ"
},
"type" => ["Electric"]
}},
{27,
%{
"base" => %{
"Attack" => 75,
"Defense" => 85,
"HP" => 50,
"Sp. Attack" => 20,
"Sp. Defense" => 30,
"Speed" => 40
},
"id" => 27,
"name" => %{
"chinese" => "穿山鼠",
"english" => "Sandshrew",
"french" => "Sabelette",
"japanese" => "サンド"
},
"type" => ["Ground"]
}},
{28,
%{
"base" => %{
"Attack" => 100,
"Defense" => 110,
"HP" => 75,
"Sp. Attack" => 45,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 28,
"name" => %{
"chinese" => "穿山王",
"english" => "Sandslash",
"french" => "Sablaireau",
"japanese" => "サンドパン"
},
"type" => ["Ground"]
}},
{29,
%{
"base" => %{
"Attack" => 47,
"Defense" => 52,
"HP" => 55,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 41
},
"id" => 29,
"name" => %{
"chinese" => "尼多兰",
"english" => "Nidoran♀",
"french" => "Nidoran♀",
"japanese" => "ニドラン♀"
},
"type" => ["Poison"]
}},
{30,
%{
"base" => %{
"Attack" => 62,
"Defense" => 67,
"HP" => 70,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 56
},
"id" => 30,
"name" => %{
"chinese" => "尼多娜",
"english" => "Nidorina",
"french" => "Nidorina",
"japanese" => "ニドリーナ"
},
"type" => ["Poison"]
}},
{31,
%{
"base" => %{
"Attack" => 92,
"Defense" => 87,
"HP" => 90,
"Sp. Attack" => 75,
"Sp. Defense" => 85,
"Speed" => 76
},
"id" => 31,
"name" => %{
"chinese" => "尼多后",
"english" => "Nidoqueen",
"french" => "Nidoqueen",
"japanese" => "ニドクイン"
},
"type" => ["Poison", "Ground"]
}},
{32,
%{
"base" => %{
"Attack" => 57,
"Defense" => 40,
"HP" => 46,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 50
},
"id" => 32,
"name" => %{
"chinese" => "尼多朗",
"english" => "Nidoran♂",
"french" => "Nidoran♂",
"japanese" => "ニドラン♂"
},
"type" => ["Poison"]
}},
{33,
%{
"base" => %{
"Attack" => 72,
"Defense" => 57,
"HP" => 61,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 33,
"name" => %{
"chinese" => "尼多力诺",
"english" => "Nidorino",
"french" => "Nidorino",
"japanese" => "ニドリーノ"
},
"type" => ["Poison"]
}},
{34,
%{
"base" => %{
"Attack" => 102,
"Defense" => 77,
"HP" => 81,
"Sp. Attack" => 85,
"Sp. Defense" => 75,
"Speed" => 85
},
"id" => 34,
"name" => %{
"chinese" => "尼多王",
"english" => "Nidoking",
"french" => "Nidoking",
"japanese" => "ニドキング"
},
"type" => ["Poison", "Ground"]
}},
{35,
%{
"base" => %{
"Attack" => 45,
"Defense" => 48,
"HP" => 70,
"Sp. Attack" => 60,
"Sp. Defense" => 65,
"Speed" => 35
},
"id" => 35,
"name" => %{
"chinese" => "皮皮",
"english" => "Clefairy",
"french" => "Mélofée",
"japanese" => "ピッピ"
},
"type" => ["Fairy"]
}},
{36,
%{
"base" => %{
"Attack" => 70,
"Defense" => 73,
"HP" => 95,
"Sp. Attack" => 95,
"Sp. Defense" => 90,
"Speed" => 60
},
"id" => 36,
"name" => %{
"chinese" => "皮可西",
"english" => "Clefable",
"french" => "Mélodelfe",
"japanese" => "ピクシー"
},
"type" => ["Fairy"]
}},
{37,
%{
"base" => %{
"Attack" => 41,
"Defense" => 40,
"HP" => 38,
"Sp. Attack" => 50,
"Sp. Defense" => 65,
"Speed" => 65
},
"id" => 37,
"name" => %{
"chinese" => "六尾",
"english" => "Vulpix",
"french" => "Goupix",
"japanese" => "ロコン"
},
"type" => ["Fire"]
}},
{38,
%{
"base" => %{
"Attack" => 76,
"Defense" => 75,
"HP" => 73,
"Sp. Attack" => 81,
"Sp. Defense" => 100,
"Speed" => 100
},
"id" => 38,
"name" => %{
"chinese" => "九尾",
"english" => "Ninetales",
"french" => "Feunard",
"japanese" => "キュウコン"
},
"type" => ["Fire"]
}},
{39,
%{
"base" => %{
"Attack" => 45,
"Defense" => 20,
"HP" => 115,
"Sp. Attack" => 45,
"Sp. Defense" => 25,
"Speed" => 20
},
"id" => 39,
"name" => %{
"chinese" => "胖丁",
"english" => "Jigglypuff",
"french" => "Rondoudou",
"japanese" => "プリン"
},
"type" => ["Normal", "Fairy"]
}},
{40,
%{
"base" => %{
"Attack" => 70,
"Defense" => 45,
"HP" => 140,
"Sp. Attack" => 85,
"Sp. Defense" => 50,
"Speed" => 45
},
"id" => 40,
"name" => %{
"chinese" => "胖可丁",
"english" => "Wigglytuff",
"french" => "Grodoudou",
"japanese" => "プクリン"
},
"type" => ["Normal", "Fairy"]
}},
{41,
%{
"base" => %{
"Attack" => 45,
"Defense" => 35,
"HP" => 40,
"Sp. Attack" => 30,
"Sp. Defense" => 40,
"Speed" => 55
},
"id" => 41,
"name" => %{
"chinese" => "超音蝠",
"english" => "Zubat",
"french" => "Nosferapti",
"japanese" => "ズバット"
},
"type" => ["Poison", "Flying"]
}},
{42,
%{
"base" => %{
"Attack" => 80,
"Defense" => 70,
"HP" => 75,
"Sp. Attack" => 65,
"Sp. Defense" => 75,
...
},
"id" => 42,
"name" => %{"chinese" => "大嘴蝠", "english" => "Golbat", "french" => "Nosferalto", ...},
"type" => ["Poison", "Flying"]
}},
{43,
%{
"base" => %{"Attack" => 50, "Defense" => 55, "HP" => 45, "Sp. Attack" => 75, ...},
"id" => 43,
"name" => %{"chinese" => "走路草", "english" => "Oddish", ...},
"type" => ["Grass", ...]
}},
{44,
%{
"base" => %{"Attack" => 65, "Defense" => 70, "HP" => 60, ...},
"id" => 44,
"name" => %{"chinese" => "臭臭花", ...},
"type" => [...]
}},
{45, %{"base" => %{"Attack" => 80, "Defense" => 85, ...}, "id" => 45, "name" => %{...}, ...}},
{46, %{"base" => %{"Attack" => 70, ...}, "id" => 46, ...}},
{47, %{"base" => %{...}, ...}},
{48, %{...}},
{49, ...},
{...},
...
]
Now we're ready to insert these records into an :ets
table we'll call the Pokedex
(a Pokédex is the in-game term for a registry of known Pokémon). We'll make a new table of type :set
, to indicate that each object is unique by key:
pokedex = :ets.new(Pokedex, [:set])
:ets.insert(pokedex, pokedex_objects)
IO.puts("Loaded #{:ets.info(pokedex, :size)} pokémon into the `Pokedex` table.")
Loaded 809 pokémon into the `Pokedex` table.
:ok
mapping-and-filtering-without-matcha
Mapping And Filtering Without Matcha
Let's start off using the basic :ets
APIs to retrieve data from our table.
The :ets.lookup/2
function lets us get our objects by their key — the first element of our tuples. For example, we can check out what the 500th Pokémon is:
:ets.lookup(pokedex, 500)
|> List.first()
|> elem(1)
|> get_in(["name", "english"])
"Emboar"
The 500th Pokémon appears to be some creature called Emboar. This scares and confuses me, as I am an elder millenial who never moved on from the 1st generation of 151 Pokémon, and refuse to believe anything has changed from my childhood.
How might we go about getting only Pokémon from the first generation, to soothe my anxiety? While we can use simple :ets
APIs to get specific objects by key, there isn't an easy way to ask it to give us all objects with a key less than 152...
filtering
Filtering
We could, of course, load all of our table's data into memory, and filter out later generation pokemon there:
first_gen_pokemon =
:ets.tab2list(pokedex)
|> Enum.filter(fn {id, _pokemon} -> id >= 1 and id <= 151 end)
|> Enum.sort_by(&elem(&1, 0))
|> Enum.map(&elem(&1, 1))
[
%{
"base" => %{
"Attack" => 49,
"Defense" => 49,
"HP" => 45,
"Sp. Attack" => 65,
"Sp. Defense" => 65,
"Speed" => 45
},
"id" => 1,
"name" => %{
"chinese" => "妙蛙种子",
"english" => "Bulbasaur",
"french" => "Bulbizarre",
"japanese" => "フシギダネ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 62,
"Defense" => 63,
"HP" => 60,
"Sp. Attack" => 80,
"Sp. Defense" => 80,
"Speed" => 60
},
"id" => 2,
"name" => %{
"chinese" => "妙蛙草",
"english" => "Ivysaur",
"french" => "Herbizarre",
"japanese" => "フシギソウ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 82,
"Defense" => 83,
"HP" => 80,
"Sp. Attack" => 100,
"Sp. Defense" => 100,
"Speed" => 80
},
"id" => 3,
"name" => %{
"chinese" => "妙蛙花",
"english" => "Venusaur",
"french" => "Florizarre",
"japanese" => "フシギバナ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 52,
"Defense" => 43,
"HP" => 39,
"Sp. Attack" => 60,
"Sp. Defense" => 50,
"Speed" => 65
},
"id" => 4,
"name" => %{
"chinese" => "小火龙",
"english" => "Charmander",
"french" => "Salamèche",
"japanese" => "ヒトカゲ"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 64,
"Defense" => 58,
"HP" => 58,
"Sp. Attack" => 80,
"Sp. Defense" => 65,
"Speed" => 80
},
"id" => 5,
"name" => %{
"chinese" => "火恐龙",
"english" => "Charmeleon",
"french" => "Reptincel",
"japanese" => "リザード"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 84,
"Defense" => 78,
"HP" => 78,
"Sp. Attack" => 109,
"Sp. Defense" => 85,
"Speed" => 100
},
"id" => 6,
"name" => %{
"chinese" => "喷火龙",
"english" => "Charizard",
"french" => "Dracaufeu",
"japanese" => "リザードン"
},
"type" => ["Fire", "Flying"]
},
%{
"base" => %{
"Attack" => 48,
"Defense" => 65,
"HP" => 44,
"Sp. Attack" => 50,
"Sp. Defense" => 64,
"Speed" => 43
},
"id" => 7,
"name" => %{
"chinese" => "杰尼龟",
"english" => "Squirtle",
"french" => "Carapuce",
"japanese" => "ゼニガメ"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 63,
"Defense" => 80,
"HP" => 59,
"Sp. Attack" => 65,
"Sp. Defense" => 80,
"Speed" => 58
},
"id" => 8,
"name" => %{
"chinese" => "卡咪龟",
"english" => "Wartortle",
"french" => "Carabaffe",
"japanese" => "カメール"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 83,
"Defense" => 100,
"HP" => 79,
"Sp. Attack" => 85,
"Sp. Defense" => 105,
"Speed" => 78
},
"id" => 9,
"name" => %{
"chinese" => "水箭龟",
"english" => "Blastoise",
"french" => "Tortank",
"japanese" => "カメックス"
},
"type" => ["Water"]
},
%{
"base" => %{
"Attack" => 30,
"Defense" => 35,
"HP" => 45,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 45
},
"id" => 10,
"name" => %{
"chinese" => "绿毛虫",
"english" => "Caterpie",
"french" => "Chenipan",
"japanese" => "キャタピー"
},
"type" => ["Bug"]
},
%{
"base" => %{
"Attack" => 20,
"Defense" => 55,
"HP" => 50,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 30
},
"id" => 11,
"name" => %{
"chinese" => "铁甲蛹",
"english" => "Metapod",
"french" => "Chrysacier",
"japanese" => "トランセル"
},
"type" => ["Bug"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 50,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 70
},
"id" => 12,
"name" => %{
"chinese" => "巴大蝶",
"english" => "Butterfree",
"french" => "Papilusion",
"japanese" => "バタフリー"
},
"type" => ["Bug", "Flying"]
},
%{
"base" => %{
"Attack" => 35,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 20,
"Sp. Defense" => 20,
"Speed" => 50
},
"id" => 13,
"name" => %{
"chinese" => "独角虫",
"english" => "Weedle",
"french" => "Aspicot",
"japanese" => "ビードル"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 25,
"Defense" => 50,
"HP" => 45,
"Sp. Attack" => 25,
"Sp. Defense" => 25,
"Speed" => 35
},
"id" => 14,
"name" => %{
"chinese" => "铁壳蛹",
"english" => "Kakuna",
"french" => "Coconfort",
"japanese" => "コクーン"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 40,
"HP" => 65,
"Sp. Attack" => 45,
"Sp. Defense" => 80,
"Speed" => 75
},
"id" => 15,
"name" => %{
"chinese" => "大针蜂",
"english" => "Beedrill",
"french" => "Dardargnan",
"japanese" => "スピアー"
},
"type" => ["Bug", "Poison"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 40,
"HP" => 40,
"Sp. Attack" => 35,
"Sp. Defense" => 35,
"Speed" => 56
},
"id" => 16,
"name" => %{
"chinese" => "波波",
"english" => "Pidgey",
"french" => "Roucool",
"japanese" => "ポッポ"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 55,
"HP" => 63,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 71
},
"id" => 17,
"name" => %{
"chinese" => "比比鸟",
"english" => "Pidgeotto",
"french" => "Roucoups",
"japanese" => "ピジョン"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 80,
"Defense" => 75,
"HP" => 83,
"Sp. Attack" => 70,
"Sp. Defense" => 70,
"Speed" => 101
},
"id" => 18,
"name" => %{
"chinese" => "大比鸟",
"english" => "Pidgeot",
"french" => "Roucarnage",
"japanese" => "ピジョット"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 56,
"Defense" => 35,
"HP" => 30,
"Sp. Attack" => 25,
"Sp. Defense" => 35,
"Speed" => 72
},
"id" => 19,
"name" => %{
"chinese" => "小拉达",
"english" => "Rattata",
"french" => "Rattata",
"japanese" => "コラッタ"
},
"type" => ["Normal"]
},
%{
"base" => %{
"Attack" => 81,
"Defense" => 60,
"HP" => 55,
"Sp. Attack" => 50,
"Sp. Defense" => 70,
"Speed" => 97
},
"id" => 20,
"name" => %{
"chinese" => "拉达",
"english" => "Raticate",
"french" => "Rattatac",
"japanese" => "ラッタ"
},
"type" => ["Normal"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 30,
"HP" => 40,
"Sp. Attack" => 31,
"Sp. Defense" => 31,
"Speed" => 70
},
"id" => 21,
"name" => %{
"chinese" => "烈雀",
"english" => "Spearow",
"french" => "Piafabec",
"japanese" => "オニスズメ"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 65,
"HP" => 65,
"Sp. Attack" => 61,
"Sp. Defense" => 61,
"Speed" => 100
},
"id" => 22,
"name" => %{
"chinese" => "大嘴雀",
"english" => "Fearow",
"french" => "Rapasdepic",
"japanese" => "オニドリル"
},
"type" => ["Normal", "Flying"]
},
%{
"base" => %{
"Attack" => 60,
"Defense" => 44,
"HP" => 35,
"Sp. Attack" => 40,
"Sp. Defense" => 54,
"Speed" => 55
},
"id" => 23,
"name" => %{
"chinese" => "阿柏蛇",
"english" => "Ekans",
"french" => "Abo",
"japanese" => "アーボ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 95,
"Defense" => 69,
"HP" => 60,
"Sp. Attack" => 65,
"Sp. Defense" => 79,
"Speed" => 80
},
"id" => 24,
"name" => %{
"chinese" => "阿柏怪",
"english" => "Arbok",
"french" => "Arbok",
"japanese" => "アーボック"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 55,
"Defense" => 40,
"HP" => 35,
"Sp. Attack" => 50,
"Sp. Defense" => 50,
"Speed" => 90
},
"id" => 25,
"name" => %{
"chinese" => "皮卡丘",
"english" => "Pikachu",
"french" => "Pikachu",
"japanese" => "ピカチュウ"
},
"type" => ["Electric"]
},
%{
"base" => %{
"Attack" => 90,
"Defense" => 55,
"HP" => 60,
"Sp. Attack" => 90,
"Sp. Defense" => 80,
"Speed" => 110
},
"id" => 26,
"name" => %{
"chinese" => "雷丘",
"english" => "Raichu",
"french" => "Raichu",
"japanese" => "ライチュウ"
},
"type" => ["Electric"]
},
%{
"base" => %{
"Attack" => 75,
"Defense" => 85,
"HP" => 50,
"Sp. Attack" => 20,
"Sp. Defense" => 30,
"Speed" => 40
},
"id" => 27,
"name" => %{
"chinese" => "穿山鼠",
"english" => "Sandshrew",
"french" => "Sabelette",
"japanese" => "サンド"
},
"type" => ["Ground"]
},
%{
"base" => %{
"Attack" => 100,
"Defense" => 110,
"HP" => 75,
"Sp. Attack" => 45,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 28,
"name" => %{
"chinese" => "穿山王",
"english" => "Sandslash",
"french" => "Sablaireau",
"japanese" => "サンドパン"
},
"type" => ["Ground"]
},
%{
"base" => %{
"Attack" => 47,
"Defense" => 52,
"HP" => 55,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 41
},
"id" => 29,
"name" => %{
"chinese" => "尼多兰",
"english" => "Nidoran♀",
"french" => "Nidoran♀",
"japanese" => "ニドラン♀"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 62,
"Defense" => 67,
"HP" => 70,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 56
},
"id" => 30,
"name" => %{
"chinese" => "尼多娜",
"english" => "Nidorina",
"french" => "Nidorina",
"japanese" => "ニドリーナ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 92,
"Defense" => 87,
"HP" => 90,
"Sp. Attack" => 75,
"Sp. Defense" => 85,
"Speed" => 76
},
"id" => 31,
"name" => %{
"chinese" => "尼多后",
"english" => "Nidoqueen",
"french" => "Nidoqueen",
"japanese" => "ニドクイン"
},
"type" => ["Poison", "Ground"]
},
%{
"base" => %{
"Attack" => 57,
"Defense" => 40,
"HP" => 46,
"Sp. Attack" => 40,
"Sp. Defense" => 40,
"Speed" => 50
},
"id" => 32,
"name" => %{
"chinese" => "尼多朗",
"english" => "Nidoran♂",
"french" => "Nidoran♂",
"japanese" => "ニドラン♂"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 72,
"Defense" => 57,
"HP" => 61,
"Sp. Attack" => 55,
"Sp. Defense" => 55,
"Speed" => 65
},
"id" => 33,
"name" => %{
"chinese" => "尼多力诺",
"english" => "Nidorino",
"french" => "Nidorino",
"japanese" => "ニドリーノ"
},
"type" => ["Poison"]
},
%{
"base" => %{
"Attack" => 102,
"Defense" => 77,
"HP" => 81,
"Sp. Attack" => 85,
"Sp. Defense" => 75,
"Speed" => 85
},
"id" => 34,
"name" => %{
"chinese" => "尼多王",
"english" => "Nidoking",
"french" => "Nidoking",
"japanese" => "ニドキング"
},
"type" => ["Poison", "Ground"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 48,
"HP" => 70,
"Sp. Attack" => 60,
"Sp. Defense" => 65,
"Speed" => 35
},
"id" => 35,
"name" => %{
"chinese" => "皮皮",
"english" => "Clefairy",
"french" => "Mélofée",
"japanese" => "ピッピ"
},
"type" => ["Fairy"]
},
%{
"base" => %{
"Attack" => 70,
"Defense" => 73,
"HP" => 95,
"Sp. Attack" => 95,
"Sp. Defense" => 90,
"Speed" => 60
},
"id" => 36,
"name" => %{
"chinese" => "皮可西",
"english" => "Clefable",
"french" => "Mélodelfe",
"japanese" => "ピクシー"
},
"type" => ["Fairy"]
},
%{
"base" => %{
"Attack" => 41,
"Defense" => 40,
"HP" => 38,
"Sp. Attack" => 50,
"Sp. Defense" => 65,
"Speed" => 65
},
"id" => 37,
"name" => %{
"chinese" => "六尾",
"english" => "Vulpix",
"french" => "Goupix",
"japanese" => "ロコン"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 76,
"Defense" => 75,
"HP" => 73,
"Sp. Attack" => 81,
"Sp. Defense" => 100,
"Speed" => 100
},
"id" => 38,
"name" => %{
"chinese" => "九尾",
"english" => "Ninetales",
"french" => "Feunard",
"japanese" => "キュウコン"
},
"type" => ["Fire"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 20,
"HP" => 115,
"Sp. Attack" => 45,
"Sp. Defense" => 25,
"Speed" => 20
},
"id" => 39,
"name" => %{
"chinese" => "胖丁",
"english" => "Jigglypuff",
"french" => "Rondoudou",
"japanese" => "プリン"
},
"type" => ["Normal", "Fairy"]
},
%{
"base" => %{
"Attack" => 70,
"Defense" => 45,
"HP" => 140,
"Sp. Attack" => 85,
"Sp. Defense" => 50,
"Speed" => 45
},
"id" => 40,
"name" => %{
"chinese" => "胖可丁",
"english" => "Wigglytuff",
"french" => "Grodoudou",
"japanese" => "プクリン"
},
"type" => ["Normal", "Fairy"]
},
%{
"base" => %{
"Attack" => 45,
"Defense" => 35,
"HP" => 40,
"Sp. Attack" => 30,
"Sp. Defense" => 40,
"Speed" => 55
},
"id" => 41,
"name" => %{
"chinese" => "超音蝠",
"english" => "Zubat",
"french" => "Nosferapti",
"japanese" => "ズバット"
},
"type" => ["Poison", "Flying"]
},
%{
"base" => %{
"Attack" => 80,
"Defense" => 70,
"HP" => 75,
"Sp. Attack" => 65,
"Sp. Defense" => 75,
"Speed" => 90
},
"id" => 42,
"name" => %{
"chinese" => "大嘴蝠",
"english" => "Golbat",
"french" => "Nosferalto",
"japanese" => "ゴルバット"
},
"type" => ["Poison", "Flying"]
},
%{
"base" => %{
"Attack" => 50,
"Defense" => 55,
"HP" => 45,
"Sp. Attack" => 75,
"Sp. Defense" => 65,
"Speed" => 30
},
"id" => 43,
"name" => %{
"chinese" => "走路草",
"english" => "Oddish",
"french" => "Mystherbe",
"japanese" => "ナゾノクサ"
},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{
"Attack" => 65,
"Defense" => 70,
"HP" => 60,
"Sp. Attack" => 85,
"Sp. Defense" => 75,
...
},
"id" => 44,
"name" => %{"chinese" => "臭臭花", "english" => "Gloom", "french" => "Ortide", ...},
"type" => ["Grass", "Poison"]
},
%{
"base" => %{"Attack" => 80, "Defense" => 85, "HP" => 75, "Sp. Attack" => 110, ...},
"id" => 45,
"name" => %{"chinese" => "霸王花", "english" => "Vileplume", ...},
"type" => ["Grass", ...]
},
%{
"base" => %{"Attack" => 70, "Defense" => 55, "HP" => 35, ...},
"id" => 46,
"name" => %{"chinese" => "派拉斯", ...},
"type" => [...]
},
%{"base" => %{"Attack" => 95, "Defense" => 80, ...}, "id" => 47, "name" => %{...}, ...},
%{"base" => %{"Attack" => 55, ...}, "id" => 48, ...},
%{"base" => %{...}, ...},
%{...},
...
]
Here, we're using :ets.tab2list/1
to load the entire :ets
dataset into our process's memory.
This filtering succeeds in giving us only the 151 Pokémon I am personally comfortable admitting exist:
first_gen_pokemon |> length
151
mapping
Mapping
Let's also try extracting just the names of the Pokémon we know about:
pokemon_names =
:ets.tab2list(pokedex)
|> Enum.map(fn {_id, %{"name" => %{"english" => name}}} -> name end)
["Melmetal", "Lugia", "Misdreavus", "Clefairy", "Swirlix", "Absol", "Tapu Bulu", "Nidoqueen",
"Venusaur", "Meowth", "Camerupt", "Wobbuffet", "Tapu Lele", "Tynamo", "Decidueye", "Wailmer",
"Bibarel", "Solrock", "Meganium", "Dusclops", "Mareep", "Manaphy", "Chinchou", "Breloom",
"Smoochum", "Corphish", "Dragonair", "Feraligatr", "Zebstrika", "Staravia", "Snubbull", "Espurr",
"Krabby", "Lucario", "Cloyster", "Igglybuff", "Xatu", "Exeggcute", "Tangela", "Marshadow",
"Sharpedo", "Floatzel", "Infernape", "Heatran", "Tentacool", "Elgyem", "Haxorus", "Magneton",
"Vanillite", "Nosepass", ...]
the-problem
The Problem
Of course, this is all terribly inefficient. :ets
has to load all of its data into our process's memory, where we only want a known handful of it. For large tables, this would greatly harm the performance of our filtering, and even risk crashing the process.
Mapping is even more problematic: if we don't do a filter beforehand, we have to map over every object, even the ones we are not interested in.
This is generally how :ets
is designed to be used, though: storing data we want globally accessible, and looking up known, specific objects by index where and when we need them. :ets
in its normal operation can be thought of as a global key/value store for arbitrary terms... That is, until you start using match specs to query them!
filtering-and-mapping-with-matcha
Filtering and Mapping With Matcha
Filtering and mapping :ets
data efficiently is exactly what match specs were invented to accomplish. We can trivially reproduce the in-memory filtering of our 1st generation of Pokémon with Matcha
, using match specs to push the actual work of filtering into :ets
itself, with a much more efficient querying mechanism, and copying just the data we want into our process.
filtering-1
Filtering
A Matcha.Spec
used in :ets
filtering looks like a case
statement, where any object that does not match our patterns is never returned from the table:
require Matcha.Table.ETS
Matcha.Table.ETS.select pokedex do
{id, _pokemon} = object when id in 1..151 -> object
end
|> length()
151
mapping-1
Mapping
Of course, we don't have to just return the full :ets
object; we can also destructure matched objects, and extract just the data from them we are interested in:
alias Matcha.Table.ETS
require ETS
ETS.select pokedex do
{id, %{"name" => %{"english" => name}}} when id in 1..151 -> name
end
["Clefairy", "Nidoqueen", "Venusaur", "Meowth", "Dragonair", "Krabby", "Cloyster", "Exeggcute",
"Tangela", "Tentacool", "Magneton", "Butterfree", "Omastar", "Rhydon", "Persian", "Aerodactyl",
"Psyduck", "Ditto", "Porygon", "Arbok", "Flareon", "Victreebel", "Fearow", "Alakazam", "Raichu",
"Eevee", "Ivysaur", "Cubone", "Hypno", "Spearow", "Pinsir", "Bellsprout", "Drowzee", "Jynx",
"Charmander", "Squirtle", "Mankey", "Electabuzz", "Rhyhorn", "Pidgey", "Nidorino", "Nidoking",
"Raticate", "Jolteon", "Growlithe", "Seaking", "Magikarp", "Rapidash", "Sandshrew", "Muk", ...]
This is what the "mapping" aspect of match specs refers to: that we can not only filter out objects that do not match our pattern, but transform the data we return from the match into what we are interested in, as if passing it through an Enum.map/2
, except at a much lower level, greatly improving the efficiency of our querying.
multiple-clauses
Multiple Clauses
Just as data in a list in Elixir need not be homogenous, but can contain any term; objects in the same :ets
table can be any term — just so long as they are all tuples, and all have a key entry at the same position in the tuples:
hetrogenous_table = :ets.new(HetrogenousTable, [:set])
:ets.insert(hetrogenous_table, [
{3, "three", "tuple"},
{4, "four", "tuple", "object"}
])
true
Our match specs can use multiple clauses to match on these different object shapes:
alias Matcha.Table.ETS
require ETS
ETS.select hetrogenous_table do
{_key, length, _} -> length
{_key, length, _, _} -> length
end
["four", "three"]
We can also return hetrogenous shapes from :ets
, our mapping operation need not always shape query results the same:
alias Matcha.Table.ETS
require ETS
ETS.select hetrogenous_table do
{_key, _, _} -> [{:three, :object, :shape}]
{_key, _, _, _} -> [{:four, :object, :shape, :tuple}]
end
[[{:four, :object, :shape, :tuple}], [{:three, :object, :shape}]]
Combining this with the specificity and expressivity of pattern matching, we can do some very powerful filter/mapping in a very efficient way:
alias Matcha.Table.ETS
require ETS
ETS.select pokedex do
{id, %{"type" => types, "name" => %{"english" => name}}}
when id in 1..151 ->
{name, generation: 1, types: types}
{id, %{"type" => types, "name" => %{"english" => name}}}
when id in 152..251 ->
{name, generation: 2, types: types}
# Other generations were a mistake
end
[
{"Lugia", [generation: 2, types: ["Psychic", "Flying"]]},
{"Misdreavus", [generation: 2, types: ["Ghost"]]},
{"Clefairy", [generation: 1, types: ["Fairy"]]},
{"Nidoqueen", [generation: 1, types: ["Poison", "Ground"]]},
{"Venusaur", [generation: 1, types: ["Grass", "Poison"]]},
{"Meowth", [generation: 1, types: ["Normal"]]},
{"Wobbuffet", [generation: 2, types: ["Psychic"]]},
{"Meganium", [generation: 2, types: ["Grass"]]},
{"Mareep", [generation: 2, types: ["Electric"]]},
{"Chinchou", [generation: 2, types: ["Water", "Electric"]]},
{"Smoochum", [generation: 2, types: ["Ice", "Psychic"]]},
{"Dragonair", [generation: 1, types: ["Dragon"]]},
{"Feraligatr", [generation: 2, types: ["Water"]]},
{"Snubbull", [generation: 2, types: ["Fairy"]]},
{"Krabby", [generation: 1, types: ["Water"]]},
{"Cloyster", [generation: 1, types: ["Water", "Ice"]]},
{"Igglybuff", [generation: 2, types: ["Normal", "Fairy"]]},
{"Xatu", [generation: 2, types: ["Psychic", "Flying"]]},
{"Exeggcute", [generation: 1, types: ["Grass", "Psychic"]]},
{"Tangela", [generation: 1, types: ["Grass"]]},
{"Tentacool", [generation: 1, types: ["Water", "Poison"]]},
{"Magneton", [generation: 1, types: ["Electric", "Steel"]]},
{"Togetic", [generation: 2, types: ["Fairy", "Flying"]]},
{"Scizor", [generation: 2, types: ["Bug", "Steel"]]},
{"Cyndaquil", [generation: 2, types: ["Fire"]]},
{"Steelix", [generation: 2, types: ["Steel", "Ground"]]},
{"Unown", [generation: 2, types: ["Psychic"]]},
{"Magcargo", [generation: 2, types: ["Fire", "Rock"]]},
{"Butterfree", [generation: 1, types: ["Bug", "Flying"]]},
{"Entei", [generation: 2, types: ["Fire"]]},
{"Omastar", [generation: 1, types: ["Rock", "Water"]]},
{"Rhydon", [generation: 1, types: ["Ground", "Rock"]]},
{"Persian", [generation: 1, types: ["Normal"]]},
{"Aerodactyl", [generation: 1, types: ["Rock", "Flying"]]},
{"Psyduck", [generation: 1, types: ["Water"]]},
{"Corsola", [generation: 2, types: ["Water", "Rock"]]},
{"Pineco", [generation: 2, types: ["Bug"]]},
{"Granbull", [generation: 2, types: ["Fairy"]]},
{"Ditto", [generation: 1, types: ["Normal"]]},
{"Bayleef", [generation: 2, types: ["Grass"]]},
{"Porygon", [generation: 1, types: ["Normal"]]},
{"Arbok", [generation: 1, types: ["Poison"]]},
{"Pichu", [generation: 2, types: ["Electric"]]},
{"Flareon", [generation: 1, types: ["Fire"]]},
{"Hoothoot", [generation: 2, types: ["Normal", ...]]},
{"Victreebel", [generation: 1, types: [...]]},
{"Remoraid", [generation: 2, ...]},
{"Tyranitar", [...]},
{"Fearow", ...},
{...},
...
]