CS312 - Spring 2012 - Class 3

  • administrative
       - assignment 1?
       - assignment 2 out today
          - should be easier than the last one
          - start early, though!
       - use the examples from class and the notes from class
       - my view on homework...

  • variable
       - what is a variable in Ruby?
          - a variable is a reference to an object
          - this is almost the same as Java (minus built-in types)
       - do variables have types?
          - No! Unlike in Java, a variable can reference any type of object

          >> x = 10
          => 10
          >> x
          => 10
          >> x = "this is a string"
          => "this is a string"
          >> x
          => "this is a string"

       - When I type the following, what would the memory picture look like?
          >> x = "this is a string"
          => "this is a string"
          >> y = x
          => "this is a string"

       - What would be the value of y after the following?
          >> x.reverse!
          => "gnirts a si siht"
          >> x
          => "gnirts a si siht"
       
             - as an aside, notice that Strings are mutable in Ruby, unlike many other languages
       
          - y would also have "gnirts a si siht", since it references the same object

  • arrays
       - what is an array?
       - Ruby has arrays
          - they are a class of objects like everything else in Ruby
       - you can create them
          - using []
             >> a = []
             => []
             >> b = [1, 2, 3, 4, 5]
             => [1, 2, 3, 4, 5]
          
          - or using new
             >> c = Array.new
             => []

       - arrays are indexable by integers with indices start at 0
          >> b[0]
          => 1
          >> b[1]
          => 2

       - and we can do all the fancy indexing tricks that we saw with strings
          >> b[-1]
          => 5
          >> b[1..3]
          => [2, 3, 4]
          >> b[1...3]
          => [2, 3]

          - .. and ... are called Ranges
             - .. is inclusive
             - ... includes the first number but not the last number

       - arrays are infinitely (well almost) expandable
          >> b
          => [1, 2, 3, 4, 5]
          >> b << 6
          => [1, 2, 3, 4, 5, 6]
          >> b << 7
          => [1, 2, 3, 4, 5, 6, 7]
          >> b[10] = 15
          => 15
          >> b
          => [1, 2, 3, 4, 5, 6, 7, nil, nil, nil, 15]

       - you never get an array out of bounds exception, but you will get nil if you access an entry that doesn't exist
          >> b[100000000]
          => nil

       - you can do some fancy things with assigning to multiple entries
          >> => [1, 2, 3, 4, 5, 6, 7, nil, nil, nil, 15]
          >> b[1,3] = 20
          => 20
          >> b
          => [1, 20, 5, 6, 7, nil, nil, nil, 15]
          >> b[5,3] = 30
          => 30
          >> b
          => [1, 20, 5, 6, 7, 30, 15]

          - Read the book for more details on this!

       - arrays have a whole bunch of useful methods that are worth browsing through
          - http://ruby-doc.org/core-1.9.3/Array.html
             - count
             - replace
             - reverse
             - shuffle
             - sort
             - ...

  • hashes (aka dictionaries or maps or associative arrays)
       - how are hashes/dictionaries/maps different than arrays?
          - arrays can only have positive integers as indices/keys
          - arrays are sequential
       - a hash is an association between a key value pair
          - they're called hashes since they're often implemented with hashtables (but they don't have to be)
          - keys can be any object
          - values can be any object
          - in most situations the lookup of a key is a constant time operation
       - We can construct new hashes
          - using {}
             >> a = {}
             => {}
             >> b = {"key1" => "value1", "key2" => "value2"}
             => {"key1"=>"value1", "key2"=>"value2"}
             
          - or using new
             >> c = Hash.new
             => {}

       - hashes are indexed by arbitrary keys
          >> b["key1"]
          => "value1"
          >> b["banana"]
          => nil
          >> b["banana"] = 1
          => 1
          >> b
          => {"key1"=>"value1", "key2"=>"value2", "banana"=>1}

       - hashes are very versatile and are very frequently used

  • blocks
       - how would you print out the values in an array e.g. b = [1, 2, 3, 4, 5]?
          1.
          for val in b
             puts val
          end   
       
          2.
          for i in 0...b.length
             puts b[i]
          end

          3.
          i = 0

          while i < b.length
             puts b[i]
             i += 1
          end

          - all of these work fine in Ruby, however, it is generally not the preferred style or the approach the people take
       
       - in Ruby, the common approach is to use "blocks"   
          1.
          b.each{ |val| puts val}
          
          2.
          b.each do |val|
             puts val
          end

       - a block is similar to an anonymous function and has multiple parts
          - the parameters are listed in between ||
             - the blocks in the example above just have two parameters
          - the body of the block is listed after it

       - block are either denoted with {} or with do... end
          - the two above are equivalent
          - in practice, one line blocks are usually written with {} and multiline with do...end

       - functions and methods utilize the blocks internally (think of it like passing a block of code for the function to use)
       
       - in the example above, "each" is an iterator and applies the block of code to each values in the array
       - this has some very powerful implications
          - sum up the values
             sum = 0

             b.each{|va| sum += val}

          - sum up the square of the values
             sum = 0

             b.each{|v| sum += v * v }

          - sum up the values and print the rolling sum
             sum = 0

             b.each do |v|
                sum += v
                puts v
             end

          - what if we wanted to print out the rolling sum and the index?
             sum = 0

             b.each_with_index do |value, index|
                sum += v
                puts "#{index}:\t#{value}"
             end

             - notice that the function/method that you call will dictate how many parameters the block needs to have, but you dictate the names

          - any code that you want to execute can go in the body of a block
       - other functions also use blocks
          - select

             >> b = [1, 2, 3, 4, 5]
             => [1, 2, 3, 4, 5]
             >> b.select{|val| val < 3}
             => [1, 2]
             >> (0..100).select{|v| v %2 == 0}
             => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100]
          - each_char
             - like each, but for strings

             "this is a test".each_char{|ch| puts ch}

  • writing blocks
       - the keyword "yield" specifies a call to a block that was passed to a function

          def array_checker(a)
             vals = []
             a.each{|v| vals << yield(v)}      
             return vals
          end

          p array_checker([1, 2, 3, 4, 5, 6]){|v| v >= 3}
          
          - this is basically the map function
             p [1, 2, 3, 4, 5, 6].map{|v| v >= 3}

       - anywhere the the keyword "yield" is used, the block is called
          - the number of parameters used with the yield command dictates how many parameters the block must have

          def some_function(num)
             puts yield(num, num)
             puts yield(num, 1)
          end

          
          some_function(10){|v1, v2| v1 * v2}
          some_function(10){|v1, v2| v1 + v2}

  • blocks and hashes
       - hashes also have methods that use blocks
          - the key difference is that hashes have keys and values

       - print out the keys and values
          h.each{|key, value| puts "#{key}\t#{value}"}

  • sorting
       - may classes have a sort_by method that takes a block of code
          - the block dictates a transformation on each value before being sorted
          - allows for many interesting sorting techniques
       - arrays
          - sort normally
             [-5, 4, -3, 2, 1].sort_by{|v| v}

             - if you just want to do this, there is a normal sort function
          - sort in reverse order
             [-5, 4, -3, 2, 1].sort_by{|v| -v}

             - or could also call .reverse on what was returned above

          - sort based on the absolute value
             [-5, 4, -3, 2, 1].sort_by{|v| v.abs}

       - hashes
          - hashes have both keys and values and the sort_by block can access either of these
          - the sort_by function returns an array of arrays (with each sub-array containing to entries)
          - sort by keys
             h.sort_by{|key, value| key}

             - notice that the block has both key and value as a parameter
          - sort by values
             h.sort_by{|k,v| v}

          - sort by values decending
             h.sort_by{|k,v| v}.reverse

          - print out the keys sorted by value
             h.sort_by{|k,v| v}.each{|k, v| puts k}

  • take a look at the LinkedListImproved code
       - we can define these methods in our own classes to make life easier
       - each method
          - traverse the linked list
          - call yield (the block passed in) on each value
       - each_with_index
          - traverse the linked list
          - keep track of the index internally
             - the block (i.e. yield) expects a second parameter
       - find
          - returns the value of the first value where yield called on the value returns true
          - returns nil if no matches exist
          - can use our each method already