iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0
自我挑戰組

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

D-27. 編譯直譯、動態靜態、強型弱型 && Leetcode:Add Digits && Move Zeroes

Ruby是直譯語言

程式碼要能讓電腦讀懂,一定會有一個轉譯過程。

編譯(Compiled language):將所有程式碼通過編譯器(compiler),編譯成電腦看得懂的機器碼,也就是0與1後,再一起執行。

  • 編譯完成的程式,由於是電腦本身能讀懂的,所以可以直接運行,例如.exe檔,mac的可執行檔。
  • 由於是一次執行,在邏輯相同的條件下,運行上也比較快。
  • 程式碼在編譯時如果有誤,就會報錯,無法執行,會減少執行時發生錯誤。
  • 也因編譯在一開始就會判定型別,所以多為靜態語言,如CC++等。
  • 相比直譯,開發較慢一點。

直譯(Interpreted language)或稱解譯:程式碼執行時,一行一行的由解譯器(interpreter)轉換成機器碼,並且執行。

  • 這類語言的程式在電腦內,必須有相對應的環境才能執行,也就是VM(台灣|IBM)。
  • 因為有解譯的手段,語言設計上可以口語化(交談式)。
  • 型別判定在執行階段,所以多為動態語言,如RubyPythonJavascript
  • 由於可完成一小段程式碼或一個功能時,執行查看結果,開發上比直譯快。
  • 由於邊翻譯邊執行,執行上會比編譯語言慢。

Ruby是強型別語言

強弱型別語言最易之區分為,程式是否對資料型態自動轉換的程度,較高者為弱型別,較弱者為強型別。

例如:

//JavaScript
Welcome to Node.js v14.15.5.
Type ".help" for more information.
> 123 + "456"
'123456'
//型別自動轉換

反之:

