Forum: Ruby Improving min/max for Range

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
madevilgenius (Guest)
on 2005-11-25 02:35
(Received via mailing list)
Range relies entirely on Enumerable to implement #min and #max, which
in turn implements these operations using #to_a. This becomes a
problem with ranges of floats, for example:

>> (0.0..100.0).min
TypeError: can't iterate from Float
        from (irb):9:in `each'
        from (irb):9
        from :0

Not to mention that it becomes inefficient to expand the whole range
into array, such as for very large integers:

>> (0.0..2**128).min

(Tends to run out of memory.)

I think it makes more sense to implement these operations in terms of
<=>, like so:

class Range

  def min
    (self.first <=> self.last) <= 0 ? self.first : nil
  end

  def max
    (self.first <=> self.last) <= 0 ? self.last : nil
  end

end

I don't see any reason to rely on expanding the range using
Enumerable#to_a, since the operations can be implemented purely in
terms of the range boundaries.

I would be happy to submit a patch.

Alexander.
matz (Guest)
on 2005-11-25 03:07
(Received via mailing list)
Hi,

In message "Re: Improving min/max for Range"
    on Fri, 25 Nov 2005 09:30:39 +0900, Alexander
<removed_email_address@domain.invalid> writes:

|I think it makes more sense to implement these operations in terms of
|<=>, like so:

#min makes sense.  I'd like to merge your idea.  #max needs to
consider about end exclusion.  I'm not sure what (0...1.0).max should
return.  Error?

							matz.
madevilgenius (Guest)
on 2005-11-25 03:55
(Received via mailing list)
On 11/25/05, Yukihiro M. <removed_email_address@domain.invalid> wrote:
> return.  Error?
>
>                                                         matz.

Good point. I think max should throw if the number doesn't support the
integer protocol; there's simply no way to handle it intelligently.
For int-type types, I suggest that (end.to_int - 1) should be used,
since there is no #pred counterpart to #succ.

Alexander.
matz (Guest)
on 2005-11-25 04:19
(Received via mailing list)
Hi,

In message "Re: Improving min/max for Range"
    on Fri, 25 Nov 2005 10:53:35 +0900, Alexander
<removed_email_address@domain.invalid> writes:

|> #min makes sense.  I'd like to merge your idea.  #max needs to
|> consider about end exclusion.  I'm not sure what (0...1.0).max should
|> return.  Error?

|Good point. I think max should throw if the number doesn't support the
|integer protocol; there's simply no way to handle it intelligently.
|For int-type types, I suggest that (end.to_int - 1) should be used,
|since there is no #pred counterpart to #succ.

I've just committed my implementation of Range#min and #max to CVS
HEAD.  Check if you have interest.

							matz.
madevilgenius (Guest)
on 2005-11-25 04:52
(Received via mailing list)
On 11/25/05, Yukihiro M. <removed_email_address@domain.invalid> wrote:
> |Good point. I think max should throw if the number doesn't support the
> |integer protocol; there's simply no way to handle it intelligently.
> |For int-type types, I suggest that (end.to_int - 1) should be used,
> |since there is no #pred counterpart to #succ.
>
> I've just committed my implementation of Range#min and #max to CVS
> HEAD.  Check if you have interest.
>
>                                                         matz.

I see you decided to return nil instead of throwing; acceptable, I
suppose. More sneaky is how you let Enumerable handle the case when
the end is excluded -- I would have preferred to do that only do if
#end was not an integer.

Here's a unit test patch, btw:

diff -u -r1.5 test_range.rb
--- test/ruby/test_range.rb     5 Aug 2005 23:56:02 -0000       1.5
+++ test/ruby/test_range.rb     25 Nov 2005 02:47:06 -0000
@@ -31,4 +31,24 @@
   def test_duckrange
     assert_equal("bc", "abcd"[DuckRange.new(1,2)])
   end
+
+  def test_min
+    assert_equal(1, (1..2).min)
+    assert_equal(nil, (2..1).min)
+    assert_equal(1, (1...2).min)
+
+    assert_equal(1.0, (1.0..2.0).min)
+    assert_equal(nil, (2.0..1.0).min)
+    assert_equal(1, (1.0...2.0).min)
+  end
+
+  def test_max
+    assert_equal(2, (1..2).max)
+    assert_equal(nil, (2..1).max)
+    assert_equal(1, (1...2).max)
+
+    assert_equal(2.0, (1.0..2.0).max)
+    assert_equal(nil, (2.0..1.0).max)
+    assert_raise(TypeError) { (1.0...2.0).max }
+  end
 end

Alexander.
matz (Guest)
on 2005-11-25 07:45
(Received via mailing list)
Hi,

In message "Re: Improving min/max for Range"
    on Fri, 25 Nov 2005 11:51:30 +0900, Alexander
<removed_email_address@domain.invalid> writes:

|I see you decided to return nil instead of throwing; acceptable, I
|suppose. More sneaky is how you let Enumerable handle the case when
|the end is excluded -- I would have preferred to do that only do if
|#end was not an integer.

Sounds reasonable.  Can you check my new modify?

|Here's a unit test patch, btw:

Thank you.  It's merged.

							matz.
madevilgenius (Guest)
on 2005-11-25 22:05
(Received via mailing list)
On 11/25/05, Yukihiro M. <removed_email_address@domain.invalid> wrote:
> Sounds reasonable.  Can you check my new modify?
Great, that works. Thanks.

Alexander.
This topic is locked and can not be replied to.