iT邦幫忙

2021 iThome 鐵人賽

DAY 2
0
自我挑戰組

初級紅寶石魔法師心得分享。系列 第 2

D-28.鴨子型別, 字串, 陣列, 範圍, 雜湊

進入其他基本資料類別前。

想先說明以下觀念。

2.7.3 :081 > 1 + 1
 => 2
# + 在這裡是數字類別的方法

2.7.3 :082 > "1" + "1"
 => "11"
# + 在這裡是字串類別的方法

1 + "1""1" + 1會噴TypeError錯誤,是因為兩種類別的 + 方法,規定了所能運用的資料型態。而不是+方法自己規定了兩種資料型態不能相加。

自己動手做一個同名但不同類別都有的方法。

class A_class
  def super_method
    puts "我是A方法"
  end
end
 => :super_method
 
class B_class
  def super_method
    puts "我是B方法"
  end
end
 => :super_method
 
2.7.3 :096 > a = A_class.new
 => #<A_class:0x00007fcb083dbcf8>
2.7.3 :097 > a.super_method
我是A方法
 => nil
2.7.3 :098 > b = B_class.new
 => #<B_class:0x00007fcb083aa978>
2.7.3 :099 > b.super_method
我是B方法
 => nil

為何提這個是因為,類別裡常有"同名"的方法,在學習初期,我們常常會記Array也可以相加,但事實上是陣列也有自己的+的方法。

2.7.3 :100 > [1, 2, 3] + [1, 2, 3]
 => [1, 2, 3, 1, 2, 3]

duck typing

請不要把下面文章想成原理,想成是一個故事。

Ruby是鴨子型別的設計風格。

「當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子。」

我們把一開始的同名方法整理一下。

class Dog
  def has_a_method
    puts "我會叫,我會跑,我會游泳"
  end
end

class Cat
  def has_a_method
    puts "我會叫,我會跑,我會游泳"
  end
end

class Duck_look_like
  def yes_i_can(another_animal)
    another_animal.has_a_method
  end
end

2.7.3 :252 > kind_of_animal = Duck_look_like.new
 => #<Duck_look_like:0x00007fa17b39dd10>

2.7.3 :253 > kitty = Dog.new
 => #<Dog:0x00007fa17b3265a8> #有一隻kitty
 
2.7.3 :254 > lucky = Cat.new
 => #<Cat:0x00007fa17b417c78> #有一隻lucky
#這邊故意反慣例,表示不在意他們本質。

2.7.3 :255 > kind_of_animal.yes_i_can(kitty)
我會叫,我會跑,我會游泳
 => nil
2.7.3 :256 > kind_of_animal.yes_i_can(lucky)
我會叫,我會跑,我會游泳
 => nil

我在做的不是規定鴨子是什麼,而是讓狗與貓變成像鴨子一樣。

初學時會說,那這一切都是設計好的呀?

對,初期比起在意字串不是數字卻能夠相加,應該在意的是能讓字串相加。

無論有人說這種設計讓Ruby好學或是難學,好維護或難維護,但這種設計是為了我們能更快速操作"物件"。

關於duck typing還會有其他衍生的問題,例如Monkey Patch等,也是很好的討論問題,這邊稍微提到是因為明天會分享一些轉換類型的leetcode題型做分享。

能解決問題就是好魔法。


字串

用"",與''包起來的任何字元都為字串。

2.7.3 :015 > "234234*(&%*(X34234.".class
 => String
2.7.3 :016 > '1+1=2'.class
 => String
2.7.3 :010 > "".class
 => String
2.7.3 :011 > ''.class
 => String
#即使""裡面是nil,也是字串。

我們來讓字串像其他類別吧。


2.7.3 :021 > "str" + "str"
 => "strstr"
2.7.3 :022 > "str"*8
 => "strstrstrstrstrstrstrstr"
 
2.7.3 :020 > str = String.new
 => ""
2.7.3 :023 > str << "123"
 => "123"
2.7.3 :024 > str << "456"
 => "123456"
