Skip to main content
Caleb-Mitchell

RB101: Notes

·36 mins

Lesson 1: Preparations #

Launch school forums use Markdown, specifically Github flavored markdown -check the ‘Formatting Help’ link, when making a post to check -surround code with backticks (preview it of course)

https://github.github.com/gfm/

Be sure to always ask good questions

https://launchschool.com/lessons/c82cd406/assignments/abae2b83


Ideas for study sessions #

  • Topic Presentation. Pick a particular topic or programming concept, and briefly present on that topic to the rest of the group using a combination of explanations and/or code examples. Have the rest of the group ask questions and give feedback.
  • Code Example Questions. Have each student in the study group prepare a few code examples which illustrate or explore a particular concept. Take turns to ask the rest of the group about your examples, e.g. ‘what will happen when this code is run?’, ‘what will line 5 output?’, etc.
  • Pair Problem Solving. With a study partner, pick a coding problem that neither of you have solved before and solve it together. Discuss the problem and approach to solving it, and iterate on an approach.
  • Live Coding Practice. With a study partner, both of you choose a problem that the other has not seen before and solve it ahead of time. Then, on a call, your partner can give you a problem (that is new to you) to solve, and you can then give them a problem (that is new to them) to solve. Practice explaining your thought process, why you’re choosing certain methods, etc.
  • Flash-card Quiz. Prepare a flash card deck on a particular topic or concept. Use these cards in the session to quiz your study partner or study group.
  • use vscode live coding, https://app.coderpad.io/sandbox, or repl.it

Exercises: Small Problems #

We recommend that you do around 20 exercises (or 2 sets) after each lesson (no need to complete the Advanced exercises set).


The two layer problem #

  • learning to solve problems while simultaneously memorizing the syntax of a particular language - is hard!

  • we need to use PEDAC

    • *P Understand the Problem
      • identify expected input and output
      • make the requirements explicit
      • identify rules
      • mental model of the program (optional) *mental model is summary of entire problem, What it requires not How to solve it
    • *E Write Examples/Test Cases
      • validate understanding of the problem
    • *D Data Structure
      • how we represent data that we will work with when converting the input to output
    • *A Algorithm
      • steps for converting input to output
    • *C Code (with intent)
      • implementation of algorithm
  • the goal is to try and separate the logic concern from the syntax concern

Common pitfalls of hack and slash or coding without intent: #

  • Missed requirements
  • Unforeseen Edge Cases
  • Hard to understand code
  • Code that’s difficult to maintain

Lesson 2: Small Programs #

For this lesson, always use parenthesis for method calls, although they are normally optional

Like everything else in Ruby, boolean objects also have real classes behind them, and you can call methods on true and false.

true.class          # => TrueClass
true.nil?           # => false
true.to_s           # => "true"
true.methods        # => list of methods you can call on the true object

Ruby is a very liberal language and considers everything to be truthy other than false and nil.

This means that even the integer 0 is considered truthy, which is not the case in some other languages.

Remember that && short circuits if it encounters a false, and nil is considered “falsy”.

Remember this rule: everything in Ruby is considered “truthy” except for false and nil.


Pseudo-code is meant for humans to read.

We don’t use programming code first, because we’re trying to load the problem into our brain first.

*keyword 	            *meaning
START 	                 start of the program
SET 	                 sets a variable we can use for later
GET 	                 retrieve input from user
PRINT 	                 displays output to user
READ 	                  retrieve value from variable
IF / ELSE IF / ELSE 	   show conditional branches in logic
WHILE 	                 show looping logic
END 	                  end of the program

SUBPROCESS label to show that there is some other thing that will take care of this process

Good to code in casual format, then in formal pseudo-code using keywords


Using a flowchart helps us map out the logical sequence of a possible solution in a visual way. #

  • oval - start/stop
  • rectangle - processing step
  • rhombus - input/output
  • diamond - decision
  • circle - connector

Arrows show the “flow” of logic, from shape to shape

Mapping out the step by step logic of our program is called the imperative or procedural way of solving a problem

using a method in a higher level programming language like ruby, which encapsulates a basic concept into that method, like each, is a declarative way of solving a problem

be imperative in flowcharts (no declarative constructs)

helps you to “think like a computer”

try and keep your train of thought at the logical level and not think about code.

As you use pseudo-code and flowcharts to help you dissect the logic of a problem, you’ll be constantly trying to figure out how detailed the chart and words should be, and what can be extracted to sub-processes. This is exactly what a programmer should be thinking about when designing the solution to a problem. You won’t get it right the first time.

