RCR: More enumerator functionality

  • Thread starter Kristof Bastiaensen
  • Start date
K

Kristof Bastiaensen

Hi,
I would like to know your opinion about the following idea's.
(It is actually are two RCR's)

(1)* Enumerable::filter and Enumerable::enum_filter

filter([[method = :each], *args, ] block)
yield values for which block returns true

3.filter:)times, &lambda{ |i| i[0] == 0 }) do |n|
puts "%i is even" % n
end
=> 2 is even

composers = %W(Brahms Berlioz Chopin)
composers.filter(&lambda { |i| i[0] == ?B }) do |c|
puts "composer with B: #{c}"
end
=> composer with B: Brahms
=> composer with B: Berlioz

and now the fun stuff (turning into an enum):

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]

(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

Thanks,
Kristof
 
N

nobu.nokada

Hi,

At Mon, 17 May 2004 20:34:05 +0900,
Kristof Bastiaensen wrote in [ruby-talk:100500]:
filter([[method = :each], *args, ] block)
yield values for which block returns true

3.filter:)times, &lambda{ |i| i[0] == 0 }) do |n|
puts "%i is even" % n
end
=> 2 is even

$ ruby -c
3.filter:)times, &lambda{ |i| i[0] == 0 }) do |n|
puts "%i is even" % n
end
-:3: both block arg and actual block given
and now the fun stuff (turning into an enum):

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]

Is it a method of Enumerator, although Integer doesn't include
it?
(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

Seems considerble, IMHO.
 
K

Kristof Bastiaensen

and now the fun stuff (turning into an enum):

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]

Is it a method of Enumerator, although Integer doesn't include
it?

Sorry, I forgot to mention it.
It would be a method of Object (like enum_for).

Regards,
Kristof
 
G

gabriele renzi

il Mon, 17 May 2004 13:30:49 +0200, Kristof Bastiaensen
Hi,
I would like to know your opinion about the following idea's.
(It is actually are two RCR's)

(1)* Enumerable::filter and Enumerable::enum_filter

filter([[method = :each], *args, ] block)
yield values for which block returns true

3.filter:)times, &lambda{ |i| i[0] == 0 }) do |n|
puts "%i is even" % n
end
=> 2 is even

what's wrong with
(1..3).select {|x| x[0]==0}.each {|x| p x}
?


(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

interesting, you should post this on rcrarchive (or maybe more than
one)
 
R

Robert Klemme

gabriele renzi said:
il Mon, 17 May 2004 13:30:49 +0200, Kristof Bastiaensen
Hi,
I would like to know your opinion about the following idea's.
(It is actually are two RCR's)

(1)* Enumerable::filter and Enumerable::enum_filter

filter([[method = :each], *args, ] block)
yield values for which block returns true

3.filter:)times, &lambda{ |i| i[0] == 0 }) do |n|
puts "%i is even" % n
end
=> 2 is even

what's wrong with
(1..3).select {|x| x[0]==0}.each {|x| p x}
?

If the Enumerable contains a huge number of elements then this version
will burn a lot of memory while in certain cases (e.g. printing) it is
more efficient to be able to handle one element at a time only.

Well, of course in those cases you could simply do

(1..3).each {|x| p x if x[0]==0}

:)

Regards

robert
 
K

Kristof Bastiaensen

Hi,

what's wrong with
(1..3).select {|x| x[0]==0}.each {|x| p x}
?

If the Enumerable contains a huge number of elements then this version
will burn a lot of memory while in certain cases (e.g. printing) it is
more efficient to be able to handle one element at a time only.

Well, of course in those cases you could simply do

(1..3).each {|x| p x if x[0]==0}

:)

That's indeed more elegant than with filter and lambdas.
The filter method would be only useful for implementing
enum_filter, maybe I will exclude it from the RCR.

Enum_filter would also behave like select, but it would
be more efficient for memory. And you can keep the
enumerable around it you need it later (even when the
original object has changed it's contents).
Regards

robert

Thanks,
Kristof
 
D

David A. Black

Hi --

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]
[...]

