Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
README.md
ruby-enumerable-builtins_notes.pdf

README.md

LESSON: ruby-enumerable-builtins


2018-07-05

OBJECTIVES

By the end of this, developers should be able to:

  • Define “list” and give two examples of lists in Ruby.
  • Diagram the flow of Enumerable methods as invoked through concrete classes.
  • Give two examples of methods defined in Enumerable and available on all three of Range, Array, and Hash.

VOCABULARY

Namespace: For a give scope, the collection of references (ie names) and associated values. For instance, when you make a variable pet = "Inland Taipan" in a script, the namespace of your script now has a pet name in it.

Module: A collection of methods and constants in a namespace. The methods in a module may be instance methods or module methods. Instance methods appear as methods in a class when the module is included, module methods do not. Conversely, module methods may be called without creating an encapsulating object, while instance methods may not. Modules are never used directly to instantiate objects. Instead, they are "mixed-in" to class declarations so that a given class can inherit properties and methods from the module. This concept of a Ruby module is somewhat distinct from the concept of a Javascript module.

Mixin: A way to include all of a module's properties and methods inside another class / module. In Ruby, we simply put a include <Module name> line directly in the class where we want to "mix-in" a module.

List (Abstract Data Type): An ordered collection of values that can be set and get by index.

Enumerable Mixin: A Ruby Module that provides standard methods and properties that can be used by all list-type variables such as Array, Range and Hash, or even if your own custom classes that implement list-type objects.

Array: A list of objects who's values are defined at the time of instantiation using either Array.new or the [<value_1>, <value_2>, etc] syntax.

Range: A list of objects from some start value to some end value, of the form (<start value>..<end value>), (<start value>...<end value>), or Range.new(<start value>, <end value>). A Range object does not actually calculate the values in the list until you run the .to_a method on it, convering the Range to an Array, which can help save memory such that values are only calcualted right when the Range is about to be used, as opposed to being calculated when they are declared as in an Array. NOTE: ranges are start value inclusive and end value exclusive if you use ..., but is start value inclusive and end value inclusive if you use ..

Hash: A list of key and value pairs where the keys are all unique. Also called associative arrays. You can use any type of value as the key but symbol types are reccomended as they allow for much faster lookup of associated values than using other types such as String type keys. Hash objects are declared using either Hash.new or { :<symbol name 1> => <value 1>, :<symbol name 2> => <value 2>, etc } notation.

NOTES

  • In classical object oriented patterns, objects inherit methods and properties from their class declaration, and classes themselves can inherit from other classes. This chain of inheritance can be quite deep and hard to follow the more layers of inheritance there are.
  • Mixins allow for us to have "shallow inheritance", ie instead of a class inheriting from a class inheriting from a classs.... that gives our object some methods and properties, we can just make a new class and "mix-in" the properties and methods declared in a module.
  • Enumerable is a built-in module in Ruby that is "mixed-in" to Array, Range, and Hash types. This provides those types with all of the expected list methos and properties, and avoids some kind of complex deep inheritance structure where say Range would inherit from Array.
  • Ruby allows you to import values from the namespace of a module, script, or other closed block of code by using the :: namespace operator (see below)

EXAMPLES

Accessing Namespaces

  • you can access namespace variables using the :: operator:

  • class MyClass
        RANDOM_VAL = 300
        def initialize(thing1, thing2)
            @thing1 = thing1
            @thing2 = thing2   
    end
        
    # you can access the `RANDOM_VAL` value 
    puts MyClass::RANDOM_VAL
    # RETURNS: 
    # 300
    # => nil
    • note than only constants (non-method variables) can be accessed in the namespace and constants must be declared in all caps, ie RANDOM_VAL

Enumerable::each_with_object

  • each_with_object allows you to iterate through an enumerable variable and for each value, accumulate som change in an object of your choosing. Let's break down what is happening in each step of this method:

  • range = 5..115
    
    altogether = range.each_with_object(Hash.new(0)) do |el, hash|
      hash[:even] += el if el.even?
      hash[:odd] += el if el.odd?
      hash[:div3] += el if (el % 3).zero?
      hash[:all] += el
    end
    1. each_with_object takes a single argument, the object you want to accumulate values within (called memo short for memory in Ruby). In this case, it's a Hash which will provide a default value of 0 for new keys

    2. We also pass a block to each_with_object using the do keyword, and the block recieves to arguments from each_with_object:

      • the current element in the utterable - we called it el
      • the object / memo we are accumulating on - we called it hash
    3. Inside the block, we have 4 conditions for adding values to keys in hash. It is important to note that each key does not exist at first in our empty hash, and when they are first added, they have a value of 0.

      • if el is even, add it's value to hash[:even]
      • if el is odd, add it's value to hash[:odd]
      • if el is divisible by 3, add it's value to hash[:div3]
      • add every single value to hash[:all]
    4. Finally, the hash is returned from the method with keys even, odd, div3, and all where values for range have accumulated sums according to the rules above.

`Enumerable::reduce

  • reduce allows you to also accumulate values in an object while iterating through an enumerable type. The syntax is very similar to each_with_object with some key differences. Let's break down what is happening in each step of this method:

  • alphabet = {
      a: 1,
      b: 2,
      c: 3,
      d: 4,
      e: 5,
      f: 6,
      g: 7,
      h: 8,
      i: 9,
      j: 10,
      k: 11,
      l: 12,
      m: 13,
      n: 14,
      o: 15,
      p: 16,
      q: 17,
      r: 18,
      s: 19,
      t: 20,
      u: 21,
      v: 22,
      w: 23,
      x: 24,
      y: 25,
      z: 26
    }
    
    reduced_hash = alphabet.reduce({keys: [], values: []}) do |memory, (key, value)|
      memory[:keys] << key
      memory[:values] << value
      memory
    end
    1. reduce takes an initial value that will be accumulated in, in this case a hash with two keys that have empty arrays as values: keys, values
    2. the block for reduce gets passed two positional arguments: the memory object which is being accumulated on, and the current value of the enumerable object that is being iterated. In a Hash, iteration returns key value pairs, which can be directly broken into two variables in the arguments of the block using (keys, value)
    3. we then push the values of the current key and value to the approriate memory hash keys
    4. REMEMBER that you must return the accumulator hash memory at the end of the block or else it's changed values won't actually be saved for the next iteration
  • let's compare the reduce implementation to using each_with_object for the same task:

  • reduced_hash_two = alphabet.each_with_object({keys: [], values: []}) do |(key, value), memory|
      memory[:keys] << key
      memory[:values] << value
    end
    • notice how the input arguments are in reverse order in each_with_object
    • also, you don't need to return memory at the end of the block for it to be saved!