How to combine blocks and recursiveness

L

Luis

Hello,

I am trying to code a method that accepts a block... easy. The problem
is that this method is recursive, so I don't know how to pass the
block through the recursive calls.

The specific code is about recurising through a directory tree while
calling the provided code block once per each directory, passing the
list of files in that directory. This is the code:

class DirInfo

# constructor, etc. removed for clarity

def scan
@path.each_entry do |p|
next if p.to_s == "." || p.to_s == ".."
p = @path + p
if p.file?
finf = FileInfo.new(p)
@files << finf
elsif p.directory?
dinf = DirInfo.new(p)
dinf.scan # ==> Recursive search of subdirs
end
end
yield @path, @files
end

end


So if I do:

di = DirInfo.new("/devtools/ruby")
di.scan do |path, files|
puts "Directory #{path} has #{files.length} files"
end

I expect to get my block called once per each subdirectory, passing
the subdir path and list of files in that subdir. The problem is that
the fisrt time the call scan is made from within scan, no block is
given, so I get a "no block given" exception and the program stops.

I know there are other ways to scan directories, but please take it
just as an example. Still the question is, how to combine
recursiveness with blocks?

Thanks in advance,

Luis.
 
M

MenTaLguY

def scan
@path.each_entry do |p|
next if p.to_s == "." || p.to_s == ".."
p = @path + p
if p.file?
finf = FileInfo.new(p)
@files << finf
elsif p.directory?
dinf = DirInfo.new(p)
dinf.scan # ==> Recursive search of subdirs
end
end
yield @path, @files
end

Another poster mentioned the &block thing, which is probably preferable.

Note that you could also simply do:

dinf.scan { |p,f| yield p,f }

The advantage to this is that (for shallow recursion), there is less
overhead than creating a Proc object for the block. The big disadvantage
is that you add an extra layer of yield-ing for each level recursion.

-mental
 
R

Robert Klemme

Hello,

I am trying to code a method that accepts a block... easy. The problem
is that this method is recursive, so I don't know how to pass the
block through the recursive calls.

The specific code is about recurising through a directory tree while
calling the provided code block once per each directory, passing the
list of files in that directory. This is the code:

class DirInfo

# constructor, etc. removed for clarity

def scan
@path.each_entry do |p|
next if p.to_s == "." || p.to_s == ".."
p = @path + p
if p.file?
finf = FileInfo.new(p)
@files << finf
elsif p.directory?
dinf = DirInfo.new(p)
dinf.scan # ==> Recursive search of subdirs
end
end
yield @path, @files
end

end


So if I do:

di = DirInfo.new("/devtools/ruby")
di.scan do |path, files|
puts "Directory #{path} has #{files.length} files"
end

I expect to get my block called once per each subdirectory, passing
the subdir path and list of files in that subdir. The problem is that
the fisrt time the call scan is made from within scan, no block is
given, so I get a "no block given" exception and the program stops.

I know there are other ways to scan directories, but please take it
just as an example. Still the question is, how to combine
recursiveness with blocks?

The typical (and IIRC most efficient) idiom is to pass on the block
parameter:

def foo(&b)
foo(&b)
end

Another remark: it seems you are reinventing Find.find(). Why do you do
that?

Kind regards

robert
 
Y

yermej

Hello,

I am trying to code a method that accepts a block... easy. The problem
is that this method is recursive, so I don't know how to pass the
block through the recursive calls.

The specific code is about recurising through a directory tree while
calling the provided code block once per each directory, passing the
list of files in that directory. This is the code:

class DirInfo

# constructor, etc. removed for clarity

def scan
@path.each_entry do |p|
next if p.to_s == "." || p.to_s == ".."
p = @path + p
if p.file?
finf = FileInfo.new(p)
@files << finf
elsif p.directory?
dinf = DirInfo.new(p)
dinf.scan # ==> Recursive search of subdirs
end
end
yield @path, @files
end

end

So if I do:

di = DirInfo.new("/devtools/ruby")
di.scan do |path, files|
puts "Directory #{path} has #{files.length} files"
end

I expect to get my block called once per each subdirectory, passing
the subdir path and list of files in that subdir. The problem is that
the fisrt time the call scan is made from within scan, no block is
given, so I get a "no block given" exception and the program stops.

I know there are other ways to scan directories, but please take it
just as an example. Still the question is, how to combine
recursiveness with blocks?

Thanks in advance,

Luis.

You can make the block argument explicit:

def scan(&block)
# do stuff
scan &block # <== recursive call with block
yield
end

I think that'll work.
 
L

Luis

The typical (and IIRC most efficient) idiom is to pass on the block
parameter:

def foo(&b)
foo(&b)
end

That looks nice and simple.
Thanks!

Another remark: it seems you are reinventing Find.find(). Why do you do
that?


As I said, the dir scanning was just an example of recursiveness.
Still, I do need to scan a directory in a way that I get an array of
files per each block call, and not one file at a time, because I want
to compare the contents of a directory with other directory, in order
to identify changed files, new files, deleted files, etc.

Regards,

Luis.
 
R

Robert Klemme

2007/10/18 said:
That looks nice and simple.
Thanks!




As I said, the dir scanning was just an example of recursiveness.
Still, I do need to scan a directory in a way that I get an array of
files per each block call, and not one file at a time, because I want
to compare the contents of a directory with other directory, in order
to identify changed files, new files, deleted files, etc.

You can still build that with Find.find() which saves you the traversal code:

#!ruby

require 'find'
require 'pp'

ARGV.each do |dir|
list = Hash.new {|h,k| h[k] = []}

Find.find dir do |f|
d, b = File.split f
next if /^\.\.?$/ =~ b
list[d] << b
end

pp list
end

Cheers

robert
 
L

Luis

2007/10/18, Luis <[email protected]>:


That looks nice and simple.
Thanks!
As I said, the dir scanning was just an example of recursiveness.
Still, I do need to scan a directory in a way that I get an array of
files per each block call, and not one file at a time, because I want
to compare the contents of a directory with other directory, in order
to identify changed files, new files, deleted files, etc.

You can still build that with Find.find() which saves you the traversal code:

#!ruby

require 'find'
require 'pp'

ARGV.each do |dir|
list = Hash.new {|h,k| h[k] = []}

Find.find dir do |f|
d, b = File.split f
next if /^\.\.?$/ =~ b
list[d] << b
end

pp list
end

Cheers

robert

Cool! The code is much more advanced compared with mine... yes, I am a
Ruby beginner.
Still, if I understood your code well, it creates a whole directory
tree in memory, which, if applied to "/" (or "c:\") it can really take
a huge amount of RAM.

I want to process the list of files in each directory, one directory
at a time, and then forget about these files once they are processed.
However I guess I can modify your code above a bit, remove the hash,
check that "d" is the same as the previous pass, and if not yield the
list, etc.

Regards,

Luis.
 
D

Dirk Traulsen

Am 18 Oct 2007 um 18:22 hat Robert Klemme geschrieben:
#!ruby

require 'find'
require 'pp'

ARGV.each do |dir|
list = Hash.new {|h,k| h[k] = []}

Find.find dir do |f|

For Windows it might be nicer to add
f= f.gsub(/\\/,'/')
so you don't have a mixture of '\' and '/' as separators.
d, b = File.split f
next if /^\.\.?$/ =~ b

Find:find does not return '.' or '..' like Dir:entries does for example
as Find.find is implicitely recursive.
So this line can be omitted.
list[d] << b
end

pp list
end

Have fun!
Dirk
 

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,774
Messages
2,569,596
Members
45,143
Latest member
SterlingLa
Top