Skip the first invocation e.g. skip_first { foo }

B

Brian Adkins

I'd try something along these lines:

# sets up a binding with a local variable _iteration = 0, and then
# creates a lambda which inherits this binding, and can use the variable
# note that the lambda also inherits any block given to skip_first,
# so it can also yield

def skip_first(_skip_count = 1)
_iteration = 0
lambda do |x|
yield x if _iteration >= _skip_count
_iteration += 1
end
end

# you can then pass the lambda returned by skip_first to any iterator,
# using the & notation

5.times &skip_first {|a| puts a}
1
2
3
4

So how do you pass the "real" block to the iterator then? The main
point of the iteration isn't the code that's skipped on the first
iteration, but the main body of the iteration. For example:

foo.each do |elem|
skip_first do
# processing to be skipped the first time
# through the loop that may or may not use
# elem
end
# other important processing using elem
end
 
M

mortee

Brian said:
So how do you pass the "real" block to the iterator then? The main
point of the iteration isn't the code that's skipped on the first
iteration, but the main body of the iteration. For example:

foo.each do |elem|
skip_first do
# processing to be skipped the first time
# through the loop that may or may not use
# elem
end
# other important processing using elem
end

Sorry, it wasn't clear to me by this whole thread but this last post
that you wanted code which doesn't skip elements, as well as code which
does.

The bad news is that I guess from inside the block passed to the
iterator, you have no way (at least no nice way) to keep track of the
iteration count, and make decisions based on it. You either have to call
each_with_index, and make use of the passed index, but this won't work
with other iterators.

You might also try to set up some global variable the first time your
block is run, and then utilize that. Note that this will get tricky even
when repeatedly running loops with skipping sections - not to mention
nested ones.

So in any way, you will have to somehow estabilish what is considered
your "outermost" scope, and do some counting relative to that. (Just
consider how skipping would work if called from inside a nested loop.
Will it skip the first n iteration of the outer loop, or the inner one?)

All in all, you'll almost certainly have to set up your skipping and
non-skipping code blocks while still *outside* the loop itself. This is
what e.g. the above proposed skip_first method would do, just hiding the
variable initialization part from the caller.

It could e.g. be extended to accept two blocks: one which skips the
first n iterations, and one wich does not - but this wouldn't be elegant
any more, and also the local variables would be separate in the two blocks.

What you could still perhaps do is trying to tag your calling contexts
as the basis for skipping, and then from inside the loop body try to
refer to it. Like going upwards the calling stack using
Binding.of_caller, and look for a specially named local variable which
is set up by the former facility, and use that as a reference for how
many times your block has been called. Something like:

collection.some_iterator &skip_context do
# this sets up the context with some counter variable
...
skip_first {
# whithin the skip_first method the above mentioned variable isn't
# visible, but it may be sought for using some stack-traversing
# trickery
...
}
...
end

mortee
 
R

Robert Klemme

2007/10/26 said:
Sorry, it wasn't clear to me by this whole thread but this last post
that you wanted code which doesn't skip elements, as well as code which
does.

Oh, that eluded me as well. In that case it's certainly best to just
use #each_with_index and use the index as you said.
The bad news is that I guess from inside the block passed to the
iterator, you have no way (at least no nice way) to keep track of the
iteration count, and make decisions based on it. You either have to call
each_with_index, and make use of the passed index, but this won't work
with other iterators.

It does, if you use Enumerator:

17:26:33 ~$ irb -r enumerator
irb(main):001:0> %w{foo bar baz}.to_enum:)each_with_index).
irb(main):002:0* map {|e,i| i % 2 == 0 ? e : :empty}
=> ["foo", :empty, "baz"]
irb(main):003:0>
You might also try to set up some global variable the first time your
block is run, and then utilize that. Note that this will get tricky even
when repeatedly running loops with skipping sections - not to mention
nested ones.

I would not go down that road either.
So in any way, you will have to somehow estabilish what is considered
your "outermost" scope, and do some counting relative to that. (Just
consider how skipping would work if called from inside a nested loop.
Will it skip the first n iteration of the outer loop, or the inner one?)

All in all, you'll almost certainly have to set up your skipping and
non-skipping code blocks while still *outside* the loop itself. This is
what e.g. the above proposed skip_first method would do, just hiding the
variable initialization part from the caller.