Start at a high level, using declarative syntax. For example, if you’re working on a calculator, you can start with something like this:

  • Get the first number
    • Make sure it’s valid, otherwise, ask for another
  • Get the second number
    • Make sure it’s valid, otherwise, ask for another
  • Get the operator
    • Make sure it’s valid, otherwise, ask again
  • Perform operation on the two numbers
  • Display result
  • Ask if user wants to do another calculation

What makes a good programmer? #

if the key to programming is debugging, then the key to debugging is having a patient and logical temperament

steps to debugging(identify, understand, fix) #
  1. reproduce the error
  2. determine the boundaries of the error
  3. trace the code -need to ‘trap the error’ (isolating it’s location)
  4. understand the problem well
  5. implement a fix -fix one problem at a time -care not to just use a trailing rescue to cover up an error if it can be avoided, this is usually a code smell that you hadn’t though carefully about the possible problems that could go wrong, and therefore you haven’t through about how to handle the potential error conditions
  6. test the fix
techniques for debugging #
  1. line by line -be patient
  2. rubber duck
  3. walking away -make sure you first spend time loading the problem in your brain
  4. using pry
  5. using a debugger

In summary, debugging is arguably the most important skill you need to learn as a programmer. Focus on developing a patient, systematic temperament; carefully read error messages; use all the wonderful resources at your disposal; approach debugging in sequential steps; and use the techniques we covered above – especially Pry. If you haven’t yet, go install Pry now and play around with it a little bit.


The meaning of an expression in Ruby is determined by what is called operator precedence. #

-Don’t rely too much on precedence. It’s easy to forget the precedence order and get confused by an unexpected result. If you’re using 2 or more different operators in an expression, use parentheses to explicitly define the meaning.

An operator that has higher precedence than another is said to bind more tightly to its operands.

a {} has slightly higher priority than a do…end block, which could effect how a block gets called

ruby’s tap method -object instance method tap, useful debugging tool

array = [1, 2, 3] mapped_array = array.map { |num| num + 1 }

mapped_array.tap { |value| p value } # => [2, 3, 4]

use parentheses, don’t rely on the precedence rules


Loan Calculator #

  • make nice header with dashes
  • uses prompt function is good
  • can test only if numbers are empty or positive float
    • why isn’t more validation necessary?
  • use main loop to ask if another calculation is desired
  • show desired format in prompts (5 for 5%, (in years))
  • save more specific variables so no transformations necessary in larger equation
  • use Kernel#format to format currency output to user

Coding Tips #

  • debug for hours, commit to the time and repitition, get burned and learn :)

Naming Things #

  • choose descriptive variable and method names
    • variables are names not for how they are set, but for what they actually store

Naming Conventions #

  • snake_case for everything except classes which are CamelCase, and constants which are UPPERCASE

Mutating Constants #

  • don’t change the value of constants

Methods #

  • make sure that methods do one thing, with very limited responsibility
  • keep them short, maybe 10 lines, 15 max
    • if too long, split it into 2 or 3 methods
How to write good methods #
  • don’t display something to the output
  • return a meaningful value
    • since ruby always returns a value, the key here is that the return value shouldn’t be the intent of the method
  • decide whether the method should return a value with no side effects, or perform side effects with no return value
    • the method name should reflect whether it has side effects or not (for example, some ruby methods end with ! if they have side effects)
  • in ruby, we would not call a method return_total, it would just be total, returning a value is implied

Methods should be at the same level of abstraction #

  • you should be able to mentally extract the method from the larger program, and just work with the method in isolation
    • when you have a method like this, you can use it without thinking about its implementation

Method names should reflect mutation #

  • update_total implies that the parameter passed in to it will be mutated, we would not expect to see something like total = update_total(total, cards)
  • goal should be to build small methods that are like LEGO blocks, stand-alone pieces of functionality that you can use to piece together larger structures.
  • some methods will be convoluted, but as our understanding grows, we should be able to refactor it later
    • it’s alright to start with a less organized, exploratory first draft

Displaying output #

  • good practice to preface method names that will return a string or output strings with something like print_, say_, or display_

Miscellaneous Tips #

  • Don’t prematurely exit the program, programs should only have one exit point
  • 2 spaces, not tabs
  • name your methods from the perspective of using them later! think about how you would like to invoke them
  • know when to use a “do/while” vs a “while” loop
  loop do
    puts "Continue? (y/n)"
    answer = gets.chomp
    break if answer.downcase == 'n'
  end
  • this is prefered to a while loop, as a while looop would require answer to be initalized outside the loop, and so it’s slightly easier to reason with
  • always clarity over terseness

Truthiness #

  • in Ruby, more than just true evaluates to true in a conditional

In Ruby, everything is truthy except nil and false

  • avoid “assignment within a conditional code”

Approach to Learning #

  • learning takes focus and attention, and repition over a long period
  • don’t be demoralized if you do something once and you can’t remember most of it, that’s normal

