Ruby 集合与文件操作全解析
1. 哈希(Hash)的高级用法
1.1 默认值处理
在 Ruby 中,当哈希(Hash)的键未定义时,可以使用默认块来返回键的后继值。例如:
plus1 = Hash.new {|hash, key| key.succ }
puts plus1[1] # 输出: 2
puts plus1["one"] # 输出: "onf"
puts plus1.default_proc # 返回计算默认值的 Proc
puts plus1.default(10) # 输出: 11
通常,为了避免重复计算,会将计算得到的值与键关联起来。以下是一个将整数映射到其阶乘的延迟初始化哈希示例:
fact = Hash.new {|h,k| h[k] = if k > 1 then k*h[k-1] else 1 end }
puts fact # 输出: {}
puts fact[4] # 输出: 24
puts fact # 输出: {1=>1, 2=>2, 3=>6, 4=>24}
需要注意的是,设置哈希的默认属性会覆盖传递给
Hash.new
构造函数的任何块。如果不想使用哈希的默认值,或者想使用自己的默认值,可以使用
fetch
方法来检索值,而不是使用方括号:
fact.fetch(5) # 抛出 IndexError: key not found
1.2 哈希码、键的相等性和可变键
要将对象用作哈希键,该对象必须有一个
hash
方法,该方法返回对象的整数“哈希码”。如果没有定义自己的
eql?
方法,可以使用从
Object
继承的
hash
方法。但如果定义了
eql?
方法来测试对象相等性,则必须定义相应的
hash
方法。如果两个不同的对象被认为相等,它们的
hash
方法必须返回相同的值。
在 Ruby 1.9 中,可以调用
compare_by_identity
方法强制哈希使用
equal?
来比较键,此时哈希将使用
object_id
作为任何对象的哈希码。例如:
h = { :a => 1, :b => 2 }
h.compare_by_identity
使用可变对象作为哈希键时要小心。如果使用了可变键并对其中一个进行了修改,必须调用
rehash
方法以确保哈希正常工作:
key = {:a=>1}
h = { key => 2 }
puts h[key] # 输出: 2
key.clear
puts h[key] # 输出: nil
h.rehash
puts h[key] # 输出: 2
1.3 其他哈希方法
-
invert方法 :用于交换哈希中的键和值。
h = {:a=>1, :b=>2}
puts h.invert # 输出: {1=>:a, 2=>:b}
-
to_s和inspect方法 :在 Ruby 1.8 中,Hash.to_s方法不太有用,建议使用inspect方法将哈希转换为字符串。在 Ruby 1.9 中,to_s和inspect是相同的。
puts {:a=>1, :b=>2}.to_s # Ruby 1.8 输出: "a1b2"; Ruby 1.9 输出: "{:a=>1, :b=>2}"
puts {:a=>1, :b=>2}.inspect # 两个版本都输出: "{:a=>1, :b=>2}"
2. 集合(Set)的使用
2.1 集合概述
集合是一个没有重复值的集合,与数组不同,集合中的元素没有顺序。可以将哈希视为键/值对的集合,反之,集合可以使用哈希来实现,其中集合元素作为键存储,值被忽略。排序集合是对其元素施加顺序的集合,但不允许像数组那样随机访问。集合的一个特点是具有快速的成员测试、插入和删除操作。
在 Ruby 中,没有内置的集合类型,但标准库包含
Set
和
SortedSet
类,使用前需要先引入:
require 'set'
2.2 创建集合
由于
Set
不是 Ruby 的核心类,没有创建集合的字面语法。可以使用
Enumerable
模块的
to_set
方法从任何可枚举对象创建集合,也可以将任何可枚举对象传递给
Set.new
。如果提供了块,则在将枚举值添加到集合之前,使用该块对其进行预处理。还可以使用
Set
类的
[]
运算符来枚举集合的成员:
puts (1..5).to_set # 输出: #<Set: {5, 1, 2, 3, 4}>
puts [1,2,3].to_set # 输出: #<Set: {1, 2, 3}>
puts Set.new(1..5) # 输出: #<Set: {5, 1, 2, 3, 4}>
puts Set.new([1,2,3]) # 输出: #<Set: {1, 2, 3}>
puts Set.new([1,2,3]) {|x| x+1} # 输出: #<Set: {2, 3, 4}>
puts Set["cow", "pig", "hen"] # 输出: #<Set: {"cow", "pig", "hen"}>
2.3 测试、比较和组合集合
-
成员测试
:最常见的集合操作是成员测试,可以使用
include?或member?方法。
s = Set.new(1..3)
puts s.include? 1 # 输出: true
puts s.member? 0 # 输出: false
- 子集和超集测试 :可以测试一个集合是否是另一个集合的子集或超集。
s = Set[2, 3, 5]
t = Set[2, 3, 5, 7]
puts s.subset? t # 输出: true
puts t.subset? s # 输出: false
puts s.proper_subset? t # 输出: true
puts t.superset? s # 输出: true
puts t.proper_superset? s # 输出: true
puts s.subset? s # 输出: true
puts s.proper_subset? s # 输出: false
-
集合大小和空集测试
:
Set定义了与Array和Hash相同的大小方法。
s = Set[2, 3, 5]
puts s.length # 输出: 3
puts s.size # 输出: 3
puts s.empty? # 输出: false
puts Set.new.empty? # 输出: true
-
集合组合
:可以使用运算符
&、|、-和^以及命名方法别名来组合两个现有集合。
primes = Set[2, 3, 5, 7]
odds = Set[1, 3, 5, 7, 9]
puts primes & odds # 输出: #<Set: {5, 7, 3}>
puts primes.intersection(odds) # 输出: #<Set: {5, 7, 3}>
puts primes | odds # 输出: #<Set: {5, 1, 7, 2, 3, 9}>
puts primes.union(odds) # 输出: #<Set: {5, 1, 7, 2, 3, 9}>
puts primes-odds # 输出: #<Set: {2}>
puts odds-primes # 输出: #<Set: {1, 9}>
puts primes.difference(odds) # 输出: #<Set: {2}>
puts primes ^ odds # 输出: #<Set: {1, 2, 9}>
2.4 添加和删除集合元素
-
添加元素
:可以使用
<<运算符或add方法添加单个元素,使用merge方法添加多个元素。
s = Set[]
s << 1
s.add 2
s << 3 << 4 << 5
s.add 3
s.add? 6
s.add? 3
puts s
s = (1..3).to_set
s.merge(2..5)
puts s
-
删除元素
:可以使用
delete或delete?方法删除单个元素,使用subtract方法删除多个元素,使用delete_if或reject!方法选择性地删除元素。
s = (1..3).to_set
s.delete 1
s.delete 1
s.delete? 1
s.delete? 2
puts s
s = (1..3).to_set
s.subtract(2..10)
puts s
primes = Set[2, 3, 5, 7]
primes.delete_if {|x| x%2==1}
primes.delete_if {|x| x%2==1}
primes.reject! {|x| x%2==1}
puts primes
s = (1..5).to_set
t = (4..8).to_set
s.reject! {|x| not t.include? x}
puts s
-
清空和替换集合
:可以使用
clear和replace方法清空和替换集合中的元素。
s = Set.new(1..3)
s.replace(3..4)
s.clear
puts s.empty?
2.5 集合迭代器
集合是可枚举的,
Set
类定义了
each
迭代器,用于遍历集合中的每个元素。在 Ruby 1.9 中,集合的迭代顺序与元素插入的顺序相同。此外,
map!
和
collect!
迭代器用于转换集合中的每个元素。
s = Set[1, 2, 3, 4, 5]
s.each {|x| print x }
s.map! {|x| x*x }
s.collect! {|x| x/2 }
puts s
2.6 其他集合方法
-
to_a、to_s和inspect方法 :to_a方法将集合转换为数组,to_s方法输出集合的对象信息,inspect方法输出集合的内容。
s = (1..3).to_set
puts s.to_a # 输出: [1, 2, 3]
puts s.to_s # 输出: "#<Set:0xb7e8f938>"
puts s.inspect # 输出: "#<Set: {1, 2, 3}>"
puts s == Set[3,2,1] # 输出: true
-
classify方法 :根据块的返回值对集合元素进行分类,返回一个哈希,该哈希将块的返回值映射到返回该值的元素集合。
s = (0..3).to_set
puts s.classify {|x| x%2} # 输出: {0=>#<Set: {0, 2}>, 1=>#<Set: {1, 3}>}
-
divide方法 :将集合划分为子集,根据块的返回值将元素分组。
s = (0..3).to_set
puts s.divide {|x| x%2} # 输出: #<Set: {#<Set: {0, 2}>, #<Set: {1, 3}>}>
s = %w[ant ape cow hen hog].to_set
puts s.divide {|x,y| x[0] == y[0]} # 输出: #<Set:{#<Set:{"hog", "hen"}>, #<Set:{"cow"}>, #<Set:{"ape", "ant"}>}>
-
flatten和flatten!方法 :用于将集合的子集展平为一个更大的集合。
s = %w[ant ape cow hen hog].to_set
t = s.divide {|x,y| x[0] == y[0]}
t.flatten!
puts t == s
以下是集合操作的流程图:
graph TD;
A[创建集合] --> B[测试集合];
B --> C[组合集合];
C --> D[添加元素];
D --> E[删除元素];
E --> F[迭代集合];
F --> G[其他操作];
集合操作总结
| 操作类型 | 方法或运算符 | 描述 |
|---|---|---|
| 创建集合 |
to_set
、
Set.new
、
Set[]
| 从可枚举对象创建集合 |
| 测试集合 |
include?
、
member?
、
subset?
、
superset?
等
| 测试元素或集合的关系 |
| 组合集合 |
&
、
|
、
-
、
^
、
intersection
、
union
、
difference
等
| 组合两个集合 |
| 添加元素 |
<<
、
add
、
merge
| 向集合中添加元素 |
| 删除元素 |
delete
、
delete?
、
subtract
、
delete_if
、
reject!
| 从集合中删除元素 |
| 迭代集合 |
each
、
map!
、
collect!
| 遍历和转换集合元素 |
| 其他操作 |
to_a
、
to_s
、
inspect
、
classify
、
divide
、
flatten
、
flatten!
| 集合的其他操作 |
3. 文件和目录操作
3.1 文件名和目录名的操作
在 Ruby 中,
File
和
Dir
类的类方法用于处理文件和目录。Ruby 使用 Unix 风格的文件名,以
/
作为目录分隔符,即使在 Windows 平台上也可以使用正斜杠。
File::SEPARATOR
常量在所有实现中都应为
/
,
File::ALT_SEPARATOR
在 Windows 上为
\
,在其他平台上为
nil
。
File
类提供了一些操作文件名的方法,具体如下表所示:
| 方法 | 描述 | 示例 |
| ---- | ---- | ---- |
|
basename
| 获取文件名 |
File.basename('/home/matz/bin/ruby.exe') # => 'ruby.exe'
|
|
basename
(带扩展名参数) | 获取不带扩展名的文件名 |
File.basename('/home/matz/bin/ruby.exe', '.exe') # => 'ruby'
|
|
dirname
| 获取文件所在目录 |
File.dirname('/home/matz/bin/ruby.exe') # => '/home/matz/bin'
|
|
split
| 分割路径和文件名 |
File.split('/home/matz/bin/ruby.exe') # => ['/home/matz/bin', 'ruby.exe']
|
|
extname
| 获取文件扩展名 |
File.extname('/home/matz/bin/ruby.exe') # => '.exe'
|
|
join
| 拼接路径 |
File.join('home','matz') # => 'home/matz'
|
|
expand_path
| 将相对路径转换为绝对路径 |
File.expand_path("ruby", "/usr/local/bin") # => "/usr/local/bin/ruby"
|
|
identical?
| 测试两个文件名是否指向同一个文件 |
File.identical?("ruby", "/usr/bin/ruby") # => true if CWD is /usr/bin
|
|
fnmatch
| 测试文件名是否匹配指定模式 |
File.fnmatch("*.rb", "hello.rb") # => true
|
下面是这些方法的代码示例:
full = '/home/matz/bin/ruby.exe'
file = File.basename(full)
puts file # 输出: ruby.exe
puts File.basename(full, '.exe') # 输出: ruby
dir = File.dirname(full)
puts dir # 输出: /home/matz/bin
puts File.dirname(file) # 输出: .
puts File.split(full) # 输出: ["home/matz/bin", "ruby.exe"]
puts File.extname(full) # 输出: .exe
puts File.extname(file) # 输出: .exe
puts File.extname(dir) # 输出:
puts File.join('home','matz') # 输出: home/matz
puts File.join('','home','matz') # 输出: /home/matz
Dir.chdir("/usr/bin")
puts File.expand_path("ruby") # 输出: /usr/bin/ruby
puts File.expand_path("~/ruby") # 输出: /home/david/ruby
puts File.expand_path("~matz/ruby") # 输出: /home/matz/ruby
puts File.expand_path("ruby", "/usr/local/bin") # 输出: /usr/local/bin/ruby
puts File.expand_path("ruby", "../local/bin") # 输出: /usr/local/bin/ruby
puts File.expand_path("ruby", "~/bin") # 输出: /home/david/bin/ruby
puts File.identical?("ruby", "ruby") # 输出: true if the file exists
puts File.identical?("ruby", "/usr/bin/ruby") # 输出: true if CWD is /usr/bin
puts File.identical?("ruby", "../bin/ruby") # 输出: true if CWD is /usr/bin
puts File.identical?("ruby", "ruby1.9") # 输出: true if there is a link
puts File.fnmatch("*.rb", "hello.rb") # 输出: true
puts File.fnmatch("*.[ch]", "ruby.c") # 输出: true
puts File.fnmatch("*.[ch]", "ruby.h") # 输出: true
puts File.fnmatch("?.txt", "ab.txt") # 输出: false
flags = File::FNM_PATHNAME | File::FNM_DOTMATCH
puts File.fnmatch("lib/*.rb", "lib/a.rb", flags) # 输出: true
puts File.fnmatch("lib/*.rb", "lib/a/b.rb", flags) # 输出: false
puts File.fnmatch("lib/**/*.rb", "lib/a.rb", flags) # 输出: true
puts File.fnmatch("lib/**/*.rb", "lib/a/b.rb", flags) # 输出: true
3.2 列出目录内容
可以使用
Dir.entries
方法或
Dir.foreach
迭代器来列出目录的内容,具体操作步骤如下:
1. 使用
Dir.entries
方法获取目录下所有文件和文件夹的名称,返回一个数组。
2. 使用
Dir.foreach
迭代器遍历目录下的文件和文件夹。
示例代码如下:
# 获取 config 目录下所有文件的名称
filenames = Dir.entries("config")
puts filenames
# 遍历 config 目录下的文件名称
Dir.foreach("config") {|filename| puts filename }
以下是文件和目录操作的流程图:
graph TD;
A[文件名操作] --> B[列出目录内容];
B --> C[其他文件操作];
3.3 总结
本文详细介绍了 Ruby 中哈希、集合以及文件和目录操作的相关知识。哈希部分包括默认值处理、哈希码和键的相等性、可变键的使用以及其他常用方法;集合部分涵盖了集合的创建、测试、比较、组合、元素的添加和删除、迭代以及其他高级操作;文件和目录操作部分介绍了文件名和目录名的处理方法以及列出目录内容的方式。
通过掌握这些知识,开发者可以更高效地处理数据集合和文件系统,提高 Ruby 程序的开发效率和质量。希望本文能对大家有所帮助,让大家在 Ruby 的开发之路上更加得心应手。
超级会员免费看
13

被折叠的 条评论
为什么被折叠?



