Ruby 札記 - Ruby 集合家族之陣列(Array)

GracKanil發表於2018-08-20

學習 Ruby 集合我覺得最好的方式是開啟 irb --simple-prompt 命令,跟著例子學習。試著藉助 Tab 補全加以思考?。

建立陣列

和其他動態語言建立相似。

>> a = [1, 2, 3]
=> [1, 2, 3]
>> a.class
=> Array
>> a.length
=> 3
>> a.size
=> 3
>> a[2]
=> 3
>> a << 4
=> [1, 2, 3, 4]
>> a
=> [1, 2, 3, 4]
複製程式碼

其中型別不必相同,可以同時是 String 、Number 等

?> a = [1, "Grac Kanil", [2, 3]]
=> [1, "Grac Kanil", [2, 3]]
>>
?> a << true
=> [1, "Grac Kanil", [2, 3], true]
>> a << 3.14
=> [1, "Grac Kanil", [2, 3], true, 3.14]
複製程式碼

一切皆物件,當然陣列跑不了這個範疇

?> a = Array.new([1, "Hello", 2])
=> [1, "Hello", 2]
複製程式碼

陣列建立可以傳引數,提前申請控制元件,但是陣列是動態的,如果傳入陣列,會建立包含 nil 的陣列。nil 也是一個物件,所以再追加,會在 nil 之後新增。當然可以快速傳入預設值,佔位陣列。

?> a = Array.new(3)
=> [nil, nil, nil]
>>
?> a = Array.new(3, 1)
=> [1, 1, 1]
>>
?>
?> a = Array.new(3)
=> [nil, nil, nil]
>>
?> a << 3
=> [nil, nil, nil, 3]
?> a.size
=> 4
複製程式碼

切割字串為陣列

  • scan 方法
>> s = "This is a test string."
=> "This is a test string."
>> s.scan(/\w/).join(",")
=> "T,h,i,s,i,s,a,t,e,s,t,s,t,r,i,n,g"
?> s.scan(/\b\w*\b/)
=> ["This", "", "is", "", "a", "", "test", "", "string", ""]
複製程式碼
  • split 方法
>> s = "This is a test string."
=> "This is a test string."
?> s.split(" ")
=> ["This", "is", "a", "test", "string."]
>> s.split(" ").inspect
=> "[\"This\", \"is\", \"a\", \"test\", \"string.\"]"

?> s.split(/\s+/)
=> ["This", "is", "a", "test", "string."]
>> s.split(/\s+/).inspect
=> "[\"This\", \"is\", \"a\", \"test\", \"string.\"]"
複製程式碼
  • % 來實現的語法捷徑