Variable Scope #

Variables and Blocks #

  • a block is do...end or {..} following a method invocation
    • do..end for multi-line blocks and {..} for single line blocks
  • blocks create a new scope for local variables (inner scope)

A method definition has no notion of “outer” or “inner” scope – you must explicitly pass in any parameters to a method definition.

  1. outer scope variables can be accessed by inner scope
  • also, you can change variables from an inner scope and have that change affect the outer scope
  • have to be careful when instantiating variables in an inner scope, to not accidentally re-assign an existing variable in an outer scope, big reason to avoid single-letter variable names
  1. inner scope variables cannot be accessed in outer scope
  • remember that where a variable is initialized determines its scope
  1. peer scopes do not conflict
  • we could use the same variable name a in two different blocks of code, however it will be two different variables
  1. nested blocks
  • nested blocks follow the same rules of inner and outer scoped variables
  • only difference is vocabulary, switching from “outer” or “inner” to “first level”, “second level”, etc..
  1. variable shadowing
  • variable shadowing prevents access to an outer scope local variable, when an block is block parameter shares a name with a variable in outer scope
  • choose long and descriptive names instead

Variables and Method Definitions #

A method definition has no notion of “outer” or “inner” scope – you must explicitly pass in any parameters to a method definition.

  1. a method definition can’t access local variables in another scope
  2. a method definition can access objects passed in

If a local variable and a method were to share the same name, Ruby will first search for the local variable if there is one, if one is not found then Ruby will try to find a method with that name. If neither is found then a NameError is thrown.

  • to remove some ambiguity, we can indicate a method specifically by including a set of empty argument parentheses with the method invocation, i.e. replacing puts hello with puts hello(), although there is no analogue for explicitly calling a local variable

Blocks within Method Definitions #

  • the rules of scope for a method invocation with a block remain in full effect even if we’re working inside a method definition

Constants #

  • the scoping rules for constants is not the same as local variables. In procedural style programming, constants behave like globals
    • they can be accessed by any inner scope, unlike local variabels, and are said to have lexical scope

More Variable Scope #

  • two key terms are method definition and method invocation

  • method definition is when, within our code, we define a ruby method using the def keyword

  • method invocation is when we call a method, whether that happens to be an existing method from the Ruby Core API or core Library, or a custom method that we’ve defined ourselves using the def keyword.

A block is defined as method invocation followed by curly braces or do..end

  • An important take away for now is that blocks and methods can interact with each other, the level of that interaction is set by the method definition and then used at method invocation
  • Given this additional context:
    • we can think of method definition as setting a certain scope for any local variables in terms of the parameters that the method definition has, what it does with those parameters, and also how it interacts (if at all) with a block
    • We can then think of method invocation as using the scope set by the method definition

Pass by Reference vs Pass by Value #

Ruby is in a way both pass by value and pass by reference

  • “pass by value” traditionally means that when you pass a method an object, you are only giving it a copy of the original object. This is how methods always work in C for example, and is shown in ruby with re-assignment within a method. variable re-assignment within a method doesn’t affect the object outside of the method, so it is an example of “pass by value”
  • “pass by reference” is used to describe when operations within a method are aboe to affect the original object. it is not only the appropriate value passed into the method, but a reference to the true location of it’s value. this is shown in Ruby by the use of a destructive method within a method, and how it is able to mutate the caller from within a method, even though it is outside the scope of a method
  • some refer to Ruby’s combination of these two behaviors as “pass by value of the reference” or “call by sharing”

