views:

96

answers:

3

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.

+2  A: 

No.

((0+1)..10)
Adrian
The only problem with this would be, that values between 0 and 1 would not be included. Yes, you can say `0.00000001..10` (or something similar, depending on how many decimals you need). But that's rather ugly, IMO, when the thing I want to say is: `0 < range < 10`.
Daniel Pietzsch
A: 

Maybe you could create your own range type.

class FancyRange
  def initialize(minimum, maximum, exclusive_minimum, exclusive_maximum)
    # insert code here
  end

  # insert more code here

end
Andrew Grimm
+2  A: 

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
yjerem
Wow. That's a great answer. Thanks a lot! I am still busy trying to understand your Ruby 1.9 solution. It's great to see what's possible. However, I think the most elegant and most obvious solution IMO would be to describe a range with excluding start and end values by four dots: `0....10`. I am really not sure if this is even possible without messing around with the range.c file!?
Daniel Pietzsch
Yeah, I agree that would be a nice syntax, but it would require a change to the Ruby grammar (it causes a syntax error if you type it in). One thing you can do close to that in Ruby 1.9 is use my last example there, but name the method `call` instead of `exclude`. Then you can use Ruby's new syntax sugar and write `0.()..10` and get the same thing. `0.()` is a shortcut for the `call` method. I don't know if that's better or not, though. :P
yjerem
Yeah, nice. But this would mean total confusion, I guess. ;-) I'd rather stick to your `(0..10).exclude_begin` or `0.exclude..10` solutions. Thanks for your answer again. I learned a lot. (I accepted another answer, because I wouldn't describe this as an elegant solution similar to the three dot notation to exclude the last value).
Daniel Pietzsch