Skip to content

Ruby Ranges

Ranges are a unique and powerful feature in Ruby, used to represent a continuous sequence of values. Ranges can include numbers, letters, and even more complex objects. Ruby ranges are feature-rich, supporting various operations and iteration methods. This chapter will introduce in detail the creation, operations, and usage methods of ranges in Ruby.

🎯 Range Basics

Range Definition

Ruby provides two ways to create ranges:

ruby
# Range including end value (..)
inclusive_range = 1..5
puts inclusive_range  # 1..5
puts inclusive_range.include?(5)  # true

# Range excluding end value (...)
exclusive_range = 1...5
puts exclusive_range  # 1...5
puts exclusive_range.include?(5)  # false

# Character range
letter_range = 'a'..'e'
puts letter_range  # "a".."e"
puts letter_range.include?('e')  # true

# String range
word_range = 'aa'..'az'
puts word_range  # "aa".."az"

# Date range
require 'date'
date_range = Date.new(2023, 12, 25)..Date.new(2023, 12, 31)
puts date_range  # 2023-12-25..2023-12-31

Range Creation Methods

ruby
# Use literal syntax (recommended)
range1 = 1..10
range2 = 'a'..'z'

# Use Range.new method
range3 = Range.new(1, 10)
range4 = Range.new('a', 'z')
range5 = Range.new(1, 10, true)  # Third parameter true means exclude end value

puts range1 == range3  # true
puts range2 == range4  # true
puts range5  # 1...10

📏 Range Properties

Range Basic Properties

ruby
range = 1..10

# Get range start and end values
puts range.begin  # 1
puts range.first  # 1 (alias for begin)
puts range.end    # 10
puts range.last   # 10 (alias for end)

# Check if end value is excluded
puts range.exclude_end?  # false
puts (1...10).exclude_end?  # true

# Range length
puts range.size  # 10

# Check if range is empty
puts range.empty?  # false
puts (5..3).empty?  # true (invalid range)

# Convert to array
array = range.to_a
puts array.inspect  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Range Comparison

ruby
range1 = 1..5
range2 = 1..5
range3 = 2..6

# Equality comparison
puts range1 == range2  # true
puts range1 == range3  # false

# Range coverage
puts range1.cover?(3)   # true
puts range1.cover?(6)   # false
puts range1.include?(3) # true
puts range1.include?(6) # false

# Use === operator (used in case statements)
puts range1 === 3  # true
puts range1 === 6  # false

# Range coverage check
range_a = 1..10
range_b = 3..7
range_c = 8..15

puts range_a.cover?(range_b)  # true (range_a contains range_b)
puts range_a.cover?(range_c)  # false

🔍 Range Operations

Range Member Check

ruby
numbers = 1..100

# Check single value
puts numbers.include?(50)   # true
puts numbers.include?(150)  # false

# Use cover? method (more efficient)
puts numbers.cover?(50)     # true
puts numbers.cover?(150)    # false

# Check another range
small_range = 25..75
puts numbers.cover?(small_range)  # true

# Character range check
letters = 'a'..'z'
puts letters.include?('m')  # true
puts letters.include?('A')  # false

# String range check
words = 'aa'..'zz'
puts words.include?('ab')   # true
puts words.include?('ba')   # true
puts words.include?('aaa')  # false (out of range)

Range Boundary Operations

ruby
range = 10..20

# Get min and max values of range
puts range.min  # 10
puts range.max  # 20

# Get first n elements
puts range.first(3).inspect  # [10, 11, 12]

# Get last n elements
puts range.last(3).inspect   # [18, 19, 20]

# Get median value of range
def range_median(range)
  min, max = range.minmax
  (min + max) / 2.0
end

puts range_median(range)  # 15.0

# Range statistics
def range_stats(range)
  {
    min: range.min,
    max: range.max,
    size: range.size,
    average: (range.min + range.max) / 2.0
  }
end

stats = range_stats(1..100)
puts stats  # {:min=>1, :max=>100, :size=>100, :average=>50.5}

🔁 Range Iteration

Basic Iteration

ruby
# Numeric range iteration
(1..5).each { |n| print "#{n} " }
puts  # 1 2 3 4 5

# Character range iteration
('a'..'e').each { |char| print "#{char} " }
puts  # a b c d e