Most importantly, remember, when an operation within the method mutates the caller, it will affect the original object

  • ! is a naming convention to end methods in Ruby which are destructive, but this is just a convention, not a rule (for example, Array#<< is destructive, but doesn’t end with !)
  • In Ruby, numbers and boolean objects are immutable, as well as a few other types
  • Most objects are mutable!
    • the ability to mutate depends in part on the mutability or immutability of the object represented by the argument, but also one how the argument is passed to the method
      • immutable objects act like Ruby passes them around by value
      • mutable objects act like Ruby passes them around by reference
      • all methods are non-mutatiing with respect to immutable ojects, and assignment is non-mutating

several common methods that sometimes cause confusion, as they mutate the caller but don’t end with !: #[]=, #<<, setter methods, String#concat

  • << is a method defined for some classes like collections and strings, careful though, it might also represent non-mutating methods for other classes
  • Setters are mutating, careful, they superficially look like assignments
    • with indexed assignmment, the elements of a collection or string are replaced. with setters, the state of the object is altered, usually by mutating or reassigning an instance variable
    person.name = 'Bill'
    person.age = 23
    

    this looks like assignment, but are mutating setter calls, they mutate the object bound to person

  • assignment in Ruby acts like a non-mutating method, it doesn’t mutate any objects, but does alter the binding for the target variable
    • howver, the syntantically similar indexed assignment and object setter operations are mutating
  • almost everything in Ruby is an object: literals, named objects (variables and constants), complex expressions
  • methods can include methods, blocks, procs, lambdas, and even operators
  • arguments can include actual arguments, the caller of the method, operator operands, or a return value
  • every computer language uses some sort of evaluation strategy when passing objects, and the most common strategies are known as strict evaluation strategies. Ruby uses strict evalaluation exclusively, and this means that every expression is evaluated and converted to an object before it is passed along to a method.
    • two most common strict evaluation strategies are pass by value and pass by reference, referred to as object passing strategies
  • pass by reference isn’t limited to mutating methods. A non-mutating method can use pass by reference as well, so pass by reference can be used with immutable objects. There may be a reference passed, but the reference isn’t a guarantee that the object can be mutated.
    • Given all of this, it’s not uncommon to just say that ruby is pass by reference value or pass by value of the reference
    • ruby passes around copies of the references! its a blend of the two evaluation strategies

Ulimately, the answer is that Ruby uses pass by reference value

  • pass by reference would be accurate if you don’t account for assignment and immutability
  • Ruby acts like pass by value for immutable objects, and appears to act like pass by reference for mutable objects, but it’s really pass by reference value

Coding Tips 2 #

Using New lines to organize code #

  • organize code into chunks to make it easier to read, using new lines

Making your code readable is of paramount importance, not only for others, but for future self

Should a method return or display? #

Understand if a method returns a value, or has side effects, or both

  • side effects could be either displaying something to the output, or mutating an object
  • avoid writing methods that do both!

Name methods appropriately #

  • preface names of methods that output value with display_ or print_

If you find yourself constantly looking at a method’s implementation every time you use it, it’s a sign that the method needs to be improved

  • a method should do one thing, and be named appropriately

Don’t mutate the caller during iteration #

Don’t mutate a collection while iterating through it, or else you’ll get unexpected behavior (although of course you can mutating elements of the collection while iterating through it)

Variable Shadowing #

Don’t do it, be careful about choosing appropriate block variables, make it unique, rubocop will catch this :)

Don’t use assignment in a conditional #

  • Never use assignment in a conditional, as it isn’t clear whether you meant to use == or if you indeed meantt o do assignment.
  • if you must, like maybe with a loop such as while num = numbers.shift, wrap the assignment in parentheses, to signify to future programmers (and yourself) that you know what you’re doing and it’s done on purpose: while (num = numbers.shift)
  • just don’t do it though

Use underscore for unused parameters #

  • Suppose you have an array of names, and you want to print out a string for every name in the array, but you don’t care about the actual names. In those situations, use an underscore to signify that we don’t care about this particular parameter.
names = ['kim', 'joe', 'sam']
names.each { |_| puts "got a name!" }
  • Or, if you have an unused parameter when there are multiple parameters:
names.each_with_index do|_, idx|
  puts "#{idx+1}. got a name!"
end

Gain experience through struggling #

  • Don’t memorize “best practices”, but spend enough time programming to the point where you understand the context for those practices.
  • Don’t be fearful of violating rules or afraid to make mistakes, but keep an eye out for improvements
  • spend the time!!, struggle, search, play around

Quiz notes #

  • Q - 10: from the discussion forums, for and while loops are expressions, not method calls, so don’t form blocks. it looks like loop..do is a method call though and forms a block?
  • Q - 16: I’m stupid, Using a method that mutates the caller does not change the memory address that the variable is pointing to, this remains the same

SPOT session RB109 assessment notes #

  • make a template of explanations of ‘all’ concepts i would need to talk about in written assessment, can copy/paste specific values into on the fly

example of template entry below!

  #### `Array#map`

 On line 9 the local variable `array` is initialized to the *Array* object ` [1, 2, 3, 4, 5]`. On line 11 the `map` method is invoked on the object referenced by the local variable `array`.The block is defined by the `{}` curly braces along side the `map` method invocation.  `Array#map` method iterates over the array object it is called on and executes the `block` for each element in the array. The `map` method returns a new array with elements transformed based on the return value of the block. 

For every iteration the `map` method passes the current element as an argument to the block which is assigned to the block parameter `num`.

