Ruby范围 #

一、范围概述 #

范围(Range)表示一个区间,包含起始值和结束值。范围常用于表示数字区间、字符区间、日期区间等。

ruby
(1..5)
(1...5)
("a".."z")
(Time.now..Time.now + 3600)

二、创建范围 #

2.1 字面量语法 #

ruby
r1 = 1..5
r2 = 1...5
r3 = "a".."z"
r4 = "a"..."z"
r5 = (1..5)
r6 = Range.new(1, 5)
r7 = Range.new(1, 5, true)

2.2 闭区间与半开区间 #

ruby
closed = 1..5
half_open = 1...5

closed.include?(5)
half_open.include?(5)

closed.to_a
half_open.to_a

2.3 无限范围 #

Ruby 2.6+ 支持无限范围:

ruby
(1..)
(1...)
(..10)
(...10)
(-Float::INFINITY..Float::INFINITY)

三、范围方法 #

3.1 基本属性 #

ruby
r = 1..5

r.begin
r.end
r.first
r.last
r.min
r.max
r.size
r.length
r.count

3.2 包含检查 #

ruby
r = 1..5

r.include?(3)
r.include?(5)
r.include?(6)
r.member?(3)
r === 3
r.cover?(3)
r.cover?(6)

3.3 include? vs cover? #

ruby
r = "a".."z"

r.include?("aa")
r.cover?("aa")

r = 1..5
r.cover?(3.5)
r.include?(3.5)

3.4 边界检查 #

ruby
r = 1..5

r.begin
r.end
r.first
r.last
r.first(3)
r.last(3)
r.minmax

四、范围迭代 #

4.1 each迭代 #

ruby
(1..5).each { |n| puts n }
("a".."e").each { |c| puts c }
(1..5).each_with_index { |n, i| puts "#{i}: #{n}" }

4.2 step步进 #

ruby
(1..10).step(2) { |n| puts n }
(1..10).step(2).to_a
(0..100).step(10).to_a
("a".."z").step(5).to_a

4.3 转换为数组 #

ruby
(1..5).to_a
(1..5).entries
(1..5).to_a
("a".."e").to_a

4.4 无限范围迭代 #

ruby
(1..).take(5)
(1..).first(5)
(1..).lazy.take(5).to_a
(1..).step(2).take(5).to_a

五、范围比较 #

5.1 比较运算 #

ruby
(1..5) == (1..5)
(1..5) == (1...6)
(1..5) === 3
(1..5) === 6
(1..5) === 3.5

5.2 重叠检查 #

ruby
def overlaps?(r1, r2)
  r1.cover?(r2.begin) || r2.cover?(r1.begin)
end

overlaps?((1..5), (3..7))
overlaps?((1..5), (6..10))

5.3 范围包含 #

ruby
def contains?(outer, inner)
  outer.begin <= inner.begin && outer.end >= inner.end
end

contains?((1..10), (3..7))
contains?((1..10), (5..15))

六、范围应用 #

6.1 case语句 #

ruby
def grade(score)
  case score
  when 90..100 then "A"
  when 80...90 then "B"
  when 70...80 then "C"
  when 60...70 then "D"
  else "F"
  end
end

grade(95)
grade(85)
grade(55)

6.2 日期范围 #

ruby
require 'date'

start_date = Date.new(2024, 1, 1)
end_date = Date.new(2024, 1, 7)

(start_date..end_date).each { |date| puts date }
(start_date..end_date).select(&:weekday?)
(start_date..end_date).count { |d| d.saturday? || d.sunday? }

6.3 时间范围 #

ruby
require 'time'

start_time = Time.now
end_time = start_time + 3600

(start_time..end_time).cover?(Time.now + 1800)
(start_time..end_time).cover?(Time.now + 7200)

6.4 字符范围 #

ruby
("a".."z").to_a
("A".."Z").to_a
("0".."9").to_a
("a".."z").include?("m")
("a".."z").cover?("aa")

6.5 数组切片 #

ruby
arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

arr[1..5]
arr[1...5]
arr[2..-1]
arr[2..]
arr[..5]
arr[...-2]

七、范围与枚举 #

7.1 枚举方法 #

ruby
(1..10).map { |n| n * 2 }
(1..10).select { |n| n.even? }
(1..10).reduce(:+)
(1..10).sum
(1..10).minmax
(1..10).any? { |n| n > 5 }
(1..10).all? { |n| n > 0 }

