Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Enumerable: Surprised by Joy

Enumerable: Surprised by Joy

A survey of the many tools Enumerable/Enumerator have to offer to tighten up one's code.

Cade Truitt

January 02, 2014
Tweet

Other Decks in Technology

Transcript

  1. Ruby/Rails My wife and two kids Making and drinking beer

    The promise of making BBQ (new smoker!) Social media Hi! I’m Cade. These bring me joy:
  2. Why Joy? “Ruby is designed to make programmers happy.” –

    Yukihiro “Matz” Matsumoto. Joy is important to the human experience.
  3. Welcome to Ruby 5.times do puts 'hello world!' end for

    (var i = 0; i < 5; i++) { console.log('hello world!') }
  4. Because we use it a lot. Things that get used

    a lot are good candidates for mining joy. Why Enumerable/Enumerator?
  5. There’s a reason the corners are dusty. The deeper you

    go, the greater the diminishing returns. For me, the joy is worth diminishing returns. Disclaimers
  6. I love when elegant flirts with clever. Elegant vs clever

    is individual and debatable It’s worth team/community discussion
  7. Real Life Case Study Dave, How do you feel about

    using that rare, surgical Enumerable method?
  8. Law of the Instrument “I call it the law of

    the instrument, and it may be formulated as follows: Give a small boy a hammer, and he will find that everything he encounters needs pounding.” - Abraham Kaplan
  9. Law of the Instrument: #each #each is the hammer of

    the Ruby toolbox #each is the most overused Ruby method I think the documentation structure is partly to blame: Enumerable gets mixed in and is therefore documented separately
  10. “If no block is given, an enumerator is returned instead.”

    - throughout Ruby docs Enumerators are chainable. You can create your own Enumerators. You can do some really weird stuff with Enumerators. Let’s get to know our instrument
  11. #map - Transform Collections arr = [] Array('a'..'e').each { |n|

    arr << n.ord } arr Array('a'..'e').map { |n| n.ord } Array('a'..'e').map &:ord # => [97, 98, 99, 100, 101]
  12. #flat_map - #map#flatten must die arr = [{:people => [{:name

    => 'Ben'}, {:name => 'Jon'}]}, {:people => [{:name => 'Mary'}, {:name => 'Jane'}]}] arr.map { |obj| obj[:people] }.flatten arr.flat_map { |obj| obj[:people] } # user system total real # map.flatten 0.020000 0.000000 0.020000 ( 0.020121) # flat_map 0.010000 0.000000 0.010000 ( 0.007311)
  13. StackOverflow problem # from: wish_list = [["brewing", "grain", "barley"], [

    "brewing", "hops", "fuggle"], [ "bbqing", "meat", "pork"]] # to: wish_list = { 'brewing' => {'grain' => 'barley', 'hops' => 'fuggle'}, 'bbqing' => {'meat' => 'pork'} }
  14. #inject still wants to be cool res = wish_list.inject({}) do

    |r, v| group, key, value = v # array decomposition r[group] ||= {} # make sure group exists r[group][key] = value # set key/value in group r # return value for next end
  15. Legible? Ish. Joyful? Nope. hash = {} wish_list.each do |value|

    if hash[value[0]] hash[value[0]][value[1]] = value[2] else hash[value[0]] = {value[1] => value[2]} end end
  16. #each_with_object Replace variable assignment before #each hsh = {} Array('a'..'e').each

    {|l| hsh[l] = l.ord} hsh Array('a'..'e').each_with_object({}) {|l, h| h[l] = l.ord} # => {"a"=>97, "b"=>98, "c"=>99, "d"=>100, "e"=>101}
  17. Remember me? res = wish_list.inject({}) do |r, v| group, key,

    value = v # destructure in block args r[group] ||= {} # use hash defaults r[group][key] = value # THIS IS ALL THAT MATTERS r # poor, poor inject end
  18. I’m baaaaack hash = {} # each_with_object param wish_list.each do

    |value| # destructure to name values if hash[value[0]] # hash default hash[value[0]][value[1]] = value[2] # hash default else hash[value[0]] = {value[1] => value[2]} # ONLY I MATTER end end
  19. Drumroll please... wish_list.each_with_object(hashtastic) do |(a, b, c), h| h[a][b] =

    c end def hashtastic Hash.new {|hsh, key| hsh[key] = {}} end
  20. h = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) } h[:foo][:bar][:baz]

    = 'quux' # => {:foo=>{:bar=>{:baz=>"quux"}}} %i(a b c d e).inject(h) { |m, l| m[l] } h # => {:a=>{:b=>{:c=>{:d=>{:e=>{}}}}}} Autovivification (aka Hashception)
  21. #each_with_object gotchas [[1], [2], [3], [4]].each_with_object([]) do |num, arr| arr

    += num if num.first.even? end # => [] [[1], [2], [3], [4]].each_with_object([]) do |num, arr| arr.concat num if num.first.even? end # => [2, 4]
  22. Replacement for #map+#select arr = Array(1..1000) arr.map { |n| n*n

    }.select { |n| n % 7 == 0 } arr.each_with_object([]) do |n, arr| x = n*n arr << x if x % 7 == 0 end # user system total real # map select 1.970000 0.050000 2.020000 ( 2.045519) # each_with_object 1.430000 0.000000 1.430000 ( 1.439536)
  23. Oh, how I long to be a `for` loop... i

    = 0 ['bicycle', 'kitchen', 'pony'].each do |name| i += 1 puts "#{i}. #{name}" end
  24. Rails views, anyone? <%= i = 0 %> <%= @wizard.steps.each

    do |step| %> <h2>Step <%= i += 1 %></h2> <%= step.render_thyself %> <% end %>
  25. #group_by games = [['video', 'NBA JAM'], ['video', 'Zelda'], ['kids', 'hopscotch']].map

    { |type, name| OpenStruct.new(:type => type, : name => name) } games.group_by &:type # { # "video" => [#<OpenStruct type="video", name="NBA JAM">, #<OpenStruct type="video", name="Zelda">], # "kids" => [#<OpenStruct type="kids", name="hopscotch">] # }
  26. #index_by (Rails) games.index_by &:name # { # "NBA JAM" =>

    #<OpenStruct type="video", name="NBA JAM" >, # "Zelda" => #<OpenStruct type="video", name="Zelda">, # "hopscotch" => #<OpenStruct type="kids", name=" hopscotch"> # }
  27. #sort / #sort_by arr = %w(asdf asdf asd asd as

    as a a asd asdf as asdfg asdfg) * 5 arr.sort {|a,b| a.length <=> b.length} arr.sort_by &:length # user system total real # sort 0.280000 0.010000 0.290000 ( 0.286199) # sort_by 0.170000 0.000000 0.170000 ( 0.172123) Schwartzian Transform Performance reflects key complexity
  28. Compound #sort with array arr = [ OpenStruct.new(:name => 'Y',

    :position => 2), OpenStruct.new(:name => 'Z', :position => 3), OpenStruct.new(:name => 'B', :position => 1), OpenStruct.new(:name => 'A', :position => 1) ] arr.sort { |a,b| [b.position, a.name] <=> [a.position, b.name] }.map &: name # => ["Z", "Y", "A", "B"]
  29. strings = %w(aa b ccc dd ee) longest = ''

    strings.each { |str| longest = str if str.size > longest.size } longest # => 'ccc' strings.max {|a,b| a.size <=> b.size} #min #max #minmax with blocks
  30. Event better… #*_by strings.max_by &:size # user system total real

    # max 0.230000 0.000000 0.230000 ( 0.232399) # max_by 0.190000 0.000000 0.190000 ( 0.186871) Performance further deviates with complexity
  31. #count with block words = 'So long and Thanks for

    all the Fish.'.split capitalized_count = 0 words.each { |w| capitalized_count += 1 if w =~ /^[A-Z]/ } capitalized_count words.count { |w| w =~ /^[A-Z]/ }
  32. #all? / #any? / #none? without block arr = [:foo,

    nil, :bar] arr.all? # => false arr.any? # => true arr.none? # => false
  33. #grep Grep uses === internally arr = [:foo, 'bar', 1]

    arr.grep Integer # => [1] arr.grep String # => ['bar'] arr.grep Symbol # => [:foo] Array(1..10).grep 3..7 # => [3, 4, 5, 6, 7] (1..10).step(2).grep 3..7 # => [3, 5, 7]
  34. #grep with transform Grep yields item that matches to block

    arr = %w(watch out 4 this 2 get crazy) arr.grep /^\d/, &:to_i # => [4, 2] arr.grep /^[^\d]/, &:upcase # => ["WATCH", "OUT", "THIS", "GET", "CRAZY"]
  35. #inject with symbol arr = Array(1..100) arr.inject :+ arr.inject &:+

    arr.sum # Rails # user system total real # sym 0.070000 0.000000 0.070000 ( 0.060673) # proc 0.080000 0.000000 0.080000 ( 0.086482)
  36. #each_slice <% @resources.each_with_index do |resource, i| %> <% if i

    % 3 == 0 %> <div class="row"> <% end %> ... <% if i % 3 == 2 %> </div> <% end %> <% end %>
  37. #in_groups_of (Rails) Array(1..5).in_groups_of(3) # [[1, 2, 3], [4, 5, nil]]

    Array(1..5).in_groups_of(3, 'hi') # [[1, 2, 3], [4, 5, "hi"]] Array(1..5).in_groups_of(3, false) # each_slice # [[1, 2, 3], [4, 5]]
  38. #in_groups (Rails) Array(1..6).in_groups(4) # [[1, 2], [3, 4], [5, nil],

    [6, nil]] Array(1..6).in_groups(4, 'hi') # [[1, 2], [3, 4], [5, "hi"], [6, "hi"]] Array(1..6).in_groups(4, false) # [[1, 2], [3, 4], [5], [6]]
  39. #zip minuends = [6, 7, 8, 9, 10] subtrahends =

    [1, 2, 3, 4, 5] minuends.zip(subtrahends) # [[6, 1], [7, 2], [8, 3], [9, 4], [10, 5]] minuends.zip(subtrahends).map { |pair| pair.reduce :- } # [5, 5, 5, 5, 5]
  40. #lazy Added in 2.0, allows you to express algorithms to

    infinity without blowing up the computer squares = (0..Float::INFINITY).lazy.map {|n| n*n} # => #<Enumerator::Lazy: ...> squares.take 10 # => #<Enumerator::Lazy: ...> squares.take(10).force # => [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  41. Law of the Instrument: #each # via StackOverflow: "Finding a

    prime number using a **custom** Ruby method" # is_prime?(27,13,42) => {27=>false, 13=>true, 42=>false} def is_prime?(*nums) i = 2 nums.each do |num| while i < num is_divisible = ((num % i) == 0) if is_divisible == false x = "#{num}: is NOT a prime number." #false else x = "#{num}: is a prime number." #true end i +=1 end return x end end
  42. #each_with_object #upto #none? def is_prime?(*nums) nums.each_with_object({}) do |num, hsh| hsh[num]

    = num > 1 && 2.upto(num - 1).none? { |i| num % i == 0 } end end is_prime?(27,13,42) # => {27=>false, 13=>true, 42=>false}
  43. StackOverflow: because you can... # Find the common elements, 30k+

    rep user arr = [1,2,3].delete_if do |n| ![[2,4,5], [3,6,7]].any? do |m| m.include?(n) end end
  44. Chaining without a block e = Array('a'..'e').each.with_index # => #<Enumerator:

    #<Enumerator: ["a", "b", "c", "d", "e"]: each>:with_index> e.to_a # => [["a", 0], ["b", 1], ["c", 2], ["d", 3], ["e", 4]]
  45. Deferred Arguments e.to_a(5) # => [["a", 5], ["b", 6], ["c",

    7], ["d", 8], ["e", 9]] x = e.each(3) # => #<Enumerator: #<Enumerator: ["a", "b", "c", "d", "e"]: each>:with_index(3)> y = e.each(7) # => #<Enumerator: #<Enumerator: ["a", "b", "c", "d", "e"]: each>:with_index(7)>
  46. Mutate Collection x.each {|l,n| l << n.to_s} # acts like

    #map # => ["a3", "b4", "c5", "d6", "e7"] x #<Enumerator: #<Enumerator: ["a3", "b4", "c5", "d6", "e7"]: each>:with_index(3)>
  47. #to_enum Defer Required Args e = %w(a b c).to_enum :each_with_object

    h = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) } e.each(h) { |l,h| h[l][:ord] = l.ord; h[l][:upper] = l.upcase } # { # "a"=>{:ord=>97, :upper=>"A"}, # "b"=>{:ord=>98, :upper=>"B"}, # "c"=>{:ord=>99, :upper=>"C"} # } e.each('hi: ') { |l, s| s.concat l } # "hi: abc"