The `map` method iterates through the collections and returns a new collection transformed based on the return value of the block.
  • will defintely be using coderpad for live assessment, get used to it

  • loop is a method invocation, and it takes a block

    • define a block
  • reference statements you’ve already made “as explained before”

  • start with the call stack

  • github markdown, use single backticks for variables and things

  • practice talking through code, line by line

    • focus on each concept, line by line
  • 3 concepts to be super strong on

    • iteration
      • each (returns the original collection)
    • selection
      • select (looks for ‘truthyness’ of the return value of the block, things that evaluate to true)(returns a new array)
    • transformation
      • map (returns a new array)
      • ‘map method is called on the object referenced by the local variable array’
    • also
      • dupe vs clone
      • truthyness
      • hash vs array
      • mutating vs non mutating methods
  • make sure algorithm matches the code! update it as you go, as necessary

  • scan is the opposite of split!

  • run the code locally before talking about it, just to be sure

  • enumerator is like a box with things inside, you have to open it

  • study with as many people as you can, get their pedac!!

Lesson 4 #

Collections Basics #

Element Reference #

  • strings use an integer-based index that represents each character in the string, starting at 0:
    • you can reference a specific character using this index
str = 'abcdefghi'
str[2] # => "c"
  • you can also reference multiple characters within a string, by using an index starting point, and the number of characters to return
str[2, 3] # => "cde"
  • this is a syntactical sugar call to slice i.e. str.slice(2, 3)
  • because methods always have a return value, we can use method chaining to call another method on the return value
str[2, 3][0] # => "c"
# this is effectively the same as 'cde'[0]

str = 'The grass is green'
str[4, 5] # => "grass"
str.slice(4, 5) # => "grass"

Array Element Reference #

  • Like strings, arrays are also ordered, zero-indexed collections
  • arrays are lists of elements that are ordered by index, where each element can be any object
    • specific elements can be referenced using their indices, just like strings
    • just like strings, can use arr[x, y] as alternative syntax for the Array#slice method

Important to remember, that Array#slice and String#slice are not the same method!

  • String#slice returns a new string, Array#slice returns a new array!!!
  • Be careful with the different returns from the Array#slice method:
arr = [1, 'two', :three, '4']
arr.slice(3, 1) # => ["4"]
arr.slice(3..3) # => ["4"]
arr.slice(3)    # => "4"

Hash Element Reference #

  • hashes use key-value pairs, instead of an integer-based index
hsh = { 'fruit' => 'apple', 'vegetable' => 'carrot' }

hsh['fruit']    # => "apple"
hsh['fruit'][0] # => "a"
  • when initializing a hash, the keys must be unique
  • values however can be duplicated
  • hash keys and values can be any object in Ruby, but it is common practice to use symbols as the keys. symbols can be thought of as immutable strings

Element Reference Gotchas #

Out of Bounds Indices #
  • referencing an out-of-bounds index of a string returns nil:
    • no big deal, nil is obviously an invalid return value for a string
  • referencing an out-of-bounds index of an array also returns nil:
    • have to be careful, as arrays can contain any type of object, including nil
    • Array#fetch throws an IndexError exception if the index is out of bounds. This is very helpful, as a regular index could be misleading if out of bounds. If you want to be safe, use #fetch
Negative Indices #
  • Negative indices references elements in String and Array objects starting from the last index in the collection -1 and working backwards
Invalid Hash Keys #
  • Hash also has a #fetch method, which can also be useful when trying to diambiguate valid hash keys with a nil value from invalid hash keys

Conversion #

  • Because strings and arrays share similarities, there are ways to convert them from one to the other, such as String#chars and Array#join:
    • String#chars returns an array of individual characters
    • Array#join returns a string wtiht the elements of the array joined together
    • hash has a #to_a method which returns an array (returns nested array of key-value pairs)
    • array similarly has a #to_h method (returns a hash, from a nested array)

Element Assignment #

String Element Assignment #
  • we can use the element assignment notation of String to change the value of a specific character within a string by referring to its index
str = "joe's favorite color is blue"
str[0] = 'J'
str # => "Joe's favorite color is blue"
Array Element Assignment #
  • similar of course to string element assignment
arr = [1, 2, 3, 4, 5]
arr[0] += 1
arr[3] = 9
arr     # => [2, 2, 3, 9, 5]
Hash Element Assignment #
  • similar again, but you use the hash key instead of an index when assigning a value
hsh = { apple: 'Produce', carrot: 'Produce', pear: 'Produce', broccoli: 'Produce' }
hsh[:apple] = 'Fruit'
hsh # => { :apple => "Fruit", :carrot => "Produce", :pear => "Produce", :broccoli => "Produce" }

Looping #

Break placement #

  • if break is placed on the last line within a loop call, this mimics the behavior of a “do/while” loop. the code within the block is guarenteed to execute at least once
  • if break is placed on the first line within a loop call, this mimics the behavior of a while loop. the code below break may or may not execute at all, depending on the condition
Looping over a hash #
  • two step process:
    • create an array of keys using Hash#keys, then iterate over that array(using a simple counter variable), saving each key into a new variable
    • use that new variable to retrieve the appropriate value out of the hash

