Slide 1

Slide 1 text

RUBYCONF 2020 Ariel Caplan • @amcaplan • amcaplan.ninja The Humble Hash

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

LIFE

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

cell.next_state = if cell.alive? [2,3].include?(cell.live_neighbors_count) ? 1 : 0 else cell.live_neighbors_count == 3 ? 1 : 0 end

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

No content

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN •

Slide 17

Slide 17 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • • Ruby programmer since 2014 • Backend Developer at • Find me online: @amcaplan • https://amcaplan.ninja Hi! I’m Ariel Caplan.

Slide 18

Slide 18 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:basics]

Slide 19

Slide 19 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • • Performant key-value pair store • Keys and values can be any Ruby object • Literal syntax: • Explicit syntax: • Strict fetching: • Fetching with default value: • Setting a different default value: What is a Hash? { key: "value" } hash = Hash.new hash[:key] = "value" hash[:key] #=> "value" hash[:non_key] #=> nil hash.fetch(:key) hash = Hash.new(4) hash[:non_key] #=> 4

Slide 20

Slide 20 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:obj_key][:set]

Slide 21

Slide 21 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Set require 'set' set = Set.new([1,2,3]) #=> # set.include?(3) #=> true set.include?(4) #=> false set.add(4) #=> # set.add(3) #=> #

Slide 22

Slide 22 text

def add(o) @hash[o] = true self end def merge # ... do_with_enum(enum) { |o| add(o) } # ... end class Set def initialize(enum = nil, &block) @hash ||= Hash.new(false) # some other stuff merge(enum) end RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Set def include?(o) @hash[o] end end

Slide 23

Slide 23 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:obj_key][:cache]

Slide 24

Slide 24 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Disclaimers # disclaimer { key: "network", value: "tier_a", text: "To receive the lowest out-of-pocket costs, please choose a provider in the Tier A network." } # provider metadata { specialty: "periodontist", network: "tier_a" }

Slide 25

Slide 25 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Disclaimers # 500 x 300 = 150,000 operations # once per disclaimer - 300 disclaimers disclaimers.select { |disclaimer| # once per provider - 500 providers providers.any? { |provider| provider.metadata[disclaimer[:key]] == disclaimer[:value] } }.map { |disclaimer| disclaimer[:text] }

Slide 26

Slide 26 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Disclaimers # 500 + 300 = 800 operations # once per provider - 500 providers provider_data = Set.new providers.each do |provider| provider.metadata.each do |key, value| provider_data << { key: key, value: value } end end # once per disclaimer - 300 disclaimers disclaimers.select { |disclaimer| data = { key: disclaimer[:key], value: disclaimer[:value] } provider_data.include?(data) }.map { |disclaimer| disclaimer[:text] }

Slide 27

Slide 27 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • ! • String • Symbol • Numeric • !1.0.eql?(1) • Array • Hash " • Anything for which equality isn’t obvious Hash Key Warning! Containing valid types {

Slide 28

Slide 28 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:defaults][:wizardry]

Slide 29

Slide 29 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • • 1 object that will always be returned • Doesn’t set a key-value pair • Set at hash initialization: • Set after hash initialization Defaults Deep Dive: Default Value Hash.new(4) hash = {} hash.default = 4

Slide 30

Slide 30 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • • Gotcha: It will always be the same object! Defaults Deep Dive: Default Value hash = Hash.new("abc") str = hash[:key] str << "d" hash[:another_key] #=> "abcd"

Slide 31

Slide 31 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • • Proc that will be run when a key is not found • Can set a key-value pair, if you want • Set at hash initialization: • Set after hash initialization Defaults Deep Dive: Default Proc hash = Hash.new { |h, k| h[k] = 4 } hash = {} hash.default_proc = ->(h, k) { h[k] = 4 }

Slide 32

Slide 32 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Default Proc Can Reference Itself! default_proc = ->(h, k) { h[k] = Hash.new(&default_proc) } hash = Hash.new(&default_proc) hash[:a][:b][:c] = 4 hash #=> {:a=>{:b=>{:c=>4}}}

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Sequence Fn = Fn-2 + Fn-1 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55…

