數組(array),散列(hash)。array 裡的項目有順序,你可以用索引號得到對應的項目。hash 裡的項目是成對的,一對裡有個 key 還有個對應的 value。得到項目的 value 可以使用項目的 key 。
任何的 Ruby 對象都可以作為 hash 的 key 或者 value ,不過注意 key 在 hash 裡一定是唯一的。hash 在其它的語言上也叫 dictionaries 或者 associative arrays 。
array 與 hash 很像。在某種意義上 array 就是 hash,只不過它的 key 是連續的整數。hash 有某種意義上也是 array,只不過索引可以是任何東西。
做個實驗:
array = ["ruby", "diamond", "emerald"]
hash = { 0 => "ruby", 1 => "diamond", 2 => "emerald" }
puts array[0] # ruby
puts hash[0] # ruby
再做個實驗:
hash = { "red" => "ruby", "white" => "diamond", "green" => "emerald" }
hash.each.with_index {|(key,value),i|
puts "Pair #{i} is: #{key}/#{value}"
}
輸出的是:
Pair 0 is: red/ruby
Pair 1 is: white/diamond
Pair 2 is: green/emerald
數組
建立新數組
有幾種方法:
Array.new 方法
字面構造器:[]
Array 頂級方法
%w{...},%i{...}
Array.new
a = Array.new
設定數組的尺寸與內容:
>> Array.new(3)
=> [nil, nil, nil]
>> Array.new(3, "abc")
=> ["abc", "abc", "abc"]
提供一個代碼塊:
>> n = 0
=> 0
>> Array.new(3) { n += 1; n * 10 }
=> [10, 20, 30]
再做個實驗:
>> a = Array.new(3, "abc")
=> ["abc", "abc", "abc"]
>> a[0] << "def"
=> "abcdef"
>> puts a[1]
abcdef
=> nil
上面的 abc 是同一個東西。想不同的話需要這樣:
Array.new(3) { "abc" }
字面構造器
數組字面構造器是:[]
a = []
建立的時候可以直接往數組裡添加對象:
a = [1,2,"three",4,[]]
Array 方法
>> string = 'hello'
=> "hello"
>> string.respond_to?(:to_ary)
=> false
>> string.respond_to?(:to_a)
=> false
>> Array(string)
=> ["hello"]
>> def string.to_a
>> split(//)
>> end
=> :to_a
>> Array(string)
=> ["h", "e", "l", "l", "o"]
%w 與 %W
>> %w{ I Love You }
=> ["I", "Love", "You"]
%W 可以用雙引號解析。
%i 與 %I
符號數組。
>> %i{ a b c }
=> [:a, :b, :c]
>> w = "love"
=> "love"
>> %I{"#{w}"}
=> [:"\"love\""]
try_convert
>> obj = Object.new
=> #<Object:0x007fa7e39e0ce0>
>> Array.try_convert(obj)
=> nil
>> def obj.to_ary
>> [1,2,3]
>> end
=> :to_ary
>> Array.try_convert(obj)
=> [1, 2, 3]
>> def obj.to_ary
>> "not an array!"
>> end
=> :to_ary
>> Array.try_convert(obj)
TypeError: can't convert Object to Array (Object#to_ary gives String)
from (irb):199:in `try_convert'
from (irb):199
from /usr/local/bin/irb:11:in `<main>'
插入,取回,刪除數組項目
插入項目:
a = []
a[0] = "first"
a[0] 是一種文法糖, 實際上是一個方法的調用,脫了它的糖衣應該是:
a.[]=(0, "first")
取回項目:
>> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> p a[2]
3
=> 3
a[2] 也是文法糖,脫了糖衣應該是:
a.[](2)
一次多重專案
>> a = ["red", "orange", "yellow", "purple", "gray", "indigo", "violet"]
=> ["red", "orange", "yellow", "purple", "gray", "indigo", "violet"]
>> a[3,2]
=> ["purple", "gray"]
>> a[3,2] = "green", "blue"
=> ["green", "blue"]
>> a
=> ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
slice 方法跟 [] 一樣用。
>> a.values_at(0,3)
=> ["red", "green"]
數組的開頭與結尾
unshift 在數組的最前面添加個項目:
>> a = [1,2,3]
=> [1, 2, 3]
>> a.unshift(0)
=> [0, 1, 2, 3]
push 會在數組最後添加項目:
>> a = [1,2,3]
=> [1, 2, 3]
>> a.push(4)
=> [1, 2, 3, 4]
也可以用 << ,像這樣:
>> a << 5
=> [1, 2, 3, 4, 5]
push 可以添加多重專案。
用 shift 與 pop 刪除數組裡的項目:
>> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
>> popped = a.pop
=> 5
>> puts popped
5
=> nil
>> p a
[1, 2, 3, 4]
=> [1, 2, 3, 4]
>> shifted = a.shift
=> 1
>> puts shifted
1
=> nil
>> p a
[2, 3, 4]
=> [2, 3, 4]
合并數組
先試試 concat:
>> [1,2,3].concat([4,5,6])
=> [1, 2, 3, 4, 5, 6]
concat 會改變源數組,如果你想得到一個新的合并以後的數組,可以使用 + :
>> a = [1,2,3]
=> [1, 2, 3]
>> b = a + [4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> a
=> [1, 2, 3]
用 replace 替換:
>> a = [1,2,3]
=> [1, 2, 3]
>> a.replace([4,5,6])
=> [4, 5, 6]
>> a
=> [4, 5, 6]
再做個實驗理解一下 = 與 replace 的區別:
>> a = [1,2,3]
=> [1, 2, 3]
>> b = a
=> [1, 2, 3]
>> a.replace([4,5,6])
=> [4, 5, 6]
>> b
=> [4, 5, 6]
>> a = [7,8,9]
=> [7, 8, 9]
>> b
=> [4, 5, 6]
數組轉型
flatten,去掉數組裡的層級,在參數裡可以指定要去掉的層級數:
>> array = [1,2,[3,4[5]],[6,[7,8]]]
=> [1, 2, [3, 0], [6, [7, 8]]]
>> array.flatten
=> [1, 2, 3, 0, 6, 7, 8]
>> array.flatten(1)
=> [1, 2, 3, 0, 6, [7, 8]]
>> array.flatten(2)
=> [1, 2, 3, 0, 6, 7, 8]
reverse 顛倒:
>> [1,2,3].reverse
=> [3, 2, 1]
join:
>> ["abc", "def", 123].join
=> "abcdef123"
join 的參數:
>> ["abc", "def", 123].join(", ")
=> "abc, def, 123"
還有種合并的方法:
>> a = %w{ one two three }
=> ["one", "two", "three"]
>> a * "-"
=> "one-two-three"
去掉重複:
>> [1,2,3,3].uniq
=> [1, 2, 3]
去掉 nil:
>> zip_codes = ["06511", "08902", "08902", nil, "10027",
"08902", nil, "06511"]
=> ["06511", "08902", "08902", nil, "10027", "08902", nil, "06511"]
>> zip_codes.compact
=> ["06511", "08902", "08902", "10027", "08902", "06511"]
數組查詢
a.size,a.length:項目的數量
a.empty?:空白嗎?
a.include?(item):有某個項目嗎?
a.count(item):這個項目有多少?
a.first(n=1):前幾個項目
a.last(n=1):後幾個項目
a.sample(n=1):隨機項目
實驗:
>> a
=> ["one", "two", "three"]
>> a.first(n=1)
=> ["one"]
>> a.first(n=2)
=> ["one", "two"]
>> a.last(n=1)
=> ["three"]
>> a.sample(n=1)
=> ["two"]
>> a.sample(n=1)
=> ["one"]
hash
先做個實驗,建立一個 hash,根據使用者的輸入,得到地區的縮寫:
p_hash = {
"北京" => "BJ",
"上海" => "SH",
"廣州" => "GZ"
}
print "輸入地區的名字:"
p = gets.chomp
abbr = p_hash[p]
puts "地區的縮寫是 #{abbr}"
建立 hash
幾個方法可以建立 hash:
字面構造器:{}
Hash.new 方法
Hash.[] 方法
Hash 頂級方法
建立字面 hash
大括弧裡面是 hash 項目,每個項目都有 key ,還有對應的 value,它們中間用 => 符號串連,項目與項目之間用逗號分隔開。
Hash.new
Hash.[] 類方法
>> Hash["北京", "BJ", "上海", "SH"]
=> {"北京"=>"BJ", "上海"=>"SH"}
>> Hash[ [[1,2], [3,4], [5,6]] ]
=> {1=>2, 3=>4, 5=>6}
Hash 方法
Hash 方法有點特別的行為。調用的時候給它一個空白的數組([ ])或 nil,它會返回一個空白的 hash。不然它會在參數上調用 to_hash ,如果數組沒 to_hash 方法,就會觸發錯誤。
插入,取回,刪除 hash 項目
添加新項目到 hash 裡
用 []= 方法:
p_hash["山東"] = "SD"
脫掉糖衣應該是這樣的:
p_hash.[]=("山東", "SD")
store 也可以:
p_hash.store("山東", "SD")
做個實驗:
>> h = Hash.new
=> {}
>> h["a"] = 1
=> 1
>> h["a"] = 2
=> 2
>> puts h["a"]
2
=> nil
擷取值
用 [] 方法:
>> p_hash["北京"]
=> "BJ"
用 fetch 方法也行:
>> p_hash.fetch("北京")
=> "BJ"
[] 與 fetch 的區別是,fetch 不存在的 key 的時候會出現異常,而 [] 會返回 nil 。
>> p_hash.fetch("雲南", "Unknown state")
=> "Unknown state"
多個 key :
>> p_hash.values_at("北京", "上海")
=> ["BJ", "SH"]
預設 hash 值與行為
這是預設的情況:
>> h = Hash.new
=> {}
>> h["no such key!"]
=> nil
設定預設的值:
>> h = Hash.new(0)
=> {}
>> h["no such key!"]
=> 0
這樣試一下:
h = Hash.new {|hash,key| hash[key] = 0 }
再這樣:
>> h["new key!"]
=> 0
>> h
=> {"new key!"=>0}
合并 hash
update,有破壞性的合并:
>> h1 = {"Smith" => "John", "Jones" => "Jane" }
=> {"Smith"=>"John", "Jones"=>"Jane"}
>> h2 = {"Smith" => "Jim"}
=> {"Smith"=>"Jim"}
>> h1.update(h2)
=> {"Smith"=>"Jim", "Jones"=>"Jane"}
>> puts h1["Smith"]
Jim
=> nil
merge,不具破壞性的合并:
>> h1 = {"Smith" => "John", "Jones" => "Jane" }
=> {"Smith"=>"John", "Jones"=>"Jane"}
>> h2 = {"Smith" => "Jim"}
=> {"Smith"=>"Jim"}
>> h3 = h1.merge(h2)
=> {"Smith"=>"Jim", "Jones"=>"Jane"}
>> p h1["Smith"]
"John"
=> "John"
hash 變型
選擇與拒絕元素
select:
>> h = Hash[1,2,3,4,5,6]
=> {1=>2, 3=>4, 5=>6}
>> h.select {|k,v| k > 1}
=> {3=>4, 5=>6}
reject:
>> h.reject {|k,v| k > 1}
=> {1=>2}
keep_if:
>> h
=> {1=>2, 3=>4, 5=>6}
>> h.keep_if {|k,v| k > 1}
=> {3=>4, 5=>6}
>> h
=> {3=>4, 5=>6}
delete_if:
>> h
=> {3=>4, 5=>6}
>> h.delete_if {|k,v| k > 3}
=> {3=>4}
>> h
=> {3=>4}
顛倒
invert:
>> h = { 1 => "one", 2 => "two" }
=> {1=>"one", 2=>"two"}
>> h.invert
=> {"one"=>1, "two"=>2}
注意 key 必須得是唯一的。
>> h = { 1 => "one", 2 => "more than 1", 3 => "more than 1" }
=> {1=>"one", 2=>"more than 1", 3=>"more than 1"}
>> h.invert
=> {"one"=>1, "more than 1"=>3}
清空
clear:
>> {1 => "one", 2 => "two"}.clear
=> {}
替換
>> { 1 => "one", 2 => "two" }.replace({ 10 => "ten", 20 => "twenty"})
=> {10 => "ten", 20 => "twenty"}
查詢 hash
h.has_key?(1)
h.include?(1)
h.key?(1)
h.member?(1)
h.has_value?("three")
h.value?("three")
h.empty?
h.size
hash 作為方法的參數
參數列表裡的最後一個參數是個 hash,不用 {} 了。
add_to_city_datebase("New York City",
state: "New York",
population: 7000000,
nickname: "Big Apple"
)
方法應該像這樣定義:
def add_to_city_datebase(name, info)
c = City.new
c.name = name
c.state = info[:state]
c.population = info[:population]
# ...
命名的參數
定義方法的時候這麼幹:
>> def m(a:, b:)
>> p a,b
>> end
=> :m
>> m(a: 1, b: 2)
1
2
=> [1, 2]
預設參數:
>> def m(a: 1, b: 2)
>> p a,b
>> end
=> :m
>> self.m
1
2
=> [1, 2]
>> m(a:10)
10
2
=> [10, 2]
你猜猜 :
>> def m(a: 1, b: 2, **c)
>> p a,b,c
>> end
=> :m
>> m(x: 1, y: 2)
1
2
{:x=>1, :y=>2}
=> [1, 2, {:x=>1, :y=>2}]
>> def m(x, y, *z, a: 1, b:, **c, &block)
>> p x,y,z,a,b,c
>> end
=> :m
>> m(1,2,3,4,5,b:10,p:20,q:30)
1
2
[3, 4, 5]
1
10
{:p=>20, :q=>30}
=> [1, 2, [3, 4, 5], 1, 10, {:p=>20, :q=>30}]
Range
Range:範圍
包含(inclusion)—— 給的值在一個範圍裡嗎?
枚舉(enumeration)—— 把範圍可擷取的單立項目的集合
建立一個 range
Range.new:
>> r = Range.new(1, 100)
=> 1..100
上面建立的是 inclusive range,再建立一個 exclusive range,用三個點:
>> r = 1...100
=> 1...100
Range-inclusion 邏輯
begin 與 end
>> r = 1..10
=> 1..10
>> r.begin
=> 1
>> r.end
=> 10
exclusive range?
>> r.exclude_end?
=> false
cover?
如果給方法的參數大於 range 的起點,小於終點,range 說它 cover 了這個對象。
>> r = "a".."z"
=> "a".."z"
>> r.cover?("a")
=> true
>> r.cover?("abc")
=> true
>> r.cover?("A")
=> false
"a" >= "a"
"a" <= "z"
"abc" >= "a"
"abc" <= "z"
"A" < "a"
不相容的情況:
>> r.cover?([])
=> false
include?
include? 把 range 看成是一個值的集合。
>> r.include?("a")
=> true
>> r.include?("abc")
=> false
>> r = 1.0..2.0
=> 1.0..2.0
>> r.include?(1.5)
=> true
Set
使用 Set 先得:
require 'set'
set 是一個集合,它裡面的項目都是唯一的。項目可以是任何東西,字串,整數,數組,其它的 set 等。
建立 set
Set.new 構造器建立 set 。
>> require('set')
=> false
>> n = [1,2,3]
=> [1, 2, 3]
>> n_set = Set.new(n)
=> #<Set: {1, 2, 3}>
給構造器提供一個代碼塊:
>> names = ["David", "Yukihiro", "Amy"]
=> ["David", "Yukihiro", "Amy"]
>> name_set = Set.new(names) {|name| name.upcase}
=> #<Set: {"DAVID", "YUKIHIRO", "AMY"}>
處理 set 元素
>> n = Set.new([1,2])
=> #<Set: {1, 2}>
>> n << 3
=> #<Set: {1, 2, 3}>
>> n << 3
=> #<Set: {1, 2, 3}>
>> n
=> #<Set: {1, 2, 3}>
delete
>> n.delete(2)
=> #<Set: {1, 3}>
add
>> n.add?(2)
=> #<Set: {1, 3, 2}>
>> n
=> #<Set: {1, 3, 2}>
>> n.add?(2)
=> nil
intersection,union,difference
intersection,別名 &
union,別名 + 與 |
difference,別名 -
這些方法都會返回新的 set。
>> n = Set.new([1, 2, 3])
=> #<Set: {1, 2, 3}>
>> n1 = Set.new([3, 4, 5])
=> #<Set: {3, 4, 5}>
>> n - n1
=> #<Set: {1, 2}>
>> n + n1
=> #<Set: {1, 2, 3, 4, 5}>
>> n & n1
=> #<Set: {3}>
>> n | n1
=> #<Set: {1, 2, 3, 4, 5}>
^
>> n ^ n1
=> #<Set: {4, 5, 1, 2}>
merge
>> n
=> #<Set: {1, 2, 3}>
>> n.object_id
=> 70179526750720
>> n.merge([4])
=> #<Set: {1, 2, 3, 4}>
>> n.object_id
=> 70179526750720
跟 hash 合并:
>> n
=> #<Set: {1, 2, 3, 4}>
>> n.merge({"北京" => "BJ"})
=> #<Set: {1, 2, 3, 4, ["北京", "BJ"]}>
subset 與 superset
subset:子集,superset:超集
>> n = Set.new([1, 2, 3])
=> #<Set: {1, 2, 3}>
>> n1 = Set_new([1, 2])
>> n1.subset?(n)
=> true
>> n.superset?(n1)
=> true
proper_subset 與 proper_superset。proper 子集至少要比父級少一個項目,如果兩個 set 一樣,那它們是彼此的 subset,但不是 proper 子集。