Summary #

  • Looping comprises four basic elements:
    1. a loop
    2. a counter
    3. a way to retrieve the current value
    4. a way to exit the loop

Introduction to PEDAC process #

  • following the PEDAC process saves time and lets you solve complex problems efficiently

P - [Understand the] Problem #

  • three steps:

    1. read the problem description
    2. check the test cases, if any
    3. if any part of the problem is unclear, ask the interviewer or problem requester to clarify the matter
  • clarifying questions:

    • problem domain, do i understand?
    • test case issues, like zero or empty inputs
    • any assumptions I can safely make?
    • are there any assumptions I’m making?:
      • do i need to return the same object or a new one???
      • always verify assumptions either by looking at the test cases or by asking the interviewer

Data Structure / Algorithm #

  • formal pseudocode is not always necessary, but can sometimes be helpful
  • you don’t need to write all your pseudocode before you start coding:
    • fine to include methods in pseudocode that you will write pseudocode to help implement after the fact
  • you should be able to write a plain English solution to the problem
    • if you can’t do that, you won’t be able to code it either, you don’t need “fancy” methods to solve these problems
  • avoid implementation detail! you risk getting locked into a particular approach or way of thinking about the problem
    • also don’t worry about the efficiency of the algorithm

Testing Frequently #

  • test your code early and often while writing it
    • don’t wait!!

Big Picture Thoughts #

  • Not a completely linear process
  • Move back and forward between the steps
  • Switch from implementation mode to abstract problem solving mode when necessary
  • Don’t try to problem solve at the code level

Selection and Transformation #

  • Selection is picking certain elemenents out of the collection depending on some criterion
  • Transformation is manipulating every element in the collection

Looping to Select and Transform #

When performing transformation, it’s always important to pay attention to whether the original collection was mutated or if a new collection was returned.

More Flexible Methods #

By defining our methods in such a way that we can pass in additional arguments to alter the logic of the iteration, we can create more flexible and generic methods.

Summary #

  • Using the three actions iteration, selection, or tranformation, we can manipulate a collection nearly any way we need to
  • pay attention to when the original collection is mutated vs when the method returns a new collection
  • understand how these methods can be made more generic by allowing for additional parameters to specify some criteria for selection or transformation

Methods (each, select, and map) #

  • each is functionally equivalent to using loop, and represents a simpler way of accompishing the same task
    • each returns the original collection! (loop returns nil, or anything you send to break instead)
  • select is a built-in way for arrays and hashes to iterate over a collection and perform selection
    • select evaluates the return value of the block, and only cares about its truthiness
    • select returns a new collection containing all of the selected elements
    • when using select, always be aware of the return value of the block!!
      • if the block evaluates to a “falsey” value, such as a puts call returning nil, the select call would return an empty array
  • map also considers the return value of the block, like select, but instead *uses the return value of the block to perfrom transformation instead of selection
    • map returns a new collection! (a map!)
    • it always performs transformation based on the return value of the block

