Idiomatic Ruby for Array#extract / Range#length?

On 9/20/07, Sammy L. [email protected] wrote:

  end

end

There’s been quite a lot of interesting discussion surrounding my first
question, which I truly appreciate and have enjoyed reading for the most
part.

  1. What about a 2 dimensional slice (you want a submatrix, for example)?

class Array
def extract_submatrix(
row_range, col_range)
self[row_range]
.transpose[col_range].transpose
end

end

But what about this 2-dimensional slice? Any thoughts on that? There
is
already Array#transpose that assumes a 2D array, so I think it might
fit.
Can you think of a better way to do it?

  1. Are the better/more idiomatic ways to do these?

On Sep 21, 2007, at 8:14 AM, Sebastian H. wrote:

Xavier N. wrote:

I guess the claim that well-defined infinite Ranges in Ruby are
implementable is now clear.

Sorry if this is a stupid question, but couldn’t you just have
proved that
claim by giving 1…(1.0/0) as an example? That’s an infinite range
right
there and you don’t have to define any custom classes. Or would you
say that doesn’t qualify as well-defined?

Exactly, thank you, let me write 1.0/0 as w to simplify the notation
in what follows.

That is the basic idea I had in mind when I mentioned an alternative
proof involving N and a few infinities. Note that technically you
still need to define a class, because you need 1, and w to belong to
the same class, and have #<=> and #succ defined for both (“have” in
the context of this thread).

It is true that w.succ is not needed to implement Range#length, but
the rules I am playing with require your objects to respond to a
couple of methods, that includes the endpoints. Thus, you need to
define w.succ and w.succ.succ in turn, and so on. The result is
another handful of “naturals” past w. We can write them like this
(note this is just intuitive notation, we have not defined addition
for w):

w, w + 1, w + 2, w + 3, …

You’d expect as well that “<” (which means <=> gives -1) satisfies

w < w.succ (:= w + 1) < w.succ.succ (:= w + 2) < …

Right, keep the ordinary ordering for the naturals. Let’s define all
naturals to be strictly less than w + n for all n, and define w + n
<=> w + m iff n <=> m. That <=> works.

So yes, that was the idea but needs a bit of extra work to follow the
docs.

I came with the ZSquared example (I prefer the second version) to
avoid more math-like stuff and still have both methods implemented in
the class, and with easy one-liners. You can visualize ZSquared in Z
x Z if you want, but people can understand ZSquared gives an infinite
Range just reasoning about arrays of integers otherwise.

Thus, even if Range required in practice that objects implemented
#succ, Range#length is not guaranteed to terminate in general written
as a loop over #succ.

Thank you Sebastian,

– fxn

I think this thread deserves a summary.

Sammry Larbi asks three questions in the post that opens this thread,
the first one asks whether it would make sense to define Range#length
as the difference between its endpoints.

The short answer is that objects in ranges do not necessarily respond
to #-.

Furthermore, according to the docs (Ruby docs and Pickaxe), objects
in ranges respond to #succ, and so one may wonder whether you can
define Range#length as an element count implemented as a loop from
left endpoint to right endpoint via #succ. There’s a warning, though,
because #<=> and #succ do not guarantee ranges are finite, so such a
method is not guaranteed to terminate. That’s a claim at that point,
and some people ask for further details.

There’s a split here in the thread (mixed in practice):

  1. The docs say so, but there’s enough evidence to say they are
    wrong, objects in ranges are only required to respond to #succ if you
    want to iterate over them. If you want to implement Range#length that
    way you have to assume objects respond to #succ anyway.

  2. A few examples of classes that give infinite ranges are described
    or implemented, the most simple of them being the ZSquared class
    copied below.

The other two questions of the original post remain unanswered by now
(at least in the mailing list).

Best,

– fxn

class ZSquared
include Comparable

 attr_reader :n, :m

 def initialize(n, m)
   @n, @m = n, m
 end

 def succ
   self.class.new(n+1, m)
 end

 def <=>(zs)
   [self.m, self.n] <=> [zs.m, zs.n]
 end

end

a = ZSquared.new(0, 0)
b = ZSquared.new(0, 1)

puts a < b # true

length = 0
(a … b).each do |c|
length += 1
puts length
end

be prepared to send Ctrl-C

On 9/22/07, Olivier R. [email protected] wrote:

  end

end

This code will work only when begin and end respond to #-. This is a valid
implementation for Numeric objects, but not for other classes.
In the general case, you’d have to generate every objects of the range to
count them (using #to_a and #size).

I think that it’s debatable even in the case of Numerics, for example:

(1.2…1.5).length #=> 0.3

Normally length returns the number of elements in a collection, and
the method as provided doesn’t actually work correctly for integer
ranges, it should be:

class Range
def length
1 + last - first
end
end

But, what about:

(1…3).length

end-start would give 2 but:

(1…3).to_a => [1, 2]

To fix this:

class Range
def length
last - first + (exclude_end? ? 0 : 1)
end
end

Now we have another case:

(3…1).to_a #=> []

so:

class Range
def length
[0, last - first + (exclude_end? ? 0 : 1)].max
end
end

But then what about:

(1.2…1.5).length

A collection can’t have 0.3 elements!

I think it only makes sense for Integer ranges.


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Le jeudi 20 septembre 2007 14:28, Sammy L. a écrit :

end
This code will work only when begin and end respond to #-. This is a
valid
implementation for Numeric objects, but not for other classes.
In the general case, you’d have to generate every objects of the range
to
count them (using #to_a and #size).
And as the previous posts were debating, some ranges are not able to
generate
the objects in the Range : the ranges of objects that do not respond to
#succ. And some ranges can be infinite.
By the way, generating all objects and counting is not restricted to
Range,
but is general for any other Enumerable.

self[row_range].transpose[col_range].transpose

end
end

Yes, this may be a useful method. It could be extended for use with
n-dimensional arrays.
Take a look to NArray library. I’m not sure, but I think it can do what
you
are looking for, in an optimized way.

  1. Are the better/more idiomatic ways to do these?

Your code is fine :slight_smile:

  1. Excuse my ignorance, as I’ve yet to use Facets, but are these the type
    of things it adds (and more)? Are they already in there?

Yes, the submatrix thing is the kind of method I could expect to see in
Facets. I’m not aware of the existence of such a method, though.