iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
自我挑戰組

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

D-26.Block、Proc、lambda && Valid Perfect Square

Block說:我讓Ruby發光發亮。

Ruby中少數天生不屬於物件的存在。
未物件化前,只能依附在有設計好的方法後面才有作用。

2.7.3 :064 > 10.to_s {|string| string.to_i}
 => "10"
#後方想再to_i也沒用。

block長什麼樣子

程式碼區塊指的是do...end,或使用{ }包起來的code,其內必須有正確格式的程式碼。

2.7.3 :137 > [1, 2, 3].map do |num|
2.7.3 :138 >   num ** num
2.7.3 :139 > end
 => [1, 4, 27]
#需要使用較複雜的程式碼時用do...end多。

2.7.3 :124 > [1, 2, 3].map {|num| num.to_s}
 => ["1", "2", "3"]
#通常使用較簡短的程式碼時用{}多。

2.7.3 :308 > {}.class
 => Hash
#只有{}就只是Hash。

哪個好看用哪個...


是map或each才會用到block嗎?

不是的,是each與map設計成可以利用bloack。

為你自己學Ruby on Rails
Block 通常得像寄生蟲一樣依附或寄生在其它的方法或物件(或是使用某些類別把它物件化)


怎麼用?

如果已經接觸過Rails專案,應該都知道views/layouts/application.html.erb中,有一個短短的程式碼yield,在官方手冊裡是這樣說的。

RailsGuides
Within the context of a layout, yield identifies a section where content from the view should be inserted. The simplest way to use this is to have a single yield, into which the entire contents of the view currently being rendered is inserted:(Rails會把繪製好的版面顯示在yield標示的地方。還是中文簡潔有力)

<html>
  <head>
  </head>
  <body>
  <%= yield %>
  </body>
</html>

利用yield是一種方式。

2.7.3 :188 > def i_am_not_each(num)
2.7.3 :189 >   yield
2.7.3 :190 >   num.to_s
2.7.3 :191 > end
 => :i_am_not_each
2.7.3 :192 > i_am_not_each(20) {
2.7.3 :193 >   puts "Hello你好嗎?,確定你有yield嗎?"
2.7.3 :194 > }
Hello你好嗎?,確定你有yield嗎?
 => "20"

#改寫一下
2.7.3 :010 > def i_am_not_each
2.7.3 :011 >   yield
2.7.3 :012 >   self.to_s
2.7.3 :013 > end
 => :i_am_not_each
2.7.3 :014 > 20.i_am_not_each{puts "Hello你好嗎?,確定你有yield嗎?"}
Hello你好嗎?,確定你有yield嗎?
 => "20"
#self,之後會更仔細說明。

yield,方法內有遇到他的先繞到block內就對了。

2.7.3 :070 > def little
2.7.3 :071 >   yield
2.7.3 :072 >   puts "我跳進來了"
2.7.3 :073 >   yield
2.7.3 :074 >   puts "我又跳進來了"
2.7.3 :075 > end
 => :little
2.7.3 :076 > little() { puts "我跑出去了" }
我跑出去了
我跳進來了
我跑出去了
我又跳進來了

Proc是一個Class

還有一個就是物件化。

2.7.3 :195 > say_something = Proc.new {puts "yield被藏起來了"}
 => #<Proc:0x00007fa17b3cdd08 (irb):195>
#say_something是變數
#Proc大寫又接new方法,那一定是個類別。
2.7.3 :042 > Proc.is_a? Class
=> true

#完成後我們就可以執行
2.7.3 :197 > say_something.call
yield被藏起來了
 => nil

#其他呼叫方法
2.7.3 :208 > say_something.()
yield被藏起來了
 => nil
2.7.3 :209 > say_something[]
yield被藏起來了
 => nil
2.7.3 :210 > say_something === 1
yield被藏起來了
 => nil
2.7.3 :212 > say_something.yield
yield被藏起來了
 => nil

帶參數

上面那幾種使用方法,感覺上就是為了可以有參數設定的。

2.7.3 :213 > do_some_thing = Proc.new {|string| string.sum }
 => #<Proc:0x00007fa17b3e7578 (irb):213>
2.7.3 :216 > do_some_thing.("你的銀行卡密碼")
 => 3776
2.7.3 :217 > do_some_thing["你的健保卡密碼"]
 => 3843
2.7.3 :218 > do_some_thing === "健保卡沒有密碼"
 => 3826

理所當然的,要帶參數,block裡也需要設計參數的位置。

{ |變數| 變數真的是很偉大 }

