Lists
Heads and Tails
[] 為 empty list
[ 3 | [] ] 就等於 [3]
[ 1 | [ 2 | [ 3 | [] ] ] ] 就等於 [1, 2, 3]
iex(2)> [ head | tail ] = [ 1, 2, 3 ]
[1, 2, 3]
iex(3)> head
1
iex(4)> tail
[2, 3]
利用 Head Tail 不斷 recursive 呼叫 func,沒有使用的參數 _head 前面加上底線,就不會出現編譯的 warning。
defmodule MyList do
def len([]), do: 0
def len([_head|tail]), do: 1 + len(tail)
end
運算過程為
len([11,12,13,14,15])
= 1 + len([12,13,14,15])
= 1 + 1 + len([13,14,15])
= 1 + 1 + 1 + len([14,15])
= 1 + 1 + 1 + 1 + len([15])
= 1 + 1 + 1 + 1 + 1 + len([])
= 1 + 1 + 1 + 1 + 1 + 0
= 5
Map function for list
defmodule MyList do
def map([], _func), do: []
def map([ head | tail ], func), do: [ func.(head) | map(tail, func) ]
end
使用 map
iex(1)> MyList.map [1,2,3,4], fn (n) -> n*n end
[1, 4, 9, 16]
iex(2)> MyList.map [1,2,3,4], fn (n) -> n+1 end
[2, 3, 4, 5]
iex(3)> MyList.map [1,2,3,4], &(&1 + 1)
[2, 3, 4, 5]
在 recursion 過程中,持續保留結果
- sum([]) -> 0
- sum([ head |tail ]) -> «total» + sum(tail)
defmodule MyList do
def sum([], total), do: total
def sum([ head | tail ], total), do: sum(tail, head+total)
end
MyList.sum([1,2,3,4,5], 0)
剛剛的寫法,呼叫時要先傳入初始化的 total,也就是 0,改寫為以下做法,隱藏實際的 _sum 演算法,另外定義一個新的 public function,只對外開放可使用這個 function
defmodule MyList do
def sum(list), do: _sum(list, 0)
# private methods
defp _sum([], total), do: total
defp _sum([ head | tail ], total), do: _sum(tail, head+total)
end
list 的 reduce function 定義
reduce(collection, initial_value, fun)
- reduce([], value, _) -> value
- reduce([ head |tail ], value, fun) -> reduce(tail, fun.(head, value), fun)
defmodule MyList do
def reduce([], value, _) do
value
end
def reduce([head | tail], value, fun) do
reduce(tail, fun.(head, value), fun)
end
end
使用 reduce.exs
iex(1)> MyList.reduce([1,2,3,4,5], 0, &(&1 + &2))
15
iex(2)> MyList.reduce([1,2,3,4,5], 1, &(&1 * &2))
120
More complex List Patterns
defmodule Swapper do
def swap([]), do: []
def swap([ a, b | tail ]), do: [ b, a | swap(tail) ]
def swap([_]), do: raise "Can't swap a list with an odd number of elements"
end
執行結果
iex(1)> Swapper.swap [1,2,3,4,5,6]
[2, 1, 4, 3, 6, 5]
Lists of Lists
defmodule WeatherHistory do
def test_data do
[
[1366225622, 26, 15, 0.125],
[1366225622, 27, 15, 0.45],
[1366225622, 28, 21, 0.25],
[1366229222, 26, 19, 0.081],
[1366229222, 27, 17, 0.468],
[1366229222, 28, 15, 0.60],
[1366232822, 26, 22, 0.095],
[1366232822, 27, 21, 0.05],
[1366232822, 28, 24, 0.03],
[1366236422, 26, 17, 0.025]
]
end
# standard recurse until the list is empty stanza
def for_location_27([]), do: []
def for_location_27([ [time, 27, temp, rain ] | tail]) do
[ [time, 27, temp, rain] | for_location_27(tail) ]
end
def for_location_27([ _ | tail]), do: for_location_27(tail)
end
iex(1)> import WeatherHistory
WeatherHistory
iex(2)> for_location_27(test_data)
[[1366225622, 27, 15, 0.45], [1366229222, 27, 17, 0.468],
[1366232822, 27, 21, 0.05]]
改進 weather2.exs
defmodule WeatherHistory do
def test_data do
[
[1366225622, 26, 15, 0.125],
[1366225622, 27, 15, 0.45],
[1366225622, 28, 21, 0.25],
[1366229222, 26, 19, 0.081],
[1366229222, 27, 17, 0.468],
[1366229222, 28, 15, 0.60],
[1366232822, 26, 22, 0.095],
[1366232822, 27, 21, 0.05],
[1366232822, 28, 24, 0.03],
[1366236422, 26, 17, 0.025]
]
end
def for_location([], _target_loc), do: []
def for_location([ [time, target_loc, temp, rain ] | tail], target_loc) do
[ [time, target_loc, temp, rain] | for_location(tail, target_loc) ]
end
def for_location([ _ | tail], target_loc), do: for_location(tail, target_loc)
end
weather3.exs
defmodule WeatherHistory do
def test_data do
[
[1366225622, 26, 15, 0.125],
[1366225622, 27, 15, 0.45],
[1366225622, 28, 21, 0.25],
[1366229222, 26, 19, 0.081],
[1366229222, 27, 17, 0.468],
[1366229222, 28, 15, 0.60],
[1366232822, 26, 22, 0.095],
[1366232822, 27, 21, 0.05],
[1366232822, 28, 24, 0.03],
[1366236422, 26, 17, 0.025]
]
end
def for_location([], target_loc), do: []
def for_location([ head = [_, target_loc, _, _ ] | tail], target_loc) do
[ head | for_location(tail, target_loc) ]
end
def for_location([ _ | tail], target_loc), do: for_location(tail, target_loc)
end
functions in List Module
#
# Concatenate lists
#
iex> [1,2,3] ++ [4,5,6]
[1, 2, 3, 4, 5, 6]
#
# Flatten
#
iex> List.flatten([[[1], 2], [[[3]]]])
[1, 2, 3]
#
# Folding (like reduce, but can choose direction)
#
iex> List.foldl([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
"3(2(1()))"
iex> List.foldr([1,2,3], "", fn value, acc -> "#{value}(#{acc})" end)
"1(2(3()))"
#
# Updating in the middle (not a cheap operation)
#
iex> list = [ 1, 2, 3 ]
[ 1, 2, 3 ]
iex> List.replace_at(list, 2, "buckle my shoe")
[1, 2, "buckle my shoe"]
#
# Accessing tuples within lists
#
iex> kw = [{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
[{:name, "Dave"}, {:likes, "Programming"}, {:where, "Dallas", "TX"}]
iex> List.keyfind(kw, "Dallas", 1)
{:where, "Dallas", "TX"}
iex> List.keyfind(kw, "TX", 2)
{:where, "Dallas", "TX"}
iex> List.keyfind(kw, "TX", 1)
nil
iex> List.keyfind(kw, "TX", 1, "No city called TX")
"No city called TX"
iex> kw = List.keydelete(kw, "TX", 2)
[name: "Dave", likes: "Programming"]
iex> kw = List.keyreplace(kw, :name, 0, {:first_name, "Dave"})
[first_name: "Dave", likes: "Programming"]
Dictionary: Map, Keyword Lists, Sets, Structs
dictionary: a data type that associates keys with values
選用 Maps 或是 Keyword Lists?
依序考慮下列問題
需不需要使用 pattern-matching for contents? 例如 matching 有 :name 為 key 的 dictionary。
如果有需要就使用 map需不需要用同樣的 key 儲存不同的 value? 如果有就使用 Keyword module
儲存的元素要不要保留順序? 如果有就使用 Keyword module
最後,就直接用 map
Keyword Lists
defmodule Canvas do
@defaults [ fg: "black", bg: "white", font: "Merriweather" ]
def draw_text(text, options \\ []) do
options = Keyword.merge(@defaults, options)
IO.puts "Drawing text #{inspect(text)}"
IO.puts "Foreground: #{options[:fg]}"
IO.puts "Background: #{Keyword.get(options, :bg)}"
IO.puts "Font: #{Keyword.get(options, :font)}"
IO.puts "Pattern: #{Keyword.get(options, :pattern, "solid")}"
IO.puts "Style: #{inspect Keyword.get_values(options, :style)}"
end
end
Canvas.draw_text("hello", fg: "red", style: "italic", style: "bold")
以 key 使用 map 裡面的value: kwlist[key]
Keyword List 可使用 Keyword 及 Enum modules 的所有 functions
Maps
iex(1)> map = %{ name: "You", likes: "Programming", where: "Home" }
%{likes: "Programming", name: "You", where: "Home"}
iex(2)> Map.keys map
[:likes, :name, :where]
iex(3)> Map.values map
["Programming", "You", "Home"]
iex(4)> map[:name]
"You"
iex(5)> map.name
"You"
iex(6)> map1 = Map.drop map, [:where, :likes]
%{name: "You"}
iex(7)> map2 = Map.put map, :also_likes, "Ruby"
%{also_likes: "Ruby", likes: "Programming", name: "You", where: "Home"}
iex(8)> Map.keys map2
[:also_likes, :likes, :name, :where]
iex(9)> Map.has_key? map1, :where
false
iex(10)> { value, updated_map } = Map.pop map2, :also_likes
{"Ruby", %{likes: "Programming", name: "You", where: "Home"}}
iex(11)> Map.equal? map, updated_map
true
Pattern Matching and Updating Maps
利用 pattern matching,在 match 同時, binding 變數
iex(15)> person = %{ name: "You", height: 1.88 }
%{height: 1.88, name: "You"}
# 是否有存在key :name
iex(16)> %{ name: a_name } = person
%{height: 1.88, name: "You"}
iex(17)> a_name
"You"
iex(18)> %{ name: _, height: _ } = person
%{height: 1.88, name: "You"}
## :name 為 "You" ?
iex(19)> %{ name: "You" } = person
%{height: 1.88, name: "You"}
## not match error
iex(20)> %{ name: _, weight: _ } = person
** (MatchError) no match of right hand side value: %{height: 1.88, name: "You"}
iterate 所有 key-value,取得符合某個條件(height > 1.5) 的 map
people = [
%{ name: "Grumpy", height: 1.24 },
%{ name: "Dave", height: 1.88 },
%{ name: "Dopey", height: 1.32 },
%{ name: "Shaquille", height: 2.16 },
%{ name: "Sneezy", height: 1.28 }
]
IO.inspect(for person = %{ height: height } <- people, height > 1.5, do: person)
people = [
%{ name: "Grumpy", height: 1.24 },
%{ name: "Dave", height: 1.88 },
%{ name: "Dopey", height: 1.32 },
%{ name: "Shaquille", height: 2.16 },
%{ name: "Sneezy", height: 1.28 }
]
defmodule HotelRoom do
def book(%{name: name, height: height})
when height > 1.9 do
IO.puts "Need extra long bed for #{name}"
end
def book(%{name: name, height: height})
when height < 1.3 do
IO.puts "Need low shower controls for #{name}"
end
def book(person) do
IO.puts "Need regular bed for #{person.name}"
end
end
people |> Enum.each(&HotelRoom.book/1)
結果為
Need low shower controls for Grumpy
Need regular bed for Dave
Need regular bed for Dopey
Need extra long bed for Shaquille
Need low shower controls for Sneezy
不能在 pattern matching 時,同時 bind key variable
iex(1)> %{ 2 => state } = %{ 1 => :ok, 2 => :error }
%{1 => :ok, 2 => :error}
iex(2)> state
:error
iex(3)> %{ item => :ok } = %{ 1 => :ok, 2 => :error }
** (CompileError) iex:3: illegal use of variable item inside map key match, maps can only match on existing variables by using ^item
(stdlib) lists.erl:1354: :lists.mapfoldl/3
Pattern Matching 可 match variable keys
iex(11)> data = %{ name: "You", state: "Home", likes: "Elixir" }
%{likes: "Elixir", name: "You", state: "Home"}
iex(12)> for key <- [ :name, :likes ] do
...(12)> %{ ^key => value } = data
...(12)> value
...(12)> end
["You", "Elixir"]
Updating Maps 最簡單的方式為
new_map = %{ old_map | key => value, … }
會先複製一份舊的 map,並將新的 key,value 替換掉
iex(16)> m = %{ a: 1, b: 2, c: 3 }
%{a: 1, b: 2, c: 3}
iex(17)> m1 = %{ m | b: "two", c: "three" }
%{a: 1, b: "two", c: "three"}
iex(18)> m2 = %{ m1 | a: "one" }
%{a: "one", b: "two", c: "three"}
Structs
struct 就是一個 limited form of map, keys 一定要是 atoms,module 名稱就是這個 map type 的名稱
defmodule Subscriber do
defstruct name: "", paid: false, over_18: true
end
使用 Subscriber 的方式
iex(1)> s1 = %Subscriber{}
%Subscriber{name: "", over_18: true, paid: false}
iex(2)> s2 = %Subscriber{ name: "You" }
%Subscriber{name: "You", over_18: true, paid: false}
iex(3)> s3 = %Subscriber{ name: "Mary", paid: true }
%Subscriber{name: "Mary", over_18: true, paid: true}
iex(4)>
nil
iex(5)> s3.name
"Mary"
iex(6)> %Subscriber{name: a_name} = s3
%Subscriber{name: "Mary", over_18: true, paid: true}
iex(7)> a_name
"Mary"
iex(8)>
nil
iex(9)> s4 = %Subscriber{ s3 | name: "Marie"}
%Subscriber{name: "Marie", over_18: true, paid: true}
限制一定要用 module 封裝 structs 的原因是,要在 module 裡面加上對該 structs 相關的操作 functions
iex(1)> a1 = %Attendee{name: "You", over_18: true}
%Attendee{name: "You", over_18: true, paid: false}
iex(2)> a2 = %Attendee{a1 | paid: true}
%Attendee{name: "You", over_18: true, paid: true}
iex(3)> Attendee.may_attend_after_party(a2)
true
iex(4)> Attendee.print_vip_badge(a2)
Very cheap badge for You
:ok
iex(5)> a3 = %Attendee{}
%Attendee{name: "", over_18: true, paid: false}
iex(6)> Attendee.print_vip_badge(a3)
** (RuntimeError) missing name for badge
defstruct1.exs:21: Attendee.print_vip_badge/1
Nested Ductionary Structures
可在 value 的部分使用 dictionary
defmodule Customer do
defstruct name: "", company: ""
end
defmodule BugReport do
defstruct owner: %Customer{}, details: "", severity: 1
end
defmodule User do
report = %BugReport{owner: %Customer{name: "You", company: "Company"},
details: "broken"}
IO.inspect report
report = %BugReport{ report | owner: %Customer{ report.owner | company: "PragProg" }}
IO.inspect report
IO.inspect update_in(report.owner.name, &("Mr. " <> &1))
end
執行結果
%BugReport{details: "broken", owner: %Customer{company: "Company", name: "You"},
severity: 1}
%BugReport{details: "broken",
owner: %Customer{company: "PragProg", name: "You"}, severity: 1}
%BugReport{details: "broken",
owner: %Customer{company: "PragProg", name: "Mr. You"}, severity: 1}
Nested Accessors and Nonstructs
put_in(path, value)
put_in( data, keys, value)
update_in(path, fun)
update_in(data, keys, fun)
iex(1)> report = %{ owner: %{ name: "You", company: "Company" }, severity: 1}
%{owner: %{company: "Company", name: "You"}, severity: 1}
iex(2)> put_in(report[:owner][:company], "Com")
%{owner: %{company: "Com", name: "You"}, severity: 1}
iex(3)> update_in(report[:owner][:name], &("Mr. " <> &1))
%{owner: %{company: "Company", name: "Mr. You"}, severity: 1}
Dynamic (Runtime) Nested Accessors
get_in
put_in
update_in
get_and_update_in
ex:
nested = %{
buttercup: %{
actor: %{
first: "Robin",
last: "Wright"
},
role: "princess"
},
westley: %{
actor: %{
first: "Cary",
last: "Ewles" # typo!
},
role: "farm boy"
}
}
IO.inspect get_in(nested, [:buttercup])
# => %{actor: %{first: "Robin", last: "Wright"}, role: "princess"}
IO.inspect get_in(nested, [:buttercup, :actor])
# => %{first: "Robin", last: "Wright"}
IO.inspect get_in(nested, [:buttercup, :actor, :first])
# => "Robin"
IO.inspect put_in(nested, [:westley, :actor, :last], "Elwes")
# => %{buttercup: %{actor: %{first: "Robin", last: "Wright"}, role: "princess"},
# => westley: %{actor: %{first: "Cary", last: "Elwes"}, role: "farm boy"}}
可利用 fun 當作 keys 傳入 get_in
get_in(data, keys)
authors = [
%{ name: "José", language: "Elixir" },
%{ name: "Matz", language: "Ruby" },
%{ name: "Larry", language: "Perl" }
]
languages_with_an_r = fn (:get, collection, next_fn) ->
for row <- collection do
if String.contains?(row.language, "r") do
next_fn.(row)
end
end
end
IO.inspect get_in(authors, [languages_with_an_r, :name])
#=> [ "José", nil, "Larry" ]
Access Module
提供 get 與 get_and_update_in
一些 predefined functions 當作參數
Access.all(), Access.at(1)
cast = [
%{
character: "Buttercup",
actor: %{
first: "Robin",
last: "Wright"
},
role: "princess"
},
%{
character: "Westley",
actor: %{
first: "Cary",
last: "Elwes"
},
role: "farm boy"
}
]
IO.inspect get_in(cast, [Access.all(), :character])
#=> ["Buttercup", "Westley"]
IO.inspect get_in(cast, [Access.at(1), :role])
#=> "farm boy"
IO.inspect get_and_update_in(cast, [Access.all(), :actor, :last],
fn (val) -> {val, String.upcase(val)} end)
#=> {["Wright", "Ewes"],
# [%{actor: %{first: "Robin", last: "WRIGHT"}, character: "Buttercup",
# role: "princess"},
# %{actor: %{first: "Cary", last: "EWES"}, character: "Westley",
# role: "farm boy"}]}
Access.elem 可處理 tuples
cast = [
%{
character: "Buttercup",
actor: {"Robin", "Wright"},
role: "princess"
},
%{
character: "Westley",
actor: {"Carey", "Elwes"},
role: "farm boy"
}
]
IO.inspect get_in(cast, [Access.all(), :actor, Access.elem(1)])
#=> ["Wright", "Elwes"]
IO.inspect get_and_update_in(cast, [Access.all(), :actor, Access.elem(1)],
fn (val) -> {val, String.reverse(val)} end)
#=> {["Wright", "Elwes"],
# [%{actor: {"Robin", "thgirW"}, character: "Buttercup", role: "princess"},
# %{actor: {"Carey", "sewlE"}, character: "Westley", role: "farm boy"}]}
Access.key Access.key! 可處理 dictionary types(maps, structs)
cast = %{
buttercup: %{
actor: {"Robin", "Wright"},
role: "princess"
},
westley: %{
actor: {"Carey", "Elwes"},
role: "farm boy"
}
}
IO.inspect get_in(cast, [Access.key(:westley), :actor, Access.elem(1)])
#=> "Elwes"
IO.inspect get_and_update_in(cast, [Access.key(:buttercup), :role],
fn (val) -> {val, "Queen"} end)
#=> {"princess",
# %{buttercup: %{actor: {"Robin", "Wright"}, role: "Queen"},
# westley: %{actor: {"Carey", "Elwes"}, role: "farm boy"}}}
Access.pop 可從 map/keyword list 移出一個 key
iex(1)> Access.pop(%{name: "Elixir", creator: "Valim"}, :name)
{"Elixir", %{creator: "Valim"}}
iex(2)> Access.pop([name: "Elixir", creator: "Valim"], :name)
{"Elixir", [creator: "Valim"]}
iex(3)> Access.pop(%{name: "Elixir", creator: "Valim"}, :year)
{nil, %{creator: "Valim", name: "Elixir"}}
Sets
Sets 是以 MapSet module 實作的
iex(1)> set1 = 1..5 |> Enum.into(MapSet.new)
#MapSet<[1, 2, 3, 4, 5]>
iex(2)> set2 = 3..8 |> Enum.into(MapSet.new)
#MapSet<[3, 4, 5, 6, 7, 8]>
iex(3)> MapSet.member? set1, 3
true
iex(4)> MapSet.union set1, set2
#MapSet<[1, 2, 3, 4, 5, 6, 7, 8]>
iex(5)> MapSet.difference set1, set2
#MapSet<[1, 2]>
iex(6)> MapSet.difference set2, set1
#MapSet<[6, 7, 8]>
iex(7)> MapSet.intersection set2, set1
#MapSet<[3, 4, 5]>
Enum and Stream
elixir 的 Enum and Stream 有一下 iteration functions,Stream 可 enumerate a collection lazily,也就是說下一個 value 只在需要時才會計算
Enum
## 將 collection 轉換為 list
iex(1)> list = Enum.to_list 1..5
[1, 2, 3, 4, 5]
## 連接兩個 collections
iex(2)> Enum.concat([1,2,3], [4,5,6])
[1, 2, 3, 4, 5, 6]
iex(3)> Enum.concat [1,2,3], 'abc'
[1, 2, 3, 97, 98, 99]
## 將 list 每個 elements 放到 function 產生新的 list
iex(4)> Enum.map(list, &(&1 * 10))
[10, 20, 30, 40, 50]
iex(5)> Enum.map(list, &String.duplicate("*", &1))
["*", "**", "***", "****", "*****"]
## 根據 position/criteria 取得 list 的 elements
iex(8)> Enum.at(10..20, 3)
13
iex(9)> Enum.at(10..20, 20)
nil
iex(10)> Enum.at(10..20, 20, :no_one_here)
:no_one_here
iex(11)> Enum.filter(list, &(&1 > 2))
[3, 4, 5]
iex(12)> require Integer
Integer
iex(13)> Enum.filter(list, &Integer.is_even/1)
[2, 4]
iex(14)> Enum.reject(list, &Integer.is_even/1)
[1, 3, 5]
## 排序/比較 elements
iex(16)> Enum.sort ["there", "was", "a", "crooked", "man"]
["a", "crooked", "man", "there", "was"]
iex(17)> Enum.sort ["there", "was", "a", "crooked", "man"], &(String.length(&1) <= String.length(&2))
["a", "was", "man", "there", "crooked"]
iex(18)> Enum.max ["there", "was", "a", "crooked", "man"]
"was"
iex(19)> Enum.max_by ["there", "was", "a", "crooked", "man"], &String.length/1
"crooked"
## split a collection
iex(20)> Enum.take(list, 3)
[1, 2, 3]
iex(21)> Enum.take_every list, 2
[1, 3, 5]
iex(22)> Enum.take_while(list, &(&1 < 4))
[1, 2, 3]
iex(23)> Enum.split(list, 3)
{[1, 2, 3], [4, 5]}
iex(24)> Enum.split_while(list, &(&1 < 4))
{[1, 2, 3], [4, 5]}
## join a collection
iex(25)> Enum.join(list)
"12345"
iex(26)> Enum.join(list, ", ")
"1, 2, 3, 4, 5"
## predicate operations
iex(27)> Enum.all?(list, &(&1 < 4))
false
iex(28)> Enum.any?(list, &(&1 < 4))
true
iex(29)> Enum.member?(list, 4)
true
iex(30)> Enum.empty?(list)
false
## merge collections
iex(31)> Enum.zip(list, [:a, :b, :c])
[{1, :a}, {2, :b}, {3, :c}]
iex(32)> Enum.with_index(["once", "upon", "a", "time"])
[{"once", 0}, {"upon", 1}, {"a", 2}, {"time", 3}]
## Fold elements into a single value
iex(33)> Enum.reduce(1..100, &(&1+&2))
5050
iex(37)> Enum.reduce(["now", "is", "the", "time"],fn word, longest ->
...(37)> if String.length(word) > String.length(longest) do
...(37)> word
...(37)> else
...(37)> longest
...(37)> end
...(37)> end)
"time"
處理撲克牌
iex(1)> import Enum
Enum
iex(2)> deck = for rank <- '23456789TJQKA', suit <- 'CDHS', do: [suit,rank]
['C2', 'D2', 'H2', 'S2', 'C3', 'D3', 'H3', 'S3', 'C4', 'D4', 'H4', 'S4', 'C5',
'D5', 'H5', 'S5', 'C6', 'D6', 'H6', 'S6', 'C7', 'D7', 'H7', 'S7', 'C8', 'D8',
'H8', 'S8', 'C9', 'D9', 'H9', 'S9', 'CT', 'DT', 'HT', 'ST', 'CJ', 'DJ', 'HJ',
'SJ', 'CQ', 'DQ', 'HQ', 'SQ', 'CK', 'DK', 'HK', 'SK', 'CA', 'DA', ...]
## 洗牌, 取 13 張
iex(3)> deck |> shuffle |> take(13)
['S5', 'DQ', 'ST', 'H8', 'S2', 'D9', 'CA', 'CT', 'C6', 'D6', 'D5', 'H2', 'D7']
## 發牌給四個人
iex(4)> hands = deck |> shuffle |> chunk(13)
[['HA', 'H3', 'HJ', 'C3', 'DA', 'SA', 'DQ', 'H6', 'C8', 'D6', 'D7', 'ST', 'D8'],
['H8', 'H5', 'C2', 'SJ', 'H2', 'S2', 'CQ', 'S4', 'S3', 'CA', 'C4', 'S8', 'H9'],
['DJ', 'S7', 'C5', 'CT', 'HK', 'HQ', 'C6', 'D3', 'SQ', 'D2', 'CK', 'H4', 'D4'],
['C9', 'D5', 'SK', 'CJ', 'S9', 'D9', 'S5', 'S6', 'DK', 'H7', 'C7', 'DT', 'HT']]
Streams - Lazy Enumerables
[ 1, 2, 3, 4, 5 ]
|> Enum.map(&(&1*&1))
|> Enum.with_index # 產生 a list of tuples
|> Enum.map(fn {value, index} -> value - index end)
|> IO.inspect #=> [1,3,7,13,21]
如果 words 有2.4MB,Enum 會全部 load 到記憶體並送進 split,應該改用 Stream 一行一行處理就好了
IO.puts File.read!("/usr/share/dict/words")
|> String.split
|> Enum.max_by(&String.length/1)
使用 Stream 會產生一個 Enum.map,而不是直接得到結果
iex(1)> s = Stream.map [1, 3, 5, 7], &(&1 + 1)
#Stream<[enum: [1, 3, 5, 7], funs: [#Function<46.40091930/1 in Stream.map/2>]]>
iex(2)> Enum.to_list s
[2, 4, 6, 8]
[1,2,3,4]
|> Stream.map(&(&1*&1))
|> Stream.map(&(&1+1))
|> Stream.filter(fn x -> rem(x,2) == 1 end)
|> Enum.to_list
大檔案資料,一次處理一行
IO.puts File.open!("/usr/share/dict/words")
|> IO.stream(:line)
|> Enum.max_by(&String.length/1)
也可以改為 File.stream
IO.puts File.stream!("/usr/share/dict/words") |> Enum.max_by(&String.length/1)
Infinite Stream
可產生非常大的 Stream,但只取某幾個,用 Enum 會卡住,要改用Stream
Enum.map(1..10_000_000, &(&1+1)) |> Enum.take(5)
iex(1)> Stream.map(1..10_000_000, &(&1+1)) |> Enum.take(5)
[2, 3, 4, 5, 6]
cycle, repeatedly, iterate, unfold, resource
Stream.cycle: 利用 enumerable 產生 infinite stream 包含這些 elements
iex(1)> Stream.cycle(~w{ green white }) |>
...(1)> Stream.zip(1..5) |>
...(1)> Enum.map(fn {class, value} ->
...(1)> ~s{<tr class="#{class}"><td>#{value}</td></tr>\n} end) |>
...(1)> IO.puts
<tr class="green"><td>1</td></tr>
<tr class="white"><td>2</td></tr>
<tr class="green"><td>3</td></tr>
<tr class="white"><td>4</td></tr>
<tr class="green"><td>5</td></tr>
Stream.repeatedly: 當需要新 value 時,就會 invoke 該 function
iex(4)> Stream.repeatedly(fn -> true end) |> Enum.take(3)
[true, true, true]
iex(5)> Stream.repeatedly(&:random.uniform/0) |> Enum.take(3)
[0.4435846174457203, 0.7230402056221108, 0.94581636451987]
Stream.iterate(start_value, next_fun)
: 由 startvalue 開始,送入 nextfun 產生下一個 value,一直無限循環
iex(8)> Stream.iterate(0, &(&1+1)) |> Enum.take(5)
[0, 1, 2, 3, 4]
iex(9)> Stream.iterate(2, &(&1*&1)) |> Enum.take(5)
[2, 4, 16, 256, 65536]
iex(10)> Stream.iterate([], &[&1]) |> Enum.take(5)
[[], [[]], [[[]]], [[[[]]]], [[[[[]]]]]]
Stream.unfold: 產生 infinite stream of values,每一個 value 都是 previous state 的某個 function
fn state -> { stream_value, new_state } end
iex(13)> Stream.unfold({0,1}, fn {f1,f2} -> {f1, {f2, f1+f2}} end) |> Enum.take(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
Stream.resource
defmodule Countdown do
def sleep(seconds) do
receive do
after seconds*1000 -> nil
end
end
def say(text) do
spawn fn -> :os.cmd('say #{text}') end
end
def timer do
Stream.resource(
fn -> # the number of seconds to the start of the next minute
{_h,_m,s} = :erlang.time
60 - s - 1
end,
fn # wait for the next second, then return its countdown
0 ->
{:halt, 0}
count ->
sleep(1)
{ [inspect(count)], count - 1 }
end,
fn _ -> nil end # nothing to deallocate
)
end
end
iex(1)> counter = Countdown.timer
#Function<50.40091930/2 in Stream.resource/3>
iex(2)> printer = counter |> Stream.each(&IO.puts/1)
#Stream<[enum: #Function<50.40091930/2 in Stream.resource/3>,
funs: [#Function<38.40091930/1 in Stream.each/2>]]>
iex(3)> speaker = printer |> Stream.each(&Countdown.say/1)
#Stream<[enum: #Function<50.40091930/2 in Stream.resource/3>,
funs: [#Function<38.40091930/1 in Stream.each/2>,
#Function<38.40091930/1 in Stream.each/2>]]>
iex(4)> speaker |> Enum.take(5)
19
18
17
16
15
["19", "18", "17", "16", "15"]
Collectable Protocol
Enumerable Procotol 可 iterate over the elements
Collectable Protocol 則是相反,可 insert elements 產生 collection
iex(1)> Enum.into 1..5, []
[1, 2, 3, 4, 5]
iex(2)> Enum.into 1..5, [100, 101 ]
[100, 101, 1, 2, 3, 4, 5]
iex(3)> Enum.into IO.stream(:stdio, :line), IO.stream(:stdio, :line)
Comprehensions
comprehension: 提供 1 到多個 collections,取出每個 values 的所有組合,過濾掉一些 values,用剩下的 values 產生一個新的 collection。
result = for generator or filter… [, into: value ], do: expression
generator 的語法
pattern <- enumerable_thing
iex(1)> for x <- [ 1, 2, 3, 4, 5 ], do: x * x
[1, 4, 9, 16, 25]
iex(2)> for x <- [ 1, 2, 3, 4, 5 ], x < 4, do: x * x
[1, 4, 9]
iex(3)> for x <- [1,2], y <- [5,6], do: x * y
[5, 6, 10, 12]
iex(4)> for x <- [1,2], y <- [5,6], do: {x, y}
[{1, 5}, {1, 6}, {2, 5}, {2, 6}]
iex(5)> min_maxes = [{1,4}, {2,3}, {10, 15}]
[{1, 4}, {2, 3}, {10, 15}]
iex(6)> for {min,max} <- min_maxes, n <- min..max, do: n
[1, 2, 3, 4, 2, 3, 10, 11, 12, 13, 14, 15]
iex(8)> first8 = [ 1,2,3,4,5,6,7,8 ]
[1, 2, 3, 4, 5, 6, 7, 8]
iex(9)> for x <- first8, y <- first8, x >= y, rem(x*y, 10)==0, do: { x, y }
[{5, 2}, {5, 4}, {6, 5}, {8, 5}]
也可以用在 Bits
iex(11)> for << ch <- "hello" >>, do: ch
'hello'
iex(12)> for << ch <- "hello" >>, do: <<ch>>
["h", "e", "l", "l", "o"]
iex(14)> for << << b1::size(2), b2::size(3), b3::size(3) >> <- "hello" >>, do: "0#{b1}#{b2}#{b3}"
["0150", "0145", "0154", "0154", "0157"]
在 list comprehension 中用到的變數,都是 local 變數
iex(17)> name="You"
"You"
iex(18)> for name <- [ "cat", "dog" ], do: String.upcase(name)
["CAT", "DOG"]
iex(19)> name
"You"
comprehension 的回傳值是由 do 的結果決定
iex(1)> for x <- ~w{ cat dog }, into: %{}, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(2)> for x <- ~w{ cat dog }, into: Map.new, do: { x, String.upcase(x) }
%{"cat" => "CAT", "dog" => "DOG"}
iex(3)> for x <- ~w{ cat dog }, into: %{"ant" => "ANT"}, do: { x, String.upcase(x) }
%{"ant" => "ANT", "cat" => "CAT", "dog" => "DOG"}
iex(5)> for x <- ~w{ cat dog }, into: IO.stream(:stdio,:line), do: "<<#{x}>>\n"
<<cat>>
<<dog>>
%IO.Stream{device: :standard_io, line_or_bytes: :line, raw: false}
沒有留言:
張貼留言