300 likes | 449 Views
A quick Ruby Tutorial, Part 2. COMP313 Source: Programming Ruby, The Pragmatic Programmers’ Guide by Dave Thomas, Chad Fowler, and Andy Hunt. Writing attributes. class Song def duration=(new_duration) @duration = new_duration end end song = Song.new("Bicylops", "Fleck", 260)
E N D
A quick Ruby Tutorial, Part 2 COMP313 Source: Programming Ruby, The Pragmatic Programmers’ Guide by Dave Thomas, Chad Fowler, and Andy Hunt
Writing attributes class Song def duration=(new_duration) @duration = new_duration end end song = Song.new("Bicylops", "Fleck", 260) song.duration → 260 song.duration = 257 # set attribute with updated value song.duration → 257
Simpler class Song attr_writer :duration end song = Song.new("Bicylops", "Fleck", 260) song.duration = 257
Virtual attributes class Song def duration_in_minutes @duration/60.0 # force floating point end def duration_in_minutes=(new_duration) @duration = (new_duration*60).to_i end end song = Song.new("Bicylops", "Fleck", 260) song.duration_in_minutes → 4.33333333333333 song.duration_in_minutes = 4.2 song.duration → 252
Class variables class Song @@plays = 0 def initialize(name, artist, duration) @name = name @artist = artist @duration = duration @plays = 0 end def play @plays += 1 @@plays += 1 "This song: #@plays plays. Total #@@plays plays." end end
Class methods class Example def instance_method # instance method end def Example.class_method # class method end end #uses: Example.class_method Example.new
Singleton class example class MyLogger private_class_method :new @@logger = nil def MyLogger.create @@logger = new unless @@logger @@logger end end
Access control • public: default for all methods (except initialize), accessible by everyone • protected: access within class and all subclasses (!= Java) • private: only the current object (again != Java) • checked at runtime only!
How to define access class MyClass def method1 # default is 'public' #... end protected # subsequent methods will be 'protected' def method2 # will be 'protected' #... end private # subsequent methods will be 'private' def method3 # will be 'private' #... end public # subsequent methods will be 'public' def method4 # and this will be 'public' #... end end
Alternatively class MyClass def method1 end # ... and so on public :method1, :method4 protected :method2 private :method3 end
Account example class Accounts def initialize(checking, savings) @checking = checking @savings = savings end private def debit(account, amount) account.balance -= amount end def credit(account, amount) account.balance += amount end public #... def transfer_to_savings(amount) debit(@checking, amount) credit(@savings, amount) end #... end
Protected example class Account attr_reader :balance # accessor method 'balance' protected :balance # and make it protected def greater_balance_than(other) return @balance > other.balance end end
Assignment semantics (like Java) person1 = "Tim" person2 = person1 person1[0] = 'J' person1 → "Jim" person2 → "Jim” person1 = "Tim" person2 = person1.dup person1[0] = "J" person1 → "Jim" person2 → "Tim"
“freeze” objects person1 = "Tim" person2 = person1 person1.freeze # prevent modifications to the object person2[0] = "J"produces: prog.rb:4:in `[]=': can't modify frozen string (TypeError) from prog.rb:4
Array access (again) a = [ 1, 3, 5, 7, 9 ] a[-1] → 9 a[-2] → 7 a[-99] → nil a[1, 3] → [3, 5, 7] a[3, 1] → [7] a[-3, 2] → [5, 7] a[1..3] → [3, 5, 7] a[1...3] → [3, 5] a[3..3] → [7] a[-3..-1] → [5, 7, 9]
Implement own sample container class SongList def initialize @songs = Array.new end def append(song) @songs.push(song) self end def delete_first @songs.shift end def delete_last @songs.pop end end
Defining array-like access class SongList def [](index) @songs[index] end end
Unit testing require 'test/unit' class TestSongList < Test::Unit::TestCase def test_delete list = SongList.new s1 = Song.new('title1', 'artist1', 1) s2 = Song.new('title2', 'artist2', 2) list.append(s1).append(s2) assert_equal(s1, list[0]) assert_equal(s2, list[1]) assert_nil(list[9]) assert_equal(s2, list.delete_last) assert_equal(s1, list.delete_first) assert_nil(list.delete_last) end end
Finding songs by title class SongList def with_title(title) for i in 0...@songs.length return @songs[i] if title == @songs[i].name end return nil end end
Finding songs by title (the Ruby way) class SongList def with_title(title) @songs.find {|song| title == song.name } end end
Hypothetical find in Array class Array def find for i in 0...size value = self[i] return value if yield(value) end return nil end end [1, 3, 5, 7, 9].find {|v| v*v > 30 } → 7
inject iterator [1,3,5,7].inject(0) {|sum, element| sum+element} → 16 [1,3,5,7].inject(1) {|product, element| product*element} → 105 [1,3,5,7].inject {|sum, element| sum+element} → 16 [1,3,5,7].inject {|product, element| product*element} → 105
Iterators in Ruby • are “internal”, I.e. just another method • Java et.al.: “external”, I.e. explicit additional object • pros and cons
more on code blocks class File def File.my_open(*args) result = file = File.new(*args) # If there's a block, pass in the file and close it on returns if block_given? result = yield file file.close end return result end end
proc and lambda def n_times(thing) return lambda {|n| thing * n } end p1 = n_times(23) p1.call(3) → 69 p1.call(4) → 92 p2 = n_times("Hello ") p2.call(3) → "Hello Hello Hello "
Numbers • ints are either in -2^30to2^30-1 (or -2^62to2^62-1) or arbitrarily large BigNums num = 81 6.times do puts "#{num.class}: #{num}" num *= num end
Loops using numbers 3.times { print "X " } 1.upto(5) {|i| print i, " " } 99.downto(95) {|i| print i, " " } 50.step(80, 5) {|i| print i, " " }
Strings to numbers • 1.to_i or Integer(1) some_file = File.new(“xyz”,”r”) some_file.each { |line| v1, v2 = line.split print v1.to_i + v2.to_i, " ” }
Song database /jazz/j00132.mp3 | 3:45 | Fats Waller | Ain't Misbehavin' /jazz/j00319.mp3 | 2:58 | Louis Armstrong | Wonderful World /bgrass/bg0732.mp3| 4:09 | Strength in Numbers | Texas Red break lines into fields convert runtime into seconds remove extra spaces in artists’ names
Song database: code File.open("songdata") do |song_file| songs = SongList.new song_file.each do |line| file, length, name, title = line.chomp.split(/\s*\|\s*/) name.squeeze(“ “) mins, secs = length.scan(/\d+/) songs.append(Song.new(title, name, mins.to_i*60+secs.to_i)) end songs end