| |這個符號內寫入變數,作為從指向從外部帶進的參數用,也當然變數(參數)可以不只設定一個。

2.7.3 :219 > default_variable = Proc.new {|var_one, var_two = 20| puts "你叫#{var_one},永遠#{var_two}歲" }
 => #<Proc:0x00007fa17b385198 (irb):219>
2.7.3 :220 > default_variable.("小明", 50)
你叫小明,永遠50歲
 => nil
2.7.3 :221 > default_variable.("小明")
你叫小明,永遠20歲
 => nil

一樣可以預設參數的值。

block內的變數,有效範圍只在block裡。

2.7.3 :224 > num = 50
 => 50
2.7.3 :225 > variable_test = Proc.new {|int| int.to_s}
 => #<Proc:0x00007fa17b2d65f8 (irb):225>
2.7.3 :226 > variable_test.(num)
 => "50"
2.7.3 :227 > variable_test.(int)
#NameError (undefined local variable or method `int' for main:Object)
2.7.3 :228 > puts int
#NameError (undefined local variable or method `int' for main:Object)
2.7.3 :229 > puts num
50
 => nil

研究一下回傳值

一切東西都有值,回傳nil是沒有回傳值,因為nil是nil。

2.7.3 :262 > [1, 2, 3].each {|num| num.to_s}
 => [1, 2, 3]
#[1, 2, 3]是black的回傳值嗎?

Ruby API
When a block given, passes each successive array element to the block; returns self。each丟元素給block,回傳"使用each的物件"。

2.7.3 :263 > [1, 2, 3].each {|num| puts num.to_s}
1
2
3
 => [1, 2, 3]
2.7.3 :264 > [1, 2, 3].each {|num| p num.to_s}
"1"
"2"
"3"
 => [1, 2, 3]

each將資料傳進block內,讓block內程式碼操作,但最後回傳值不是取決於block,而是each。
那block到底有沒有回傳值?

#這時改看map,會更瞭解。
2.7.3 :265 > [1, 2, 3].map {|num| p num.to_s}
"1"
"2"
"3"
 => ["1", "2", "3"]
2.7.3 :266 > [1, 2, 3].map {|num| num.to_s}
 => ["1", "2", "3"]

有的,map收走了,並集合成新的物件回傳。

Calls the block, if given, with each element of self; returns a new Array whose elements are the return values from the block。


但是不能亂加return。

2.7.3 :267 > def i_am_not_each(num)
2.7.3 :268 >   yield
2.7.3 :269 >   num.to_s
2.7.3 :270 > end
 => :i_am_not_each
2.7.3 :271 > i_am_not_each(20) { p "Hello你好嗎?,確定你有yield嗎?"}
"Hello你好嗎?,確定你有yield嗎?"
 => "20"
 
2.7.3 :272 > i_am_not_each(20) { return "Hello你好嗎?,確定你有yield嗎?"}
LocalJumpError (unexpected return) #跳失敗了
#yield部分。

2.7.3 :273 > do_some_thing = Proc.new {|string| p string.sum }
 => #<Proc:0x00007fa17b325608 (irb):273>
2.7.3 :274 > do_some_thing.("試試看")
1660
 => 1660
 
2.7.3 :275 > do_some_thing = Proc.new {|string|  string.sum; return "2000" }
 => #<Proc:0x00007fa17b278de0 (irb):275>
2.7.3 :276 > do_some_thing.("試試看")
LocalJumpError (unexpected return)
#物件化也一樣,區塊內強制加return會出錯。

Proc內return不能亂加,但如果就是想要加return?


lambda half life!!??

寫法

2.7.3 :309 > new_lambda = lambda {|num|num+1}
 => #<Proc:0x00007fa17b3c6580 (irb):309 (lambda)>

#另一種寫法
2.7.3 :310 > another_lambda = ->(num){num+1}
 => #<Proc:0x00007fa17b3ed388 (irb):310 (lambda)>

#補充proc另一種寫法
2.7.3 :311 > new_proc = proc {|x|x+1}
 => #<Proc:0x00007fa17b3c5040 (irb):311>

#但是沒有Lambda.new, why?

請注意小寫。

#剛剛出現過的
2.7.3 :042 > Proc.is_a? Class
 => true
2.7.3 :304 > Proc.is_a? Object
 => true
2.7.3 :300 > x = Proc.new {}
 => #<Proc:0x00007fa17b29f0d0 (irb):300>
2.7.3 :301 > x.class
 => Proc

2.7.3 :293 > Lambda.is_a? Object
NameError (uninitialized constant Lambda)
#Lambda不是一種類別,lambda是一個方法