# Use each_with_index
('x'..'z').each_with_index do |char, index|
  puts "#{index}: #{char}"
end
# 0: x
# 1: y
# 2: z

# Reverse iteration
(1..5).reverse_each { |n| print "#{n} " }
puts  # 5 4 3 2 1

Advanced Iteration

ruby
# Use map for transformation
squared = (1..5).map { |n| n ** 2 }
puts squared.inspect  # [1, 4, 9, 16, 25]

# Use select for filtering
evens = (1..10).select(&:even?)
puts evens.inspect  # [2, 4, 6, 8, 10]

# Use reject for exclusion
odds = (1..10).reject(&:even?)
puts odds.inspect  # [1, 2, 3, 5, 7, 9]

# Use find for searching
first_large = (1..100).find { |n| n > 50 }
puts first_large  # 51

# Use take and drop
range = 1..20
puts range.take(5).inspect   # [1, 2, 3, 4, 5]
puts range.drop(15).inspect  # [16, 17, 18, 19, 20]

Chunk Iteration

ruby
# Use each_slice for chunked processing
(1..20).each_slice(5) do |slice|
  puts slice.inspect
end
# [1, 2, 3, 4, 5]
# [6, 7, 8, 9, 10]
# [11, 12, 13, 14, 15]
# [16, 17, 18, 19, 20]

# Use each_cons for consecutive processing
(1..10).each_cons(3) do |cons|
  puts cons.inspect
end
# [1, 2, 3]
# [2, 3, 4]
# [3, 4, 5]
# [4, 5, 6]
# [5, 6, 7]
# [6, 7, 8]
# [7, 8, 9]
# [8, 9, 10]

# Use cycle for infinite looping
# (1..3).cycle(2) { |n| print "#{n} " }
# puts  # 1 2 3 1 2 3

🎯 Range Practice Examples

Number Range Applications

ruby
class NumberRangeUtils
  # Check if number is within valid range
  def self.valid_range?(number, min, max)
    (min..max).cover?(number)
  end
  
  # Generate random number within range
  def self.random_in_range(range)
    range.to_a.sample
  end
  
  # Calculate primes within range
  def self.primes_in_range(range)
    range.select { |n| prime?(n) }
  end
  
  # Sum of numbers in range
  def self.sum_range(range)
    # For large ranges, mathematical formula is more efficient
    if range.exclude_end?
      n = range.last - range.first
    else
      n = range.last - range.first + 1
    end
    n * (range.first + range.last) / 2
  end
  
  private
  
  def self.prime?(n)
    return false if n < 2
    return true if n == 2
    return false if n.even?
    
    (3..Math.sqrt(n)).step(2) do |i|
      return false if n % i == 0
    end
    true
  end
end

# Use number range tools
puts NumberRangeUtils.valid_range?(15, 10, 20)  # true
puts NumberRangeUtils.valid_range?(25, 10, 20)  # false

random_number = NumberRangeUtils.random_in_range(1..100)
puts "Random number: #{random_number}"

primes = NumberRangeUtils.primes_in_range(1..30)
puts "Primes from 1 to 30: #{primes.inspect}"  # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

sum = NumberRangeUtils.sum_range(1..100)
puts "Sum from 1 to 100: #{sum}"  # 5050

Character Range Applications

ruby
class CharacterRangeUtils
  # Generate alphabet
  def self.alphabet
    ('a'..'z').to_a
  end
  
  # Generate uppercase alphabet
  def self.uppercase_alphabet
    ('A'..'Z').to_a
  end
  
  # Check if character is a letter
  def self.letter?(char)
    ('a'..'z').cover?(char.downcase)
  end
  
  # Check if character is a digit
  def self.digit?(char)
    ('0'..'9').cover?(char)
  end
  
  # Generate random letter
  def self.random_letter
    ('a'..'z').to_a.sample
  end
  
  # Generate random string within range
  def self.random_string(length, char_range = 'a'..'z')
    Array.new(length) { char_range.to_a.sample }.join
  end
  
  # String range check
  def self.in_range?(string, start_str, end_str)
    (start_str..end_str).cover?(string)
  end
end

# Use character range tools
puts "Alphabet: #{CharacterRangeUtils.alphabet.take(5).inspect}"  # ["a", "b", "c", "d", "e"]
puts "Uppercase: #{CharacterRangeUtils.uppercase_alphabet.take(5).inspect}"  # ["A", "B", "C", "D", "E"]

