# get ith element of array: a[i] # set ith element of array: a[i] = e # ruby arrays are much more dynamic # fewer errors, but less efficient a = [3, 2, 7, 9] a[2] # 7 a[0] # 3 a[4] # not in the array, returns nil a[1000] # also not in the array, also returns nil a.size # 4 a[-1] # last element, 9 a[-2] # next to last element, 7 a[-4] # first element, 3 a[-5] # past the first element, nil a[1] = 6 # update 2nd element, a = [3, 6, 7, 9] a[6] = 14 # can assign outside the current array bounds # creates nil in intermediate position # a = [3, 6, 7, 9, nil, nil, 14] # but can't assign past the beginning a[-7] = 0 # error! a[3] = "hi" # ruby is dynamic, can put whatever we want in arrays b = a + [true, false] # append to arrays with "+" # does not change a c = [3, 2, 3] | [1, 2, 3] # returns array without duplicates # as we saw in previous lecture, can also subtract "-" # use arrays for tuples triple = [false, "hi", a[0] + 4] triple[2] x = if a[1] < a[0] then 10 else 20 end y = Array.new(x) # can make a new array of a particular size z = Array.new(x) { 0 } # can initialize with blocks z = Array.new(x) {|i| -i} # 0 .. -19 # arrays can be used as stacks (LIFO) # use the method push to add to the right side a.push 5 a a.push 7 a a.pop a a.pop a # can also use arrays for queues (FIFO) a.push 11 a a.shift a a[0] # now 6 a.shift a[0] # now "hi" # use push and shift for queue # can also put things on the left with unshift a.unshift 19 a a.shift # arrays are objects, so previous discussion of aliasing still applies d = a # d and a are aliased e = a + [] # plus returns a new array d[0] a[0] = 6 d[0] a[0] e[0] f = [2, 4, 6, 8, 10, 12, 14] # can take part of an array with slices f[2, 4] # [6, 8, 10, 12] # starts at 2 and gives 4 elements # can also assign into slices f[2, 4] = [1, 1] # note that the thing we assign in doesn't have to be same size # also want to do map/fold [1, 3, 4, 12].each {|i| puts (i * i)} f.each {|i| puts (i * i)} # almost any method over arrays is defined in ruby # remember that we use arrays for tuples, lists, stacks, queues, etc. # PASSING BLOCKS # blocks are similar to closures # you can pass a block like an anonymous function to a method # can take 0 or more arguments # use lexical scope 3.times { puts "hi" } [4, 6, 8].each { puts "hi" } [4, 6, 8].each {|x| puts x} # can take arg # blocks have lexical scope i = 7 [4, 6, 8].each {|x| if i > x then puts (x + 1) end } # blocks are a little strange # a method can take 0 or 1 blocks # callee might ignore blocks or give error if expects one # callee may change behavior if you do / don't send a block # blocks are passed "on the side", next to other arguments # blocks can take multiple arguments # {|x, y| e} # can also use "do" "end" for left and right brace # preferred when the block is multiple lines # people don't use loops in ruby, basically just pass blocks to stuff in # std lib a = Array.new 5 a = Array.new(5 {|i| 4 * (i + 1)} a.each {|x| puts (x * 2)} a.map {|x| x * 2} # returns a new array a b = a.map {|x| x * 2} # a and b not aliased a.any? {|x| x > 7} a.any? {|x| x > 700} a.all? {|x| x > 7} a.all? {|x| x > -7} a.any? # sees if anything is true (not false or nil) a.all? # see if everything is non-false and non-nil (a + [nil]).all? (a + ["hi"]).all? a.inject(0) {|acc, elt| acc + elt} # inject is reduce / fold a.inject {|acc, elt| acc + elt} # first elt is default accumulator # a.collect is same as map # a.select is same as filter a a.select {|x| x > 7} a.select {|x| x > 7 && x < 18} # morally loops, but really just methods def t i (0..i).each do |j| print " " * j (j..i).each {|k| print k; print " "} print "\n" end end t 9 # usually use blocks just like above # but we can also define our methods that use blocks # callee does not have a name for block # callee can invoke block with "yield" # so there is no name for the block other than "yield" def silly a (yield a) + (yield 42) end x = 0 x.silly(5) {|b| b * 2} # can ask block_given? to see if there is a block or not class Foo def initialize(max) @max = max end def silly yield(4, 5) + yield(@max, @max) end def count base if base > @max raise "reached max" elsif yield base 1 else # can't really pass same block # so do something like "unnecessary function wrapping" 1 + (count(base + 1) {|i| yield i}) end end end f = Foo.new(1000) f.silly # no block given, error f.silly {|a, b| 2 * a - b} f.count(10) {|i| (i * i) == (34 * i)} # procs are a lot blocks, but they're actually objects # blocks are not really first class # blocks are "second class" # can't store them, put them into objects, etc. etc. # can put blocks into objects called "procs" # procs have method "call" which invokes block # here we distinguish between first class and second class # in ruby, blocks are 2nd class # in ruby, procs are 1st class # use lambda method (of Object class) which # takes a block and returns the corresponding Proc # often don't need procs, blocks do the trick a = [3, 5, 7, 9] b = a.map {|x| x + 1} i = b.count {|x| x >= 6} # but supposed I want to create an array of closures # this will not work : c = a.map {|x| {|y| x >= y}} # can't put a block in a block, it's not an expression c = a.map {|x| (lambda {|y| x >= y})} c[2] c[2].call # error, expects argument c[2].call 17 # false c[2].call 7 # true j = c.count {|x| x.call(5)} # ==> 3 j = c.count {|x| x.call(50)} # ==> 0 # Procs are first class closures and are more powerful than blocks # they are objects, which makes them first class # why make 2nd class blocks at all? # when is it worth to make the common case more convenient # at the expense of learning more stuff when you need generality