(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

Keep in mind rejected RCR #50
(http://www.rcrchive.net/rgarchive/rejected.html#rcr50). It's perhaps
not identical to what you're proposing, but very similar. (It's
proposed as a modification of #map.)

(Aside: collect without a block returns an array with the same
elements as the receiver, so there's probably no point calling it.)


David
 
R

Robert Klemme

David A. Black said:
Hi --

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]
[...]

(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

Keep in mind rejected RCR #50
(http://www.rcrchive.net/rgarchive/rejected.html#rcr50). It's perhaps
not identical to what you're proposing, but very similar. (It's
proposed as a modification of #map.)

(Aside: collect without a block returns an array with the same
elements as the receiver, so there's probably no point calling it.)

Probably #to_a would be clearer here. I guess the aim was to print an array
from the Enumerator.

robert
 
D

David A. Black

Hi --

David A. Black said:
Hi --

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]
[...]

(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

Keep in mind rejected RCR #50
(http://www.rcrchive.net/rgarchive/rejected.html#rcr50). It's perhaps
not identical to what you're proposing, but very similar. (It's
proposed as a modification of #map.)

(Aside: collect without a block returns an array with the same
elements as the receiver, so there's probably no point calling it.)

Probably #to_a would be clearer here. I guess the aim was to print an array
from the Enumerator.

Sorry; I was misreading the whole post, and therefore
misunderstanding.


David
 
N

nobu.nokada

Hi,

At Mon, 17 May 2004 20:34:05 +0900,
Kristof Bastiaensen wrote in [ruby-talk:100500]:
5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]
=> [0, 2, 4]

Isn't it?
(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

I tried the implementation of these two experimentally. But I
feel enum_filter is overdoing after all.

This patch provides block for Enumerable#enum_for and
Enumerable#enum_if instead.

$ ./ruby -renumerator -e 'p 5.enum_for:)times){|i| i*i}.to_a'
[0, 1, 4, 9, 16]
$ ./ruby -renumerator -e 'p 5.enum_for:)times).enum_if{|i| i[0] == 0}.to_a'
[0, 2, 4]

Just my 2 yens.


Index: eval.c
===================================================================
RCS file: /cvs/ruby/src/ruby/eval.c,v
retrieving revision 1.663
diff -U2 -p -d -r1.663 eval.c
--- eval.c 14 May 2004 16:45:21 -0000 1.663
+++ eval.c 18 May 2004 01:02:30 -0000
@@ -141,5 +141,5 @@ static VALUE rb_f_block_given_p _((void)
static VALUE block_pass _((VALUE,NODE*));
static VALUE rb_cMethod;
-static VALUE method_call _((int, VALUE*, VALUE));
+VALUE rb_method_call _((int, VALUE*, VALUE));
static VALUE rb_cUnboundMethod;
static VALUE umethod_bind _((VALUE, VALUE));
@@ -8116,6 +8116,6 @@ proc_invoke(proc, args, self, klass)
*/

-static VALUE
-proc_call(proc, args)
+VALUE
+rb_proc_call(proc, args)
VALUE proc, args; /* OK */
{
@@ -8606,5 +8606,5 @@ method_unbind(obj)
*/

-static VALUE
+VALUE
rb_obj_method(obj, vid)
VALUE obj;
@@ -8686,6 +8686,6 @@ method_clone(self)
*/

-static VALUE
-method_call(argc, argv, method)
+VALUE
+rb_method_call(argc, argv, method)
int argc;
VALUE *argv;
@@ -8981,5 +8981,5 @@ bmcall(args, method)

a = svalue_to_avalue(args);
- return method_call(RARRAY(a)->len, RARRAY(a)->ptr, method);
+ return rb_method_call(RARRAY(a)->len, RARRAY(a)->ptr, method);
}

@@ -9179,7 +9179,7 @@ Init_Proc()

rb_define_method(rb_cProc, "clone", proc_clone, 0);
- rb_define_method(rb_cProc, "call", proc_call, -2);
+ rb_define_method(rb_cProc, "call", rb_proc_call, -2);
rb_define_method(rb_cProc, "arity", proc_arity, 0);
- rb_define_method(rb_cProc, "[]", proc_call, -2);
+ rb_define_method(rb_cProc, "[]", rb_proc_call, -2);
rb_define_method(rb_cProc, "==", proc_eq, 1);
rb_define_method(rb_cProc, "eql?", proc_eq, 1);
@@ -9199,6 +9199,6 @@ Init_Proc()
rb_define_method(rb_cMethod, "hash", method_hash, 0);
rb_define_method(rb_cMethod, "clone", method_clone, 0);
- rb_define_method(rb_cMethod, "call", method_call, -1);
- rb_define_method(rb_cMethod, "[]", method_call, -1);
+ rb_define_method(rb_cMethod, "call", rb_method_call, -1);
+ rb_define_method(rb_cMethod, "[]", rb_method_call, -1);
rb_define_method(rb_cMethod, "arity", method_arity, 0);
rb_define_method(rb_cMethod, "inspect", method_inspect, 0);
Index: ext/enumerator/enumerator.c
===================================================================
RCS file: /cvs/ruby/src/ruby/ext/enumerator/enumerator.c,v
retrieving revision 1.3
diff -U2 -p -d -r1.3 enumerator.c
--- ext/enumerator/enumerator.c 17 Oct 2003 14:09:43 -0000 1.3
+++ ext/enumerator/enumerator.c 19 May 2004 02:40:29 -0000
@@ -14,9 +14,70 @@

#include "ruby.h"
-#include "node.h"

static VALUE rb_cEnumerator;
static ID sym_each, sym_each_with_index, sym_each_slice, sym_each_cons;
-static ID id_new, id_enum_obj, id_enum_method, id_enum_args;
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+static ID id_call;
+#endif
+
+static VALUE
+proc_call(proc, args)
+ VALUE proc, args;
+{
+#ifdef HAVE_RB_PROC_CALL
+ if (TYPE(args) != T_ARRAY) {
+ args = rb_values_new(1, args);
+ }
+ return rb_proc_call(proc, args);
+#else
+ return rb_funcall2(proc, id_call, 1, &args);
+#endif
+}
+
+static VALUE
+method_call(method, args)
+ VALUE method, args;
+{
+#ifdef HAVE_RB_METHOD_CALL
+ return rb_method_call(RARRAY(args)->len, RARRAY(args)->ptr, method);
+#else
+ return rb_funcall2(method, id_call, RARRAY(args)->len, RARRAY(args)->ptr);
+#endif
+}
+
+struct enumerator {
+ VALUE method;
+ VALUE proc;
+ VALUE args;
+ VALUE (*iter)_((VALUE, struct enumerator *));
+};
+
+static void
+enumerator_mark(ptr)
+ struct enumerator *ptr;
+{
+ rb_gc_mark(ptr->method);
+ rb_gc_mark(ptr->proc);
+ rb_gc_mark(ptr->args);
+}
+
+static struct enumerator *
+enumerator_ptr(obj)
+ VALUE obj;
+{
+ struct enumerator *ptr = DATA_PTR(obj);
+ if (!ptr) {
+ rb_raise(rb_eArgError, "uninitialized enumerator");
+ }
+ return ptr;
+}
+
+static VALUE
+enumerator_iter_i(i, e)
+ VALUE i;
+ struct enumerator *e;
+{
+ return rb_yield(proc_call(e->proc, i));
+}

static VALUE
@@ -26,5 +87,7 @@ obj_to_enum(obj, enum_args)
rb_ary_unshift(enum_args, obj);

- return rb_apply(rb_cEnumerator, id_new, enum_args);
+ return rb_class_new_instance(RARRAY(enum_args)->len,
+ RARRAY(enum_args)->ptr,
+ rb_cEnumerator);
}

@@ -33,14 +96,17 @@ enumerator_enum_with_index(obj)
VALUE obj;
{
- return rb_funcall(rb_cEnumerator, id_new, 2, obj, sym_each_with_index);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_with_index;
+ return rb_class_new_instance(2, args, rb_cEnumerator);
}

static VALUE
-each_slice_i(val, memo)
+each_slice_i(val, args)
VALUE val;
- NODE *memo;
+ VALUE *args;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = args[0];
+ long size = (long)args[1];

rb_ary_push(ary, val);
@@ -48,5 +114,5 @@ each_slice_i(val, memo)
if (RARRAY(ary)->len == size) {
rb_yield(ary);
- memo->u1.value = rb_ary_new2(size);
+ args[0] = rb_ary_new2(size);
}

@@ -59,17 +125,16 @@ enum_each_slice(obj, n)
{
long size = NUM2LONG(n);
- NODE *memo;
- VALUE ary;
+ VALUE args[2], ary;

if (size <= 0) rb_raise(rb_eArgError, "invalid slice size");

- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_slice_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_slice_i, (VALUE)args);

- ary = memo->u1.value;
+ ary = args[0];
if (RARRAY(ary)->len > 0) rb_yield(ary);

- rb_gc_force_recycle((VALUE)memo);
return Qnil;
}
@@ -79,14 +144,18 @@ enumerator_enum_slice(obj, n)
VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_slice, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_slice;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
}

static VALUE
-each_cons_i(val, memo)
+each_cons_i(val, args)
VALUE val;
- NODE *memo;
+ VALUE *args;
{
- VALUE ary = memo->u1.value;
- long size = memo->u3.cnt;
+ VALUE ary = args[0];
+ long size = (long)args[1];
long len = RARRAY(ary)->len;

@@ -107,13 +176,12 @@ enum_each_cons(obj, n)
{
long size = NUM2LONG(n);
- NODE *memo;
- VALUE ary;
+ VALUE args[3];

if (size <= 0) rb_raise(rb_eArgError, "invalid size");
- memo = rb_node_newnode(NODE_MEMO, rb_ary_new2(size), 0, size);
+ args[0] = rb_ary_new2(size);
+ args[1] = (VALUE)size;

- rb_iterate(rb_each, obj, each_cons_i, (VALUE)memo);
+ rb_iterate(rb_each, obj, each_cons_i, (VALUE)args);

- rb_gc_force_recycle((VALUE)memo);
return Qnil;
}
@@ -123,5 +191,41 @@ enumerator_enum_cons(obj, n)
VALUE obj, n;
{
- return rb_funcall(rb_cEnumerator, id_new, 3, obj, sym_each_cons, n);
+ VALUE args[2];
+ args[0] = obj;
+ args[1] = sym_each_cons;
+ args[2] = n;
+ return rb_class_new_instance(3, args, rb_cEnumerator);
+}
+
+static VALUE
+enumerator_select(i, e)
+ VALUE i;
+ struct enumerator *e;
+{
+ if (!RTEST(proc_call(e->proc, i))) return Qnil;
+ return rb_yield(i);
+}
+
+static VALUE
+enumerator_enum_if(obj, enum_args)
+ VALUE obj, enum_args;
+{
+ rb_ary_unshift(enum_args, obj);
+
+ obj = rb_class_new_instance(RARRAY(enum_args)->len,
+ RARRAY(enum_args)->ptr,
+ rb_cEnumerator);
+ ((struct enumerator *)DATA_PTR(obj))->iter = enumerator_select;
+ return obj;
+}
+
+static VALUE enumerator_allocate _((VALUE));
+static VALUE
+enumerator_allocate(klass)
+ VALUE klass;
+{
+ struct enumerator *ptr;
+ return Data_Make_Struct(rb_cEnumerator, struct enumerator,
+ enumerator_mark, -1, ptr);
}

@@ -133,4 +237,5 @@ enumerator_initialize(argc, argv, obj)
{
VALUE enum_obj, enum_method, enum_args;
+ struct enumerator *ptr = enumerator_ptr(obj);

rb_scan_args(argc, argv, "11*", &enum_obj, &enum_method, &enum_args);
@@ -139,16 +244,25 @@ enumerator_initialize(argc, argv, obj)
enum_method = sym_each;

- rb_ivar_set(obj, id_enum_obj, enum_obj);
- rb_ivar_set(obj, id_enum_method, enum_method);
- rb_ivar_set(obj, id_enum_args, enum_args);
+ ptr->method = rb_obj_method(enum_obj, enum_method);
+ if (rb_block_given_p()) {
+ ptr->proc = rb_block_proc();
+ ptr->iter = enumerator_iter_i;
+ }
+ else {
+ ptr->iter = (VALUE (*)())rb_yield;
+ }
+ ptr->args = enum_args;

- return Qnil;
+ return obj;
}

+static VALUE enumerator_iter _((VALUE));
static VALUE
-enumerator_iter(memo)
- NODE *memo;
+enumerator_iter(arg)
+ VALUE arg;
{
- return rb_apply(memo->u1.value, memo->u2.id, memo->u3.value);
+ struct enumerator *e = (struct enumerator *)arg;
+
+ return method_call(e->method, e->args);
}

@@ -157,13 +271,16 @@ enumerator_each(obj)
VALUE obj;
{
- VALUE val;
+ struct enumerator *e = enumerator_ptr(obj);

- obj = (VALUE)rb_node_newnode(NODE_MEMO,
- rb_ivar_get(obj, id_enum_obj),
- rb_to_id(rb_ivar_get(obj, id_enum_method)),
- rb_ivar_get(obj, id_enum_args));
- val = rb_iterate((VALUE (*)_((VALUE)))enumerator_iter, obj, rb_yield, 0);
- rb_gc_force_recycle(obj);
- return val;
+ return rb_iterate(enumerator_iter, (VALUE)e, e->iter, (VALUE)e);
+}
+
+static VALUE
+enumerator_call(obj, args)
+ VALUE obj, args;
+{
+ struct enumerator *e = enumerator_ptr(obj);
+
+ return method_call(e->method, rb_ary_concat(e->args, args));
}

@@ -183,10 +300,13 @@ Init_enumerator()
rb_define_method(rb_mEnumerable, "each_cons", enum_each_cons, 1);
rb_define_method(rb_mEnumerable, "enum_cons", enumerator_enum_cons, 1);
+ rb_define_method(rb_mEnumerable, "enum_if", enumerator_enum_if, -2);

rb_cEnumerator = rb_define_class_under(rb_mEnumerable, "Enumerator", rb_cObject);
rb_include_module(rb_cEnumerator, rb_mEnumerable);

+ rb_define_alloc_func(rb_cEnumerator, enumerator_allocate);
rb_define_method(rb_cEnumerator, "initialize", enumerator_initialize, -1);
rb_define_method(rb_cEnumerator, "each", enumerator_each, 0);
+ rb_define_method(rb_cEnumerator, "call", enumerator_call, -2);

sym_each = ID2SYM(rb_intern("each"));
@@ -195,7 +315,6 @@ Init_enumerator()
sym_each_cons = ID2SYM(rb_intern("each_cons"));

- id_new = rb_intern("new");
- id_enum_obj = rb_intern("enum_obj");
- id_enum_method = rb_intern("enum_method");
- id_enum_args = rb_intern("enum_args");
+#if !defined(HAVE_RB_PROC_CALL) || !defined(HAVE_RB_METHOD_CALL)
+ id_call = rb_intern("call");
+#endif
}
Index: ext/enumerator/extconf.rb
===================================================================
RCS file: ext/enumerator/extconf.rb
diff -N ext/enumerator/extconf.rb
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ ext/enumerator/extconf.rb 18 May 2004 11:50:10 -0000
@@ -0,0 +1,5 @@
+require 'mkmf'
+
+%w"rb_obj_method rb_method_call".all? {|f| have_func(f, "ruby.h")}
+have_func("rb_proc_call", "ruby.h")
+create_makefile("enumerator")
 
K

Kristof Bastiaensen

5.enum_filter:)times){ |i| i[0] == 0 }.collect
=> [2, 4]
=> [0, 2, 4]

Isn't it?

Oh, yes, of course. (stupid mistake)
(2)* Enumerable::Enumerator takes a optional block

powers = 4.enum_for:)times){ |i| i * i }
powers.collect
=> [0, 1, 4, 9]

I tried the implementation of these two experimentally. But I
feel enum_filter is overdoing after all.

This patch provides block for Enumerable#enum_for and
Enumerable#enum_if instead.

Yes, that's cleaner. I like the naming also.
$ ./ruby -renumerator -e 'p 5.enum_for:)times){|i| i*i}.to_a'
[0, 1, 4, 9, 16]
$ ./ruby -renumerator -e 'p 5.enum_for:)times).enum_if{|i| i[0] == 0}.to_a'
[0, 2, 4]

Just my 2 yens.
Great!

Thanks,
Kristof
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,756
Messages
2,569,535
Members
45,008
Latest member
obedient dusk

Latest Threads

Top