#Ruby
2.7.3 :001 > 123 + "456"
TypeError (String can't be coerced into Integer)
#型別無法轉換

#需更改如下
2.7.3 :002 > 123.to_s + "456"
 => "123456"
2.7.3 :003 > 123 + "456".to_i
 => 579

弱型別:型別處理感覺上較寬鬆,較容許資料隱性型別直接轉換。優點在於執行較快,但若未注意型別,可能會得到錯誤的資料。

強型別:型別處理感覺上較嚴謹,較不容許資料隱性型別直接轉換。優點在於資料型別區分清楚,但執行上較慢。
但不是絕對強型或絕對弱型,是比較級的。

Ruby是動態型語言

動態型別語言如之前程式碼示範,於執行階段,會藉由to_i這類程式碼,由解譯器協助轉換型別(於執行期檢查型別),相對轉換資料類別的方法(函式,功能)較多。

wiki
動態程式語言是高階程式語言的一個類別,在電腦科學領域已被廣泛應用。它是一類在執行時可以改變其結構的語言:例如新的函式、物件、甚至代碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。動態語言目前非常具有活力。眾所周知的ECMAScript(JavaScript)便是一個動態語言,除此之外如PHP、Ruby、Python等也都屬於動態語言,而C、C++、Java等語言則不屬於動態語言。

靜態型別語言則是在編譯階段檢查型別。

並不是動態語言就一定是強型別,也不是靜態語言就一定弱型別,wiki引言內的PHP即為動態弱型別語言,Ruby則是強型別動態型語言。依照這些特性,與昨日介紹的鴨子型別介紹,前日的Ruby設計風格,可以知道Ruby的優點是較自由、口語化、類別分類清楚、且有較多語法可針對資料類型直接做轉換處理。


要快速瞭解Ruby設計了多少好方法,只有不斷地練習。

雖然不是刷題刷得多,一定找得到工作,但是知道的越多,要用時才不用找半天,今天繼續分享能感覺到語法懂得多是好處的題型。

Leetcode258.Add Digits
題目連結:https://leetcode.com/problems/add-digits/
題目重點:需要重複處理資料至單個數字為止。
我自己會習慣整理到自己的開發工具內,例如:

# @param {Integer} num
# @return {Integer}
def add_digits(num)

end

puts add_digits(38)  #=> 2
puts add_digits(0)  #=> 0

但建議能習慣於直接在Leetcode上直接操作較好。

題目需要每個數字相加。

2.7.3 :005 > 3 + 8
 => 11
#大於10,要繼續處理。
2.7.3 :006 > 1 + 1
 => 2

#將Integer拆成每個數字分開的狀態
2.7.3 :032 > 38.to_s.split""
 => ["3", "8"] #喔,還要變成數字...
2.7.3 :034 > ["3", "8"].map {|str|str.to_i}
 => [3, 8]
#再將數字相加後判斷是否大於2位數(所以是大於9)
2.7.3 :035 > [3, 8].sum
 => 11
#如果是重複處理,就再如第一條重複處理。
2.7.3 :037 > 11.to_s.split""
 => ["1", "1"]
2.7.3 :038 > ["1", "1"].map {|str|str.to_i}
 => [1, 1]
2.7.3 :039 > [1, 1].sum
 => 2

整理後如下。

def add_digits(num)
  num = num.to_s.split""
  num = num.map {|str| str.to_i}.sum
  num > 9 ? add_digits(num) : num
end

但是如果認識digits這個語法

def add_digits(num)
  num = num.digits.sum
  num > 9 ? add_digits(num) : num
end

2.7.3 :056 > 38.digits
 => [8, 3]

補充

  #利用正整數的除9的餘數 == 正整數每個數相加到最後這個原理
def add_digits(num)
  num == 0 ? 0 : (num%9 == 0 ? 9 : num%9)
end

def
  if num == 0
    0
  elsif num%9 == 0
    9
  else
    num%9
end

個人還是喜歡用digits,Integer.digits => 數字的數字們,真的很口語化。


再一題顯示多了解語法的好處。
Leetcode283.Move Zeroes
題目連結:https://leetcode.com/problems/move-zeroes/
題目重點:要求不做一個新Array。


def move_zeroes(nums)

end

puts move_zeroes([0,1,0,3,12]) #=> [1,3,12,0,0]
puts move_zeroes([0]) #=> 0

這題是可以用快慢指針解法,我先換一個小一點的例子。

 *          #檢查用的指針
[0, 1, 0, 1]
 $          #標記用的指針

先檢查第一個數值是0,那我先不動它,標記它是0。

 *          #檢查指針
[0, 1, 0, 1]
 $          #零在這裡喔

檢查第二個。

    * #檢查指針到第二個位置了
[0, 1, 0 ,1]
 $

發現不是零,那要交換。
    *       #檢查指針
[1, 0, 0 ,1]
    $       #Array中,位置+1 就交換了,所以檢查指針是零時標記指針不動,不是零時+1。

繼續往前檢查。

       *    #檢查指針
[1, 0, 0 ,1]
    $       #零在這裡喔
    
是零,那檢查指針往前。

          * #檢查指針
[1, 0, 0 ,1]
    $       #零在這裡喔

不是零,跟標記的零交換吧。
          *
[1, 1, 0, 0]
       $    #標記位置+1

檢查指針跑完了,也排完了。

#原諒我打字累了,我直接整理吧
def move_zeroes(nums)
  pointer_fast = 0
  pointer_slow = 0
  for pointer_fast in 0..(nums.size-1) do
    if nums[pointer_fast] != 0
      nums[pointer_fast], nums[pointer_slow] = nums[pointer_slow], nums[pointer_fast]
      pointer_slow += 1
    end
  end
  nums
end

換成map.with_index,也感覺沒簡單很多。

def move_zeroes(nums)
  slow = 0
  nums.map.with_index do |num, index|
    if num != 0
      nums[index], nums[slow], = nums[slow], nums[index]
      slow += 1
    end
  end
end

試試口語化的Ruby語法吧

def move_zeroes(nums)
  # 我要知道我有幾個零
  # 把數列中的零都去掉後
  # 從陣列後面加回去
end

def move_zeroes(nums)
  zero_count = nums.count(0)
  nums.delete(0)
  zero_count.times {nums.push(0)}
end
#簡單易懂許多

這還不夠簡單,就試試大神分享的咒語吧!

def move_zeroes(nums)
  #陣列呀,依照0是比較大的往後排吧!
end

def move_zeroes(nums)
  nums.sort_by! { |num| num.zero? ? 1 : 0 }
end

我又開始不正經了

沒有錯,在一開始,除非剛好看過,怎麼可能知道利用sort_by
沒有練習,只有看,可能看完一個class內所有方法時,前一個class的方法就都忘光了。
所以只有練習再練習,沒看過的神奇語法,就去瞭解怎麼用。
多用幾次,就會是自己的了。

首先sort_bysort_by!的差異

2.7.3 :058 > str = ["abc", "ab", "a"]
 => ["abc", "ab", "a"]
2.7.3 :059 > str.sort_by {|s|s.length}
 => ["a", "ab", "abc"]
2.7.3 :060 > str
 => ["abc", "ab", "a"] 
#可以發現,雖然有回傳["a", "ab", "abc"],但str本身並無改變。

2.7.3 :061 > str.sort_by! {|s|s.length}
 => ["a", "ab", "abc"]
2.7.3 :062 > str
 => ["a", "ab", "abc"]
#多了!後str自身改變了。

再來將程式碼改回用do...end,以及不用三元運算子

[0,1,0,3,12].sort_by! do |num|
  if num == 0
    1
  else
    0
  end
end
 => [1, 3, 12, 0, 0]
#依照如果0給1這個比較大的值,不是0的給與0這個比較小的值,依照給予的值來排列。
#即使是寫
2.7.3 :071 > [0,1,0,3,12].sort_by! {|nun|nun.zero? ? 999:9}
 => [1, 3, 12, 0, 0]
#也是一樣的意思,只是這樣寫會被唸....

如果還是看不懂,沒關係,那我們再多看看程式碼。

2.7.3 :081 > [5, 2, 7, 8, 3, 1].sort
 => [1, 2, 3, 5, 7, 8]
#sort語法本身就是依照值的大小排
2.7.3 :082 > [5, 2, 7, 8, 3, 1].sort_by {|num| num}
 => [1, 2, 3, 5, 7, 8]
#sort_by,很直覺感覺就是可以依照我們指定的規則來排序,沒有就是依照原本小到大的規則。

nums.sort_by! { |num| num.zero? ? 1 : 0 }
# if == 0 那就給你比較大的值 1,if != 0 那就給你比較小的值 0。
#即使改寫如下,一樣的意思,規則定好枚舉方法,block內就會是你的許願池
2.7.3 :084 > [0,1,0,3,12].sort_by! {|nun| nun != 0 ? 0 : 1}
 => [1, 3, 12, 0, 0]

本日提到的

1.Ruby是直譯,動態類型,強型別語言。
2.Ruby真好玩,快來學!

明日開始對Ruby更好玩的部分block做說明,一樣會盡量以實作取代說明。


上一篇
D-28.鴨子型別, 字串, 陣列, 範圍, 雜湊
下一篇
D-26.Block、Proc、lambda && Valid Perfect Square
系列文
初級紅寶石魔法師心得分享。30

尚未有邦友留言

立即登入留言