7.2 懒加载 #

ruby
(1..).lazy
      .select { |n| n.even? }
      .map { |n| n * 2 }
      .take(5)
      .to_a

(1..).lazy.take(3).force
(1..).lazy.select(&:even?).first(5)

7.3 无限序列 #

ruby
natural_numbers = (1..)
natural_numbers.take(10).to_a

fibonacci = Enumerator.new do |yielder|
  a, b = 0, 1
  loop do
    yielder << a
    a, b = b, a + b
  end
end

fibonacci.take(10).to_a

八、范围操作 #

8.1 范围合并 #

ruby
def merge_ranges(r1, r2)
  return nil unless overlaps?(r1, r2)

  Range.new([r1.begin, r2.begin].min, [r1.end, r2.end].max)
end

merge_ranges((1..5), (3..7))
merge_ranges((1..5), (6..10))

8.2 范围差集 #

ruby
def range_difference(r1, r2)
  return [r1] unless overlaps?(r1, r2)

  result = []
  result << (r1.begin...r2.begin) if r1.begin < r2.begin
  result << (r2.end + 1..r1.end) if r1.end > r2.end
  result
end

range_difference((1..10), (3..7))
range_difference((1..10), (1..5))

8.3 范围分割 #

ruby
def split_range(range, step)
  range.step(step).each_cons(2).map { |a, b| a...b }
end

split_range((1..10), 3)

九、自定义范围 #

9.1 可比较对象 #

ruby
class Point
  include Comparable

  attr_reader :x, :y

  def initialize(x, y)
    @x, @y = x, y
  end

  def <=>(other)
    [x, y] <=> [other.x, other.y]
  end

  def succ
    if y < 10
      Point.new(x, y + 1)
    else
      Point.new(x + 1, 0)
    end
  end
end

p1 = Point.new(0, 0)
p2 = Point.new(1, 1)

(p1..p2).to_a

9.2 时间范围扩展 #

ruby
require 'time'

class TimeRange < Range
  def duration
    (self.end - self.begin).to_i
  end

  def hours
    duration / 3600
  end

  def minutes
    duration / 60
  end

  def each_hour
    return enum_for(:each_hour) unless block_given?
    current = self.begin
    while current <= self.end
      yield current
      current += 3600
    end
  end
end

range = TimeRange.new(Time.now, Time.now + 7200)
range.duration
range.hours
range.each_hour.to_a

十、实用示例 #

10.1 分页 #

ruby
def paginate(total, page, per_page)
  start = (page - 1) * per_page
  finish = [start + per_page - 1, total - 1].min
  start..finish
end

paginate(100, 1, 10)
paginate(100, 2, 10)
paginate(100, 10, 10)
paginate(100, 11, 10)

10.2 时间槽 #

ruby
def time_slots(start_time, end_time, duration_minutes)
  duration = duration_minutes * 60
  (start_time.to_i..end_time.to_i).step(duration).map do |t|
    Time.at(t)
  end
end

time_slots(Time.now, Time.now + 7200, 30)

10.3 IP地址范围 #

ruby
def ip_range(start_ip, end_ip)
  start = start_ip.split(".").map(&:to_i)
  finish = end_ip.split(".").map(&:to_i)

  (start.join(".").to_i(256)..finish.join(".").to_i(256)).map do |n|
    [
      (n >> 24) & 255,
      (n >> 16) & 255,
      (n >> 8) & 255,
      n & 255
    ].join(".")
  end
end

ip_range("192.168.1.1", "192.168.1.5")

10.4 版本号比较 #

ruby
class Version
  include Comparable

  attr_reader :parts

  def initialize(version)
    @parts = version.split(".").map(&:to_i)
  end

  def <=>(other)
    parts <=> other.parts
  end

  def to_s
    parts.join(".")
  end
end

v1 = Version.new("1.0.0")
v2 = Version.new("2.0.0")

(v1..v2).cover?(Version.new("1.5.0"))

十一、总结 #

本章我们学习了:

  1. 创建范围:…、…、Range.new
  2. 范围方法:begin、end、include?、cover?
  3. 范围迭代:each、step、to_a
  4. 无限范围:(1…)、(…10)
  5. 范围应用:case语句、日期范围、数组切片
  6. 自定义范围:实现<=>和succ方法

接下来让我们学习Ruby的集合!

最后更新:2026-03-27