Frankly, I believe this shows it's all not worth the effort. Checking
the provided iteration counter is straightforward and easy to read and
understand.

Kind regards

robert
 
M

mortee

Robert said:
2007/10/26 said:
The bad news is that I guess from inside the block passed to the
iterator, you have no way (at least no nice way) to keep track of the
iteration count, and make decisions based on it. You either have to call
each_with_index, and make use of the passed index, but this won't work
with other iterators.

It does, if you use Enumerator:

17:26:33 ~$ irb -r enumerator
irb(main):001:0> %w{foo bar baz}.to_enum:)each_with_index).
irb(main):002:0* map {|e,i| i % 2 == 0 ? e : :empty}
=> ["foo", :empty, "baz"]
irb(main):003:0>

Thanks, this is nice - I haven't known it existed. However,
unfortunately it's less "elegant" than simply using the iterator itself,
and enclosing parts of the loop body in skip_first{...}

mortee
 
R

Robert Klemme

Robert said:
2007/10/26 said:
The bad news is that I guess from inside the block passed to the
iterator, you have no way (at least no nice way) to keep track of the
iteration count, and make decisions based on it. You either have to call
each_with_index, and make use of the passed index, but this won't work
with other iterators.
It does, if you use Enumerator:

17:26:33 ~$ irb -r enumerator
irb(main):001:0> %w{foo bar baz}.to_enum:)each_with_index).
irb(main):002:0* map {|e,i| i % 2 == 0 ? e : :empty}
=> ["foo", :empty, "baz"]
irb(main):003:0>

Thanks, this is nice - I haven't known it existed. However,
unfortunately it's less "elegant" than simply using the iterator itself,
and enclosing parts of the loop body in skip_first{...}

Obviously tastes differ. :) Personally I find the effort you have to
invest to get a correct working and failure safe version of skip_first
extremely inelegant for a problem which has a much simpler solution.

Kind regards

robert
 
A

ara.t.howard

Clever :) I've used a similar technique in JavaScript, but I don't
like defining the code outside of the loop, and it's also not as clear
as other solutions IMO.


it's just a building block... to generalize your idea, now that you
sketched it out more fully, i'd probably do


cfp:~ > cat a.rb
class Proc
def skip(n, i = 0) lambda{|*a| call *a if (i += 1) > n } end
end
class Object
def skip(n, &block) block.skip n end
end

3.times &skip(1){ puts 'foo' }

%w[ a b c ].each_with_index &skip(2){|element, index| p element, index}


cfp:~ > ruby a.rb
foo
foo
"c"
2


a @ http://codeforpeople.com/
 
P

Pit Capitain

2007/10/26 said:
Here's the specific example that motivated the question with my
current solution, hopefully it will clarify some things.

class SkipN
...
end

skip_first = SkipN.skip_first
ARGV.each do |domain|
skip_first.run { sleep 10 }
available = `whois #{domain}` =~ /No match for "#{domain.upcase}"\./
puts "#{domain} is #{available ? '' : 'NOT'} available"
end

Hi Brian, sorry for the late reply. Here's something with a simpler
calling syntax:

module SkipN
@callers = Hash.new(0)
def self.skip(n = 1, key = caller.first)
yield if (@callers[key] += 1) > n
end
def self.skip_first
skip(1, caller.first) { yield }
end
end

Usage:

3.times do |i|
puts "start #{i}"
SkipN.skip_first { puts "*** #{i} ***" }
puts "end #{i}"
end

puts

%w[a b c].each_with_index do |e, i|
puts "start #{e} at #{i}"
SkipN.skip(2) { puts "*** #{e} at #{i} ***" }
puts "end #{e} at #{i}"
end

Output:

start 0
end 0
start 1
*** 1 ***
end 1
start 2
*** 2 ***
end 2

start a at 0
end a at 0
start b at 1
end b at 1
start c at 2
*** c at 2 ***
end c at 2

Note that this only works in simple cases. For example it's not thread-safe.

Regards,
Pit
 
R

Rick DeNatale

Hi Brian, sorry for the late reply. Here's something with a simpler
calling syntax:

