39、Ruby 集合与文件操作全解析

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 的开发之路上更加得心应手。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值