Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an elegant way to exclude the first value of a range?

Tags:

range

ruby

Let's say I have a range from 0 to 10:

range = 0...10

Three dots mean, that the last value (10) is excluded:

range.include? 10
=> false

Now, is there a similar and elegant way to exclude the first value?
For the above example, this would mean to include all values that are bigger (>, not >=) than 0 and smaller than 10.

like image 208
Daniel Pietzsch Avatar asked Jul 29 '10 01:07

Daniel Pietzsch


2 Answers

I have two suggestions for you, they're not very ideal but they're the best I can think of.

First you can define a new method on the Range class that does what you describe. It'd look like this:

class Range
  def have?(x)
    if x == self.begin
      false
    else
      include?(x)
    end
  end
end

p (0..10).have?(0)       #=> false
p (0..10).have?(0.00001) #=> true

I don't know, I just used a synonym of "include" as a method name, maybe you can think of something better. But that's the idea.

And then you could do something a little more elaborate, and define a method on the Range class that marks a range as one you want to exclude the beginning value of, and then change Range's include? method to check for that mark.

class Range
  def exclude_begin
    @exclude_begin = true
    self
  end

  alias_method :original_include?, :include?
  def include?(x)
    return false if x == self.begin && instance_variable_defined?(:@exclude_begin)
    original_include?(x)
  end

  alias_method :===, :include?
  alias_method :member?, :include?
end

p (0..10).include?(0)                     #=> true
p (0..10).include?(0.00001)               #=> true
p (0..10).exclude_begin.include?(0)       #=> false
p (0..10).exclude_begin.include?(0.00001) #=> true

Again, you may want a better (more elegant?) name for the method than exclude_begin, I just chose that since it's consistent with Range's exclude_end? method.

Edit: I've got another one for you, just because I find this problem so interesting. :P This'll only work in the very latest version of Ruby 1.9, but will allow the following syntax:

(0.exclude..10).include? 0       #=> false
(0.exclude..10).include? 0.00001 #=> true

It uses the same idea as my 2nd suggestion, but stores the "exclusion marker" in the number instead of the range. I have to use Ruby 1.9's SimpleDelegator to accomplish this (numbers on their own can't have instance variables or anything), which is why it won't work in earlier versions of Ruby.

require "delegate"

class Numeric
  def exclude
    o = SimpleDelegator.new(self)
    def o.exclude_this?() true end
    o
  end
end

class Range
  alias_method :original_include?, :include?
  def include?(x)
    return false if x == self.begin &&
                    self.begin.respond_to?(:exclude_this?) &&
                    self.begin.exclude_this?
    original_include?(x)
  end

  alias_method :===, :include?
  alias_method :member?, :include?
end
like image 117
Paige Ruten Avatar answered Nov 13 '22 03:11

Paige Ruten


No.

((0+1)..10)
like image 4
Adrian Avatar answered Nov 13 '22 03:11

Adrian