module SkipN
@callers = Hash.new(0)
def self.skip(n = 1, key = caller.first)
yield if (@callers[key] += 1) > n
end
def self.skip_first
skip(1, caller.first) { yield }
end
end
Bravo!

Note that this only works in simple cases. For example it's not thread-safe.

It also leaks memory since the @callers class instance variable never
has the keys deleted.

But cool nonetheless.
 
R

Robert Dober

<snip>

class Object
def skip(n=1,&block)
lambda{|*a| block.call *a if ( n -= 1 ) < 0 }
end
end

May I suggest this simplification or did I miss a hidden functionality?

Cheers
Robert
 
B

Brian Adkins

2007/10/26 said:
Here's the specific example that motivated the question with my
current solution, hopefully it will clarify some things.
class SkipN
...
end
skip_first = SkipN.skip_first
ARGV.each do |domain|
skip_first.run { sleep 10 }
available = `whois #{domain}` =~ /No match for "#{domain.upcase}"\./
puts "#{domain} is #{available ? '' : 'NOT'} available"
end

Hi Brian, sorry for the late reply. Here's something with a simpler
calling syntax:

module SkipN
@callers = Hash.new(0)
def self.skip(n = 1, key = caller.first)
yield if (@callers[key] += 1) > n
end
def self.skip_first
skip(1, caller.first) { yield }
end
end

Usage:

3.times do |i|
puts "start #{i}"
SkipN.skip_first { puts "*** #{i} ***" }
puts "end #{i}"
end

puts

%w[a b c].each_with_index do |e, i|
puts "start #{e} at #{i}"
SkipN.skip(2) { puts "*** #{e} at #{i} ***" }
puts "end #{e} at #{i}"
end

Very imaginative - thanks for the post :) It has a problem that will
exclude it (below), but I always like to see different ways of
thinking in Ruby. I don't see a possible solution without
initialization outside the loop.

2.times do
2.times do |i|
SkipN.skip_first { puts "i is #{i}" }
end
puts '-'*10
end

Output:

i is 1
 
P

Pit Capitain

2007/10/29 said:
Very imaginative - thanks for the post :) It has a problem that will
exclude it (below), but I always like to see different ways of
thinking in Ruby. I don't see a possible solution without
initialization outside the loop.

2.times do
2.times do |i|
SkipN.skip_first { puts "i is #{i}" }
end
puts '-'*10
end

Output:

i is 1

Ah, but you didn't show us this use case yet, didn't you?

Not that I would use or recommend the following code, but
nethertheless here's a more powerful (albeit slower) version:

module SkipN

@skips = Hash.new(0)
@min_levels = Hash.new(0)
@level = 0

@trace = lambda do |event, file, line, id, binding, classname|
case event
when "call", "c-call"
@level += 1
when "return", "c-return"
@level -= 1
@min_levels.each do |key, min|
@min_levels[key] = @level if @level < min
end
end
end

def self.skip(n = 1, reset_levels = 1, key = caller.first)
set_trace_func(nil)
@level -= 1
min = @min_levels[key]
@skips[key] = 0 if min > 0 and @level - min > reset_levels
yield if (@skips[key] += 1) > n
@min_levels[key] = @level
set_trace_func(@trace)
end

def self.skip_first(reset_levels = 1)
skip(1, reset_levels + 1, caller.first) { yield }
end

set_trace_func(@trace)

end

Usage:

3.times do |i1|
3.times do |i2|
3.times do |i3|
SkipN.skip_first(3) { puts "level 3, indexes #{i1}#{i2}#{i3}" }
SkipN.skip_first(2) { puts "level 2, indexes #{i1}#{i2}#{i3}" }
SkipN.skip_first(1) { puts "level 1, indexes #{i1}#{i2}#{i3}" }
end
end
end

The level 3 code only skips the first iteration with indexes 000.
The level 2 code skips iterations with indexes *00.
The level 1 code skips iterations with indexes **0.

But specifying the "reset level" in the inner loop looks as too much
magic to me. It's much more readable if you explicitly reset the
counter at the appropriate places.

Regards,
Pit
 

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

Forum statistics

Threads
473,772
Messages
2,569,593
Members
45,111
Latest member
KetoBurn
Top