2.7.3 :025 > "abc".bytes
 => [97, 98, 99]
#當然不只這些。

分享過的:leetcode.028:Implement strStr()
解法為利用index方法,由於已寫過就不重複拿來騙篇幅,直接整理如下。

def str_str(haystack, needle)
  ans = haystack.index(needle)
  ans != nil ? ans : -1
end

def str_str(haystack, needle)
  haystack.index(needle) != nil ? haystack.index(needle) : -1
end

#看久了三元運算子很可愛,不常看覺得很討厭,跟不准家裡養寵物的爸媽,看到偷抱回來的毛小孩一樣。

陣列

陣列由於記憶體儲存方式,常會是用到迴圈,迭代,枚舉的好題型。

2.7.3 :026 > arr = Array.new
 => []
2.7.3 :027 > arr << 1
 => [1]
2.7.3 :028 > arr << 2
 => [1, 2]
2.7.3 :029 > arr << "3"
 => [1, 2, "3"]
# << 這個方法,陣列與字串的非常的不同。
#另外陣列也可以運用到運算符號,但使用結果與使用對象須注意
2.7.3 :031 > [1, 2, "3", 1, 2, "3"] - [1, 2, "3"]
 => []
2.7.3 :032 > [1, 1, 1] + [1, 2, 3]
 => [1, 1, 1, 1, 2, 3]
2.7.3 :033 > [1] * [1]
TypeError (no implicit conversion of Array into Integer)
2.7.3 :034 > [1] * 5
 => [1, 1, 1, 1, 1]

leetcode035:Search Insert Position
跟上題連結一樣。

#先不簡化
def search_insert(nums, target)
  nums.each_with_index do |num, index|  
    if num >= target 
      return index 
    end  #大於0的目標與裡面的值的關係是,剛好相等時回報位置,比較小時取代裡面值的位置
  end
  nums.size
end

def search_insert(nums, target)
  nums.each_with_index do |num, index|
    return index if num >= target
  end
  nums.size
end

範圍

Range用到new時,需要帶入正確參數,所以在解題上常會直接寫出自己要的範圍,不常會用到new。

2.7.3 :036 > Range.new
ArgumentError (wrong number of arguments (given 0, expected 2..3))
#也不是隨便帶參數就可以。
2.7.3 :045 > Range.new(1, 6)
 => 1..6
2.7.3 :046 > Range.new("a", 6)
ArgumentError (bad value for range)
2.7.3 :047 > Range.new("a", "b")
 => "a".."b"

#字串可以的原因,字母本身具有自己的字節數。
2.7.3 :007 > "a".bytes
 => [97]
2.7.3 :008 > "z".bytes
 => [122]


#很好玩的一點
2.7.3 :037 > 1..6.class
ArgumentError (bad value for range)
#字串,數字,陣列,雜湊我們自己輸入好就會new完成。範圍沒有。
2.7.3 :038 > (1..6).class
 => Range

一般解題剛接觸範圍,常見到會用在迴圈,例如:

for num in 1..array.size-1 do ... end

#或是..與...差異
2.7.3 :040 > (1..5).to_a
 => [1, 2, 3, 4, 5]
2.7.3 :041 > (1...5).to_a
 => [1, 2, 3, 4]


#另外不用to_a轉型寫法。
2.7.3 :043 > [*"a".."h"]
 => ["a", "b", "c", "d", "e", "f", "g", "h"]

leetcode.122:Best Time to Buy and Sell Stock II

#第一個是錯誤答案,第二個正確,主要是因為..與...差異。

def max_profit(prices)
  max_profit = 0
  for i in 0..(prices.size - 1)
    max_profit += (prices[i+1] - prices[i]) if prices[i+1] > prices[i]
  end
  max_profit
end

def max_profit(prices)
  max_profit = 0
  for i in 0...(prices.size - 1)  
    max_profit += (prices[i+1] - prices[i]) if prices[i+1] > prices[i]
    #prices[i+1] > prices[i] && max_profit += (prices[i+1] - prices[i])
    # 上一句可改寫成這樣,久了會習慣,不習慣就是原本的比較好。
  end
  max_profit