Slide 35

Slide 35 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Sequence: Iterative def fib(n) i, j = 0, 1 counter = 0 while counter < n i, j = j, i + j counter += 1 end i end

Slide 36

Slide 36 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Sequence: Recursive def fib(n) case n when 0, 1 n else fib(n - 2) + fib(n - 1) end end

Slide 37

Slide 37 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Sequence: Default Proc hash = Hash.new { |h, k| h[k] = h[k-2] + h[k-1] } hash[0] = 0 hash[1] = 1

Slide 38

Slide 38 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance fib(4) fib(2) fib(1) fib(0) fib(3) fib(2) fib(1) fib(1) fib(0)

Slide 39

Slide 39 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance hash[4] hash[2] hash[1] hash[0] hash[1] hash[3] hash[2]

Slide 40

Slide 40 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance hash[4] hash[2] hash[1] hash[0] hash[1] hash[3] hash[2] fib(4) fib(2) fib(1) fib(0) fib(3) fib(2) fib(1) fib(1) fib(0) 9 calls 7 calls

Slide 41

Slide 41 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance hash[4] hash[0] hash[1] hash[3] hash[2] fib(4) fib(2) fib(1) fib(0) fib(3) fib(2) fib(1) fib(1) fib(0) fib(5) fib(1) fib(3) fib(2) fib(1) fib(0) hash[5] hash[1] hash[3] hash[2] 15 calls 9 calls

Slide 42

Slide 42 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance hash[4] hash[0] hash[1] hash[3] hash[2] fib(4) fib(2) fib(1) fib(0) fib(3) fib(2) fib(1) fib(1) fib(0) fib(5) fib(1) fib(3) fib(2) fib(1) fib(0) hash[5] hash[1] hash[3] hash[2] 25 calls 11 calls fib(6) fib(4) fib(2) fib(1) fib(0) fib(3) fib(2) fib(1) fib(1) fib(0) hash[6] hash[4]

Slide 43

Slide 43 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Fibonacci Performance 29,860,703 calls 69 calls fib(35) hash[35]

Slide 44

Slide 44 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Conway’s Game of Life def life_func(x, y, step) if step == 0 INITIAL_STATES[[x, y]] || 0 else neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1], [ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]] live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| life_func((x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1) } if life_func(x, y, step - 1) == 1 [2, 3].include?(live_neighbors) ? 1 : 0 else live_neighbors == 3 ? 1 : 0 end end end

Slide 45

Slide 45 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Conway’s Game of Life life_hash = Hash.new do |h, (x, y, step)| h[[x, y, step]] = if step == 0 INITIAL_STATES[[x, y]] || 0 else neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1], [ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]] live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| h[[(x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1]] } if h[[x, y, step - 1]] == 1 [2, 3].include?(live_neighbors) ? 1 : 0 else live_neighbors == 3 ? 1 : 0 end end end

Slide 46

Slide 46 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Conway’s Game of Life life_hash = Hash.new do |h, (x, y, step)| h[[x, y, step]] = if step == 0 INITIAL_STATES[[x, y]] || 0 else neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1], [ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]] live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| h[[(x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1]] } if h[[x, y, step - 1]] == 1 [2, 3].include?(live_neighbors) ? 1 : 0 else live_neighbors == 3 ? 1 : 0 end end end def life_func(x, y, step) if step == 0 INITIAL_STATES[[x, y]] || 0 else neighbor_diffs = [[-1, -1], [-1, 0], [-1, 1], [ 0, -1], [ 0, 1], [ 1, -1], [ 1, 0], [ 1, 1]] live_neighbors = neighbor_diffs.sum { |x_diff, y_diff| life_func((x + x_diff) % WIDTH, (y + y_diff) % HEIGHT, step - 1) } if life_func(x, y, step - 1) == 1 [2, 3].include?(live_neighbors) ? 1 : 0 else live_neighbors == 3 ? 1 : 0 end end end

Slide 47

Slide 47 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Conway’s Game of Life

Slide 48

Slide 48 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:defaults][:abbrev]

Slide 49