?> est = "est"
=> "est"
>> %W{This is a t#{est} string.}
=> ["This", "is", "a", "test", "string."]
>>
?> %w{This is a t#{est} string.}
=> ["This", "is", "a", "t\#{est}", "string."]
複製程式碼

陣列越界問題

很多語言訪問不存在的索引,會丟擲陣列越界異常,但 ruby 中不會,會返回 nil 哦

?> a = []
=> []
>> a[4]
=> nil
複製程式碼

cool!? 以上是讀,如果寫入呢?

?> a
=> []
>> a[3] = "Grac"
=> "Grac"
>> a
=> [nil, nil, nil, "Grac"]
>> a[7] = "Kanil"
=> "Kanil"
>>
?> a
=> [nil, nil, nil, "Grac", nil, nil, nil, "Kanil"]
複製程式碼

so cool! 在很多語言中,如果你的索引值小於 0 呢,又會怎樣?

?> a = [1, 2, 3]
=> [1, 2, 3]
>>
?> a[-1]
=> 3
>>
?> a[-4]
=> nil
複製程式碼

wonderful!

陣列分類

表示陣列前兩個元素的多種表示方法

?> strings = %w{a b c d e f g}
=> ["a", "b", "c", "d", "e", "f", "g"]
>>
?> strings[0..1]
=> ["a", "b"]
>> strings[0...1]
=> ["a"]
>> strings[0...2]
=> ["a", "b"]
>> strings[0,2]
=> ["a", "b"]
>> strings[-7,2]
=> ["a", "b"]
>> strings[-7..-6]
=> ["a", "b"]
>> strings[-7...2]
=> ["a", "b"]
>> strings[-7..1]
=> ["a", "b"]
>> strings[0..-6]
=> ["a", "b"]
>> strings[0...-5]
=> ["a", "b"]
複製程式碼

進階思考

>> (0..1).class
=> Range
?> strings.[](0..1)
=> ["a", "b"]
>> strings.[](Range.new(0,1))
=> ["a", "b"]
複製程式碼

有趣的 numerouno

➜  ~ gem install numerouno
Fetching: numerouno-0.2.0.gem (100%)
Successfully installed numerouno-0.2.0
Parsing documentation for numerouno-0.2.0
Installing ri documentation for numerouno-0.2.0
Done installing documentation for numerouno after 0 seconds
1 gem installed
➜  ~ irb --simple-prompt

?> require 'numerouno'
=> true
>>
?> "sixty one".as_number
=> 61

# 為陣列新增英文索引
?> class EnglishArray < Array
>>   def [](idx)
>>     if String === idx
>>       self.at(idx.as_number)
>>     end
>>   end
>> end
=> :[]
>>
?> array = EnglishArray.new([1, 2, 3, 4])
=> [1, 2, 3, 4]
>>
?> array["one"]
=> 2
>> array["three"]
=> 4
複製程式碼

Ruby 陣列的萬能結構

  • 棧 可以將陣列作為。其功能和 Python list 相似,使用 poppush 方法。
?> a = []
=> []
>> a.push(3)
=> [3]
>> a.push("Grac")
=> [3, "Grac"]
>>
?> a.push("Kanil")
=> [3, "Grac", "Kanil"]
>>
?>
?> a.pop
=> "Kanil"
>> a.pop
=> "Grac"
>> a.pop
=> 3
>> a
複製程式碼
  • 佇列 佇列,即先進先出
?> a = []
=> []
>> a.push(1)
=> [1]
>> a.push(2)
=> [1, 2]
>> a.push(3)
=> [1, 2, 3]
>> a.shift
=> 1
>> a.shift
=> 2
>> a.shift
=> 3
>> a
=> []
複製程式碼

還可以使用 unshift 隊首加入新元素

?> a = []
=> []
>> a.push(1)
=> [1]
>> a.unshift(2)
=> [2, 1]
>> a.unshift(3)
=> [3, 2, 1]
複製程式碼

使用 delete 刪除任意位置元素

?> a = [1, 2, 3]
=> [1, 2, 3]
>> a.delete(2)
=> 2
>> a
=> [1, 3]
複製程式碼
  • 集合
?> [1, 2, 3] + [4, 5, 6]
=> [1, 2, 3, 4, 5, 6]

?> [1, 2, 3] + [2, 3]
=> [1, 2, 3, 2, 3]

>> [1, 2, 3].concat([4, 5, 6])
=> [1, 2, 3, 4, 5, 6]

>> ["a", 1, 2, 3, "b"] - ["a", "b"]
=> [1, 2, 3]

>> [1, 2, 4] & [1, 2, 3]
=> [1, 2]

>> [1, 2, 4] | [1, 2, 3]
=> [1, 2, 4, 3]
複製程式碼

Ruby 陣列常用方法

  • 檢查陣列是否為空
>> a = []
=> []
>> a.empty?
=> true
複製程式碼
  • 移動元素
# 轉置
?> [1, 2, 4].reverse
=> [4, 2, 1]

# 向左移動一
?> [1, 2, 4].rotate
=> [2, 4, 1]

# 向右移動一
?> [1, 2, 4].rotate(-1)
=> [4, 1, 2]
複製程式碼
  • 安全提示
?> a = [1, 2, 3]
=> [1, 2, 3]
>> a.freeze
=> [1, 2, 3]
>> a << 4
RuntimeError: can't modify frozen Array
	from (irb):85
	from /Users/gekang/.rvm/rubies/ruby-2.2.4/bin/irb:11:in `<main>'
複製程式碼
  • 陣列的 join 方法,將元素都連結為字串
?> a = [1, 2, "Grac", "Kanil"]
=> [1, 2, "Grac", "Kanil"]
>>
?> a.join
=> "12GracKanil"
複製程式碼

當然,可以傳入引數

?> a.join(",")
=> "1,2,Grac,Kanil"
複製程式碼
  • 刪除巢狀
?> [1, [2, 3], [4, ["a", nil]]].flatten
=> [1, 2, 3, 4, "a", nil]
>>
?> [1, [2, 3], [4, ["a", nil]]].flatten(1)
=> [1, 2, 3, 4, ["a", nil]]
複製程式碼
  • 刪除副本
?> [4, 1, 2, 3, 1, 4].uniq
=> [4, 1, 2, 3]
複製程式碼
  • 檢查是否包含某元素
?> a = [1, "a", 2]
=> [1, "a", 2]
>> a.include? "a"
=> true
>> a.include? 1
=> true
>> a.include?(1)
=> true
複製程式碼
  • 分割陣列
?> a = [1, 2, 3, 4, 5, 6]
=> [1, 2, 3, 4, 5, 6]
>> a.first
=> 1
>> a.last
=> 6
>> a.first(3)
=> [1, 2, 3]
>> a.last(4)
=> [3, 4, 5, 6]
複製程式碼
  • 統計陣列中某元素的個數
?> a = [4, 1, 2, 3, 1, 4]
=> [4, 1, 2, 3, 1, 4]
>> a.count 4
=> 2
複製程式碼
  • 查詢陣列所有方法
?> Array.methods
=> [:[], :try_convert, :allocate, :new, :superclass, :freeze, :===, :==, :<=>, :<, :<=, :>, :>=, :to_s, 
:inspect, :included_modules, :include?, :name, :ancestors, :instance_methods, :public_instance_methods, :protected_instance_methods, :private_instance_methods, :constants, :const_get, :const_set, 
:const_defined?, :const_missing, :class_variables, :remove_class_variable, :class_variable_get, 
:class_variable_set, :class_variable_defined?, :public_constant, :private_constant, :singleton_class?, 
:include, :prepend, :module_exec, :class_exec, :module_eval, :class_eval, :method_defined?, 
:public_method_defined?, :private_method_defined?, :protected_method_defined?, :public_class_method, :private_class_method, :autoload, :autoload?, :instance_method, :public_instance_method, :nil?, :=~, 
:!~, :eql?, :hash, :class, :singleton_class, :clone, :dup, :itself, :taint, :tainted?, :untaint, 
:untrust, :untrusted?, :trust, :frozen?, :methods, :singleton_methods, :protected_methods, 
:private_methods, :public_methods, :instance_variables, :instance_variable_get, :instance_variable_set, :instance_variable_defined?, :remove_instance_variable, :instance_of?, :kind_of?, :is_a?, :tap, 
:send, :public_send, :respond_to?, :extend, :display, :method, :public_method, :singleton_method, :define_singleton_method, :object_id, :to_enum, :enum_for, :equal?, :!, :!=, :instance_eval, 
:instance_exec, :__send__, :__id__]
複製程式碼

迭代

迭代可以說是 Ruby 的一大亮點,我覺得 Objective-C 中的迭代就很少使用,比較笨重,更多的會想到寫一個 for 迴圈,然而,Ruby 並不會,優雅的迭代使用起來比較順手。

each

Ruby 中迭代的核心應該就是 each 方法了

?> a = ["This", "is", "a", "test", "string"]
=> ["This", "is", "a", "test", "string"]
>>
?> a.each {|s| puts s.cap} # Tab 鍵可以聯想
s.capitalize   s.capitalize!  s.captures

?> a.each { |s| puts s.capitalize }
This
Is
A
Test
String
=> ["This", "is", "a", "test", "string"]
複製程式碼

還有 reverse_each 反向迭代

?> a = ["This", "is", "a", "test", "string"]
=> ["This", "is", "a", "test", "string"]
>> a.reverse_each { |s| puts s.up }
s.upcase   s.upcase!  s.update   s.upto
>> a.reverse_each { |s| puts s.upcase }
STRING
TEST
A
IS
THIS
=> ["This", "is", "a", "test", "string"]
複製程式碼

如果你需要索引,當然也給你準備好了當前處理的索引 each_with_index

>> a = ["This", "is", "a", "test", "string"]
=> ["This", "is", "a", "test", "string"]
>> a.each_with_index { |s, index| puts "#{index} is #{s}" }
0 is This
1 is is
2 is a
3 is test
4 is string
=> ["This", "is", "a", "test", "string"]
複製程式碼

如果你需要迭代陣列中某一部分,可以使用分割陣列

?> a = ["This", "is", "a", "test", "string"]
=> ["This", "is", "a", "test", "string"]
>> a[0..2]
=> ["This", "is", "a"]
>> a[0..2].each {|s| puts s}
This
is
a
=> ["This", "is", "a"]
複製程式碼

也可以使用 Range 來實現

?> a = ["This", "is", "a", "test", "string"]
=> ["This", "is", "a", "test", "string"]
>>
?> (0..2).each {|i| puts a[i]}
This
is
a
=> 0..2
複製程式碼

map 或 collect

>> a = [1, 2, 3]
=> [1, 2, 3]
>> a.map { |i| i + 1 }
=> [2, 3, 4]
複製程式碼

如果只是讓每一個元素呼叫方法,不關心每個元素的值時,可以使用快捷寫法

>> a = [1, 2, 3]
=> [1, 2, 3]
>> a.map(&:to_f)
=> [1.0, 2.0, 3.0]
複製程式碼

#map 不改變原來的陣列,而是會生成新的陣列,如果需要修改原陣列,需要使用 #map! 方法

>> a = [1, 2, 3]
=> [1, 2, 3]
>> a.map(&:to_f)
=> [1.0, 2.0, 3.0]
>>
?> a
=> [1, 2, 3]
>> a.map!(&:to_f)
=> [1.0, 2.0, 3.0]
>> a
=> [1.0, 2.0, 3.0]
複製程式碼

#map#collect 相同,可以互換使用

?> a = [1, 2, 3]
=> [1, 2, 3]
>> a.map(&:to_f)
=> [1.0, 2.0, 3.0]
>> a.collect(&:to_f)
=> [1.0, 2.0, 3.0]
複製程式碼