end

習慣多利用each,map

def max_profit(prices)
  max_profit = 0
  prices.each_cons(2) do |price , next_price| 
    next_price > price && max_profit += next_price - price 
  end
  max_profit
end

後面日程會再提到each,map。
若真有比我新的新手路過,先記得初期題型Range比較常被拿來利用,尤其在陣列身上。


雜湊

又愛又恨...
原因是ㄓ跟ㄗ,ㄔ跟ㄘ

先瞭解長相

2.7.3 :050 > {a: 123, :b => 123}.class
 => Hash
#前面是比較新的寫法,後面是箭頭式,都對。
2.7.3 :051 >{a: 123, :a => 123}.class
(irb):51: warning: key :a is duplicated and overwritten on line 51
 => Hash
#被覆蓋指向了,所以請確定Key沒用過。
2.7.3 :052 > {a: 123, "a": 123}.class
(irb):52: warning: key :a is duplicated and overwritten on line 52
 => Hash
#一樣被overwritten
2.7.3 :054 > {:a => 123, "a" => 123}.class
 => Hash
#又沒問題了....

會這樣的原因在昨日的符號。
新手初期手動寫雜湊請記得格式統一。
利用運算變成Hash的資料也是幫你統一格式,沒理由自己寫用兩種以上格式。

Hash在學習上除了知道找Key跟Value怎麼處理外,會有很多題型是將資料轉成Hash來處理。

2.7.3 :057 > hash = { a: 1, b: 2, c: 3}
 => {:a=>1, :b=>2, :c=>3}
2.7.3 :058 > hash.keys
 => [:a, :b, :c]
2.7.3 :059 > hash.values
 => [1, 2, 3]
#keys與values這兩個用復數,這慣例在Rails上也很常見,可以當成一種習慣。

2.7.3 :060 > hash[:a]
 => 1
2.7.3 :061 > hash[:b]
 => 2
2.7.3 :063 > hash.index(2)
 => :b

leetcode001:two_sum
永遠勸退的第一題

def two_sum(nums, target)
  new_hash = {}
  nums.each_with_index do |num, index| 
    return [new_hash[num], index] if new_hash.has_key?(num)
    new_hash[target - num] = index
  end
end

leetcode167:two_sum II

def two_sum(numbers, target)
  hash = {}    
  numbers.each_with_index do |num, i|
    return [hash[target - num] +1 , i + 1]  if hash[target - num]
    hash[num] = i
  end
end
#說明,文章連結都有喔。

利用枚舉產生的Hash,其中的Key與Value常是會有關聯性的,依照這個關聯性,當我們知道其中一個值後,能很快速知道另外一個的結果。

例如

2.7.3 :075 > [1, 2, 3, 3, 5, 6, 1, 2, 7].group_by {|num|num}
 => {1=>[1, 1], 2=>[2, 2], 3=>[3, 3], 5=>[5], 6=>[6], 7=>[7]}
2.7.3 :076 > [1, 2, 3, 3, 5, 6, 1, 2, 7].tally
 => {1=>2, 2=>2, 3=>2, 5=>1, 6=>1, 7=>1}

查表法難在建立表格,建好之後的快樂會讓你忘記建立時的痛苦。

Hash往往是被介紹的很少,但後面越用用多的資料類型,尤其到Rails後,可以發現所有實體都長的跟Hash沒什麼差別,刷題與學習Rails不一定有正比關係,但是練習越多,越不會害怕Hash,越能體會迭代與枚舉的好處。


今日提到的。
1.Duck Typing
2.一些曾經解過的leetcode題目。



上一篇
D-29. 常數, 變數, 符號, 數字 && Leetcode : Power_of
下一篇
D-27. 編譯直譯、動態靜態、強型弱型 && Leetcode:Add Digits && Move Zeroes
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言