rememeber, certain collection types have access to specific methods for a reason (select and map are defined in the Enumerable module and are available to the Array and Hash classes, cannot call select or map on a string, but could always call String#split first to make that possible

Method Action Considers the return value of the block? Returns a new collection from the method? Length of the returned collection
each Iteration No No, it returns the original Length of original
select Selection Yes, its truthiness Yes Length of original or less
map Transformation Yes Yes Length of original

More Methods #

  • With many methods, such as Enerable#any?, you need to be aware of 2 return values, the return value of the method, and the return value of the block
    • any? looks at the truthiness of the block’s return value in order to determine what the method’s return value will be
      • can be used with a hash, just need to pass two parameters in order to access both the key and the value
    • Enerable#all? is similar to any?, also looks at truthiness of the block’s return value, but method only returns true if the block returns true in every iteration
    • Enumerable#each_with_index, nearly identical to each, block’s return value is ignored, but takes a second argument representing the index of each element
      • when calling on a hash, the first argument now represents an array containing both the key and the value
{ a: "ant", b: "bear", c: "cat" }.each_with_index do |pair, index|
  puts "The index of #{pair} is #{index}."
end

# The index of [:a, "ant"] is 0.
# The index of [:b, "bear"] is 1.
# The index of [:c, "cat"] is 2.
# => { :a => "ant", :b => "bear", :c => "cat" }
  • Enumerable#each_with_object takes a block like above, but also takes a method argument, which is a collection object that will be returned by the method. Additionally, the block takes two arguments of its own:
    • the first block argument represents the current element, and the second represents the collection object that was passed in as an argument to the method.
[1, 2, 3].each_with_object([]) do |num, array|
  array << num if num.odd?
end
# => [1, 3]
  • Enumerable#first doesn’t take a block, but does take an optional argument which represents the number of elements to return. If no argument given, it returns only the first element in the collection
  • Enumerable#include? doesn’t take a block, but does require one argument. It returns true if the argument exists in the collection and false if it doesn’t.
    • when called on a hash, include? only checks the the keys, not the values
    • essentially an alias for Hash#key?, should probably use Hash#key? instead, as the intention is more explicit.
  • Enumerable#partition divides up elements in the current collection into two collections, depending on the block’s return value, and the most idiomatic way to use partition is to parallel assign variables to capture the divided inner arrays
odd, even = [1, 2, 3].partition do |num|
  num.odd?
end

odd  # => [1, 3]
even # => [2]
- always returns an array
  • method documentation will normally include:
    • one or more method signatures, indicating arguments, block or no block, and what it returns
    • a brief description of how the method is used
    • some code examples

Quiz Mistakes #

  • Enumerable#map
    • “If map was called with a block that returned nil on every iteration, it would return an empty array.”
    • NOT TRUE, the return value in this situation would be an array containing nils; one for each item in the original array. The array would be the same size as the original, but would be filled with nils, not empty like select would return with falsey values.
  • Enumerable#select
    • “If select was called on an array with a block that returned a truthy value on each iteration, the original array would be returned”
    • NOT TRUE, select always returns a NEW ARRAY! Might be the same original values contained therein, but a new array nonetheless.

Lesson 5: Advanced Ruby Collections #

Sorting #

  • Sorting is mostly performed on arrays, since items in arrays are accessed via their index
  • Strings don’t have access to sorting methods, but it’s easy to convert them to an array first
  • Since Ruby 1.9 it is possible to sort a hash, though there generally isn’t a need to do this

What is sorting? #

  • sorting is setting the order of the items in a collection according to certain criterion

Comparison #

  • sorting is carried out by comparing the items in a collection with each other, it is at the heart of how sorting works

The <=> method (the “spaceship” operator) #

  • any object in a collection that we want to sort must implement a <=> method, this performs comparison and returns a -1, 0, or 1
  • if <=> returns nil to sort then it throws an arguemnt error
  • all the sort method cares about it the return value of the <=> method
  • if you want to sort a collection that contains particular types of objects, you need to know two things:
    1. does that object type implement a <=> comparison method?
    2. if yes, what is the specific implementation of that method for that object type (i.e. String#<=> is implemented differently than Integer#<=>
      • String order is determined by a character’s position in the ASCII table
      • careful, uppercase A precedes a in ASCIIbetical order
      • similarly, careful with symbols
      • can call ord on a string to determine it’s ASCII position
    • some useful rules are:
      • uppercase letters come before lowercase letters
      • digits and (most) punctuation come before letters
      • there is an extended ASCII table containing accented and other characters, this comes after the main ASCII table

The sort method #

  • we can also call sort with a block, giving us more control over how items are sorted. the block needs two arguments (the two items to be compared), and the return value of the block has to be -1, 0, or nil
    • you can add addional code in the block, as long as the block returns -1, 0, or nil
[2, 5, 3, 4, 1].sort do |a, b|
  puts "a is #{a} and b is #{b}"
  a <=> b
end
  • String#<=> compares multi-character strings character by character (like casecmp), so strings beginning with a will come before those beginning with b, which matters more than string length
  • Array#<=> works similarly, going index by index - “in an element-wise manner”
    • if an int and a string are compared, an error would be thrown by sort, but it’s possible the comparison will short-circuit first and the offending comparison would never be made

The sort_by method #

  • sort_by is similar to sort but is usually called with a block, the code in the block determines how the items are compared
['cot', 'bed', 'mat'].sort_by do |word|
  word[1]
end
# => ["mat", "bed", "cot"]
  • sort_by could be used to sort a hash, two arguments would need to be passed to the block, the key and the value
    • sort_by always returns an array, even when called on a hash
  • Array#sort and Array#sort_by have equivalent destructive methods sort! and sort_by!. these are specific to arrays and not available to hashes
  • other methods which use comparison:
    • min
    • max
    • minmax
    • min_by
    • max_by
    • minmax_by

Summary #

  • sorting is complex algorithmicly to implement yourself, but we can use the built-in sort and sort_by methods to do it for us
  • comparison is at the heart of sorting. when sorting collections, you need to know if the objects you want to sort on implement a <=> method and how that method is defined
  • methods other than sort and sort_by also use comparison as the basis for how they work

Nested Data Structures #

  • collections can contain other collections

Referencing collection ellements #

  • arr[0][1] # -> 3

Updating collection elements #

arr = [[1, 3], [2]]
arr[0][1] = 5

careful, this is element reference, followed by element update

Other nested structures #

  • hashes can be nested within an array as well
  • careful, arrays can contain any type of ruby object, including multiple different objects at the same time, including nested data structures
arr = [['a', ['b']], { b: 'bear', c: 'cat' }, 'cab']

arr[0]              # => ["a", ["b"]]
arr[0][1][0]        # => "b"
arr[1]              # => { :b => "bear", :c => "cat" }
arr[1][:b]          # => "bear"
arr[1][:b][0]       # => "b"
arr[2][2]           # => "b"

Variable reference for nested collections #

  • careful, variables are pointers!!
a = [1, 3]
b = [2]
arr = [a, b]
arr # => [[1, 3], [2]]

a[1] = 5
arr # => [[1, 5], [2]]

Shallow copy #

  • ruby provides us with two methods that allow us to copy an object, including collections:
    • dup and clone
    • these both create a shallow copy of an object
    • this means that only the object that the method is called on is copied, if the object contains other objects - like a nested array - then those objects will be shared, not copied.
    • dup allows objects within the copied object to be modified
arr1 = ["a", "b", "c"]
arr2 = arr1.dup
arr2[1].upcase!

arr2 # => ["a", "B", "c"]
arr1 # => ["a", "B", "c"]
  • clone works the same way
arr1 = ["abc", "def"]
arr2 = arr1.clone
arr2[0].reverse!

arr2 # => ["cba", "def"]
arr1 # => ["cba", "def"]
  • careful, in these examples both arr1 and arr2 are changed
  • this is because the destructive methods of the examples are called on the object within the array rather than the array itself! the objects are shared and the variables still point to the same collection
  • careful careful, are you modifying an array or hash, or at the level of the object within those collections?
    • for example, calling map! on a copied array would change the array itself, and not the original array… but, called upcase! on each object within an array by means of an each call would mutate each object, as referenced by both arrays

Freezing Objects #

  • the main difference between dup and clone is that clone preserves the frozen state of the object, and dup doesn’t
  • in Ruby, objects can be frozen in order to prevent them from being modified
  • freeze only freezes the object it’s called on. if the object it’s called on contains other objects, those objects will not be frozen.
    • for example, if you freeze a nested array, the nested objects could still be modified after calling freeze
    • this also applies to strings within an array
arr = [[1], [2], [3]].freeze
arr[2] << 4
arr # => [[1], [2], [3, 4]]
arr = ["a", "b", "c"].freeze
arr[2] << "d"
arr # => ["a", "b", "cd"]

Deep Copy #

  • In Ruby, there’s not built-in or easy way to create a deep copy or deep freeze objects within objects.
    • when working with collections, especially nested collections, be aware of the level within the collection at which you are working

Working with Blocks #

  • when evaluating seemlingly complex code, ask the following questions:

    • what is the type of action being performed? (method call, block, conditional, etc?)
    • what is the object that action is being performed on?
    • what is the side-effect of that action (e.g. output or destructive action)?
    • what is the return value of that action?
    • is the return value used by whatever instigated the action?
  • Do Not Mutate The Collection That You Are Iterating Through

    • one way to avoid this, is creating a shallow copy of an array, and iterating through the copy while mutating the original

Summary #

  • Some important things to remember:
    • if at first code appears opaque or complex, take the time to break it down step by step
    • if necessary use some sort of systematic approach (such as a table)
    • figure out what is happening at each step, paying particular attention to:
      • return value
      • side effects
    • pay attention to the return values of all statements in your code, especially where implicit return values are being relied on
    • make sure you have a clear understanding of the underlying concepts such as data structure, loops, iterative methods and the blocks passed to them
    • be clear about the method implementation of the iterative method(s) being used, especially:
      • what values are passed to the block
      • what the method does with the return value of the block
    • if you are unclear about a method implementation, a good initial step is to refer to the ruby docs

Lesson Summary #

  • be aware, when you make a copy of a collection object it is a shallow copy, the objects within the collections are shared between the copy an dthe original
  • when working with blocks, especially when using nested collections:
    • take the time to break down and understand the structure of a collection
    • choose an appropriate method and be clear on its implementation and return value
    • understand what is being returned by the various methods and blocks at each level. when iterating through nested collections, be particularly aware of the return value of the block and any side effects of the code within the block.

Lesson 6 #

Introduction #

  • recommended approach to solving larger problems:
    1. break down the problem into smaller peices
    2. map out the flow of the problem in a flow-chart, using sub-processes
    3. when ready to tackle a component or sub-process, write out the pseudo-code for that sub-process only, with clear inputs and clear outputs
    4. play around with the code, write every line, don’t copy/paste
    5. do the assignments in sequence
    6. don’t be afraid to watch the walk-through videos