puts CharacterRangeUtils.letter?('A')  # true
puts CharacterRangeUtils.digit?('5')   # true

random_letter = CharacterRangeUtils.random_letter
puts "Random letter: #{random_letter}"

random_string = CharacterRangeUtils.random_string(8)
puts "Random string: #{random_string}"

puts CharacterRangeUtils.in_range?("apple", "a", "z")  # true

Date Range Applications

ruby
require 'date'

class DateRangeUtils
  # Create date range
  def self.date_range(start_date, end_date)
    Date.parse(start_date)..Date.parse(end_date)
  end
  
  # Calculate days in date range
  def self.days_in_range(date_range)
    (date_range.end - date_range.begin).to_i + 1
  end
  
  # Get business days count
  def self.business_days(date_range)
    count = 0
    date_range.each { |date| count += 1 unless date.saturday? || date.sunday? }
    count
  end
  
  # Get weekend days count
  def self.weekend_days(date_range)
    count = 0
    date_range.each { |date| count += 1 if date.saturday? || date.sunday? }
    count
  end
  
  # Group by week
  def self.group_by_week(date_range)
    weeks = Hash.new { |h, k| h[k] = [] }
    date_range.each do |date|
      week_number = date.cweek
      weeks[week_number] << date
    end
    weeks
  end
  
  # Get dates for specific weekday
  def self.days_of_week(date_range, weekday)
    # 0 = Sunday, 1 = Monday, ..., 6 = Saturday
    date_range.select { |date| date.wday == weekday }
  end
end

# Use date range tools
date_range = DateRangeUtils.date_range("2023-12-01", "2023-12-31")
puts "Days in December: #{DateRangeUtils.days_in_range(date_range)}"  # 31

business_days = DateRangeUtils.business_days(date_range)
puts "Business days: #{business_days}"

weekend_days = DateRangeUtils.weekend_days(date_range)
puts "Weekend days: #{weekend_days}"

mondays = DateRangeUtils.days_of_week(date_range, 1)
puts "Mondays in December: #{mondays.inspect}"

🔧 Range with Other Data Structures

Range and Array

ruby
# Range to array
numbers = (1..10).to_a
puts numbers.inspect  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Array to range (if possible)
def array_to_range(arr)
  return nil if arr.empty?
  min, max = arr.minmax
  (min..max) if (min..max).to_a == arr.sort
end

consecutive = [1, 2, 3, 4, 5]
puts array_to_range(consecutive)  # 1..5

non_consecutive = [1, 3, 5, 7]
puts array_to_range(non_consecutive)  # nil

# Range and array operations
range = 1..10
array = [5, 15, 25, 35]

# Check if array elements are within range
in_range = array.select { |n| range.cover?(n) }
puts in_range.inspect  # [5]

# Find array elements outside range
out_of_range = array.reject { |n| range.cover?(n) }
puts out_of_range.inspect  # [15, 25, 35]

Range and Hash

ruby
# Use range as hash keys
grade_ranges = {
  90..100 => "A",
  80...90 => "B",
  70...80 => "C",
  60...70 => "D",
  0...60 => "F"
}

def get_grade(score, grade_ranges)
  grade_ranges.each do |range, grade|
    return grade if range.cover?(score)
  end
  "Invalid"
end

puts get_grade(95, grade_ranges)  # A
puts get_grade(85, grade_ranges)  # B
puts get_grade(75, grade_ranges)  # C
puts get_grade(65, grade_ranges)  # D
puts get_grade(55, grade_ranges)  # F

# Group data by range
scores = [95, 87, 76, 92, 88, 73, 91, 85, 79, 94]
score_groups = scores.group_by do |score|
  case score
  when 90..100 then "Excellent"
  when 80...90 then "Good"
  when 70...80 then "Average"
  when 60...70 then "Pass"
  else "Fail"
  end
end

puts score_groups
# {"Excellent"=>[95, 92, 91, 94], "Good"=>[87, 88, 85], "Average"=>[76, 73, 79]}

📊 Range Performance Optimization

Large Range Processing

ruby
# For large ranges, avoid converting to array
# Inefficient way
# sum = (1..1_000_000).to_a.sum

# Efficient way
def sum_range_efficiently(range)
  first, last = range.first, range.last
  count = last - first + 1
  count * (first + last) / 2