有請手冊

lambda { |...| block } → a_proc
Equivalent to Proc.new, except the resulting Proc objects check the number of parameters passed when called.

再看一下程式碼

2.7.3 :312 > x = Proc.new {}
 => #<Proc:0x00007fa17b30a470 (irb):312>
2.7.3 :313 > x.lambda?
 => false
2.7.3 :315 > new_lambda = lambda {|num|num+1}
 => #<Proc:0x00007fa17b3d6f98 (irb):315 (lambda)>
2.7.3 :316 > new_lambda.class
 => Proc
2.7.3 :317 > new_lambda.lambda?
 => true

接著看實例

def proc_test
  new_proc = Proc.new {return 10}
  int= new_proc.call
  int.to_s
end
 => :proc_test
2.7.3 :325 > proc_test()
 => 10
#這裡並不是Proc可以加return了,是proc的return讓整個程式碼回傳10就停了,所以結果給10。

def lambda_test
  new_lambda = lambda{ return 10}
  int = new_lambda.call
  int.to_s
end
 => :lambda_test
2.7.3 :331 > lambda_test()
 => "10"
#用lambda形成的proc才讓程式碼正確運行。

lanbda像是一種故意要與Proc分開,要形成有另一種特性的Proc。
手冊裡的Proc類別,只有lanbda?方法,但沒有lambda方法。

block,Proc,lambda是Ruby的經典面試題,如果能深入討論Proc與lambda的關係那真的很厲害(大神們常常讓菜鳥我找不到下巴),菜鳥我的文章只說明了皮毛。


第四天的leetcode367. Valid Perfect Square
題目連結:https://leetcode.com/problems/valid-perfect-square/
題目重點:不要引入函式庫,求是不是正整數的平方根,題目很善良,num去掉負數了。

2.7.3 :332 > Math.sqrt(16)
 => 4.0
#不給用這種解法

整理一下。
可能大大們都清楚,下面被標記的那兩行,代表leetcod對給的資料或是解法都有加工處理,有些解法開發工具上可以,但是leetcode上不行,如果發現這樣,代表要仔細再看看題目了。

# @param {Integer} num
# @return {Boolean}
def is_perfect_square(num)

end

puts is_perfect_square(16)  #=> true
puts is_perfect_square(14)  #=> false
def is_perfect_square(num)
  #平方 == 有兩個相同數字相乘
  #兩個連續正整數平方後,中間的數值都不是完美平方
  #那就試到平方 >= num後,停下來判斷。
end

def is_perfect_square(num)
  for int in 0..num
    return int * int == num if int * int >= num
  end
end
#很簡單對不對?

但是有更簡單的

def is_perfect_square(num)
  ans = num ** 0.5
  ans.to_i == ans
end

#原理
2.7.3 :333 > 16 ** 0.5
 => 4.0
2.7.3 :334 > 14 ** 0.5
 => 3.7416573867739413
2.7.3 :335 > 4.0.to_i
 => 4
2.7.3 :336 > 4 == 4.0
 => true
2.7.3 :337 > 3.7416573867739413.to_i
 => 3

來一個跟今天主題比較有關的解法

def is_perfect_square(num)
  (0..num).bsearch { |int| int*int >=num } ** 2 == num
end

#加工一下 比較看得懂
int = (0..num).bsearch { |x| x*x >=num }
int ** 2 == num

bsearch{}https://ruby-doc.org/core-3.0.2/Range.html#method-i-bsearch

By using binary search, finds a value in range which meets the given condition in O(log n) where n is the size of the range.

2.7.3 :340 > [3, 1, 6, 7].bsearch {|num| num > 5 }
 => 6
2.7.3 :341 > [7, 1, 6, 3].bsearch {|num| num > 5 }
 => 6
#單純條件下,會找到最接近的答案傳出

2.7.3 :345 > (0..100).bsearch { |x| x*x >= 240 }
 => 16
2.7.3 :346 > 16 * 16
 => 256

嗯..殺雞不必用牛刀。
但牛刀可以殺牛以下的生物?
雖然感覺還是像跑迴圈一個一個檢查,用在這題反而覺得沒有**0.5好用,可是需要複雜判斷時,就會比較萬用嚕。


今日提到的
1.Block是什麼?
2.Proc, lambda的差別?
3.leetcode367. Valid Perfect Square


上一篇
D-27. 編譯直譯、動態靜態、強型弱型 && Leetcode:Add Digits && Move Zeroes
下一篇
D-25. 枚舉(enumerate) && Intersection of Two Arrays II
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言