Slide 49 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev require 'abbrev' Abbrev.abbrev(["conf", "code"]) #=> {"conf"=>"conf", "con"=>"conf", "code"=>"code", "cod"=>"code"}

Slide 50

Slide 50 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev def abbrev(words, pattern = nil) table = {} seen = Hash.new(0) if pattern.is_a?(String) pattern = /\A#{Regexp.quote(pattern)}/ # regard as a prefix end words.each do |word| next if word.empty? word.size.downto(1) { |len| abbrev = word[0...len] next if pattern && pattern !~ abbrev case seen[abbrev] += 1 when 1 table[abbrev] = word when 2 table.delete(abbrev) else break end } end words.each do |word| next if pattern && pattern !~ word table[word] = word end table end

Slide 51

Slide 51 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf => conf

Slide 52

Slide 52 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf => conf conf 1 => conf

Slide 53

Slide 53 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf => conf conf 1 con => conf

Slide 54

Slide 54 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf => conf conf 1 con con 1 => conf => conf

Slide 55

Slide 55 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co co 1 c c 1 => conf => conf => conf => conf code => code

Slide 56

Slide 56 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co co 1 c c 1 => conf => conf => conf => conf code => code code 1 => code cod cod 1 => code co

Slide 57

Slide 57 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co co 1 c c 1 => conf => conf => conf => conf code => code code 1 => code cod cod 1 => code co 2

Slide 58

Slide 58 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co 1 c c 1 => conf => conf => conf code => code code 1 => code cod cod 1 => code 2

Slide 59

Slide 59 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co 1 c c 1 => conf => conf => conf code => code code 1 => code cod cod 1 => code 2 c

Slide 60

Slide 60 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev Seen Table conf conf 1 con con 1 co 1 c 1 => conf => conf code => code code 1 => code cod cod 1 => code 2 2

Slide 61

Slide 61 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev def abbrev(words) table = {} seen = Hash.new(0) words.each do |word| word.size.downto(1) { |len| abbrev = word[0...len] case seen[abbrev] += 1 when 1 table[abbrev] = word when 2 table.delete(abbrev) else break end } end table end Default Value Return Value Add Abbreviations Seen Once Remove Ambiguous Abbreviations Never set seen[abbrev] to 0! Iterate over each word portion

Slide 62

Slide 62 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • talk[:defaults][:app_code]

Slide 63

Slide 63 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Abbrev-like Default Value totals = Hash.new(0) items = ["a", "a", "b", "c", "c", "c", "d", "b"] items.each { |item| totals[item] += 1 } totals #=> {"a"=>2, "b"=>2, "c"=>3, "d"=>1} totals["e"] #=> 0

Slide 64

Slide 64 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Real-Life Defaults! totals = Hash.new(0) # { "sum" => 1000 } by_company = Hash.new { |h,k| h[k] = Hash.new(0) } # { "a_company" => { "sum" => 100 }} totals["unset_key"] #=> 0 by_company["some_company"]["sum"] #=> 0

Slide 65

Slide 65 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Real-Life Defaults! def some_expensive_calculation @some_expensive_calculation ||= _do_the_expensive_calculation end

Slide 66

Slide 66 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Real-Life Defaults! def some_expensive_calculation_a @some_expensive_calculation_a ||= _do_the_expensive_calculation(:a) end def some_expensive_calculation_b @some_expensive_calculation_b ||= _do_the_expensive_calculation(:b) end def some_expensive_calculation_c @some_expensive_calculation_c ||= _do_the_expensive_calculation(:c) end

Slide 67

Slide 67 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Real-Life Defaults! def some_expensive_calculation(argument) @some_expensive_calculation ||= Hash.new do |h, k| h[k] = _do_the_expensive_calculation(k) end @some_expensive_calculation[argument] end

Slide 68

Slide 68 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • •Flexibility: any object can be a key or value •Dynamicity and Resilience: default value/proc •Recursion-ish •Caching strategies What have we covered?

Slide 69

Slide 69 text

RUBYCONF 2020 • ARIEL CAPLAN • @AMCAPLAN • Please say hi in the Slack chat!