end

large_sum = sum_range_efficiently(1..1_000_000)
puts "Large range sum: #{large_sum}"  # 500000500000

# Use lazy for deferred computation
def process_large_range(range)
  range.lazy
    .select(&:even?)
    .map { |n| n ** 2 }
    .first(10)
    .to_a
end

result = process_large_range(1..1_000_000)
puts result.inspect  # [4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

Range Search Optimization

ruby
# Use bsearch for binary search (requires sorted range)
sorted_array = (1..1000).to_a

# Find first element >= 500
index = sorted_array.bsearch_index { |x| x >= 500 }
puts "Index: #{index}, Value: #{sorted_array[index]}"  # Index: 499, Value: 500

# Find last element <= 500
index = sorted_array.bsearch_index { |x| x > 500 }&.pred || (sorted_array.length - 1)
puts "Index: #{index}, Value: #{sorted_array[index]}"  # Index: 499, Value: 500

# Efficient search within range
class RangeSearch
  def self.binary_search(range, target)
    return false unless range.cover?(target)
    
    low, high = range.first, range.last
    
    while low <= high
      mid = (low + high) / 2
      return true if mid == target
      if mid < target
        low = mid + 1
      else
        high = mid - 1
      end
    end
    
    false
  end
end

puts RangeSearch.binary_search(1..1000, 500)  # true
puts RangeSearch.binary_search(1..1000, 1500) # false

🎯 Range Best Practices

1. Choose Appropriate Range Type

ruby
# Range including end value (for most cases)
inclusive_range = 1..10
puts inclusive_range.to_a  # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Range excluding end value (for array indices, etc.)
exclusive_range = 0...10
puts exclusive_range.to_a  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# Use in loops
array = ['a', 'b', 'c', 'd', 'e']
(0...array.length).each { |i| puts "#{i}: #{array[i]}" }

# Use in case statements
def grade_letter(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

puts grade_letter(95)  # A
puts grade_letter(85)  # B

2. Safely Handle Ranges

ruby
class SafeRangeHandler
  # Safely create range
  def self.safe_range(start_val, end_val, exclude_end = false)
    return nil if start_val.nil? || end_val.nil?
    return nil if start_val > end_val
    
    exclude_end ? (start_val...end_val) : (start_val..end_val)
  end
  
  # Safely iterate range
  def self.safe_each(range, &block)
    return [] unless range.is_a?(Range)
    return [] if range.empty?
    
    range.each(&block) if block_given?
    range.to_a
  end
  
  # Validate range bounds
  def self.validate_range(range, min_bound, max_bound)
    return false unless range.is_a?(Range)
    
    range.begin >= min_bound && range.end <= max_bound
  end
end

# Use safe range handling
safe_range = SafeRangeHandler.safe_range(1, 10)
puts safe_range  # 1..10

invalid_range = SafeRangeHandler.safe_range(10, 1)
puts invalid_range  # nil

SafeRangeHandler.safe_each(1..5) { |n| puts "Number: #{n}" }

valid = SafeRangeHandler.validate_range(1..10, 0, 100)
puts "Range valid: #{valid}"  # Range valid: true

3. Range Performance Optimization

ruby
class RangePerformance
  # Efficient range sum
  def self.sum_range(range)
    # Use mathematical formula instead of iteration
    n = range.size
    first, last = range.first, range.last
    n * (first + last) / 2
  end
  
  # Efficient range average
  def self.average_range(range)
    (range.first + range.last) / 2.0
  end
  
  # Efficient range median
  def self.median_range(range)
    min, max = range.minmax
    (min + max) / 2.0
  end
  
  # Fast contains check (use cover? instead of include?)
  def self.fast_contains?(range, value)
    range.cover?(value)
  end
end

# Use performance-optimized range operations
large_range = 1..1_000_000
puts "Sum: #{RangePerformance.sum_range(large_range)}"  # 500000500000
puts "Average: #{RangePerformance.average_range(large_range)}"  # 500000.5
puts "Median: #{RangePerformance.median_range(large_range)}"  # 500000.5

puts RangePerformance.fast_contains?(1..100, 50)  # true

📚 Next Steps

After mastering Ruby range operations, it is recommended to continue learning:

Continue your Ruby learning journey!

Content is for learning and research only.