[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

How to combine blocks and recursiveness

Luis

10/17/2007 5:37:00 PM

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.

7 Answers

MenTaLguY

10/17/2007 6:02:00 PM

0

On Thu, 18 Oct 2007 02:40:09 +0900, Luis <lcrespom@gmail.com> wrote:
> 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


Robert Klemme

10/17/2007 6:21:00 PM

0

On 17.10.2007 19:36, Luis wrote:
> 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

yermej

10/17/2007 7:51:00 PM

0

On Oct 17, 12:36 pm, Luis <lcres...@gmail.com> wrote:
> 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.

Luis

10/18/2007 7:27:00 AM

0

> 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.


Robert Klemme

10/18/2007 9:23:00 AM

0

2007/10/18, Luis <lcrespom@gmail.com>:
> > 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.

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

Luis

10/19/2007 8:48:00 AM

0

On Oct 18, 11:22 am, "Robert Klemme" <shortcut...@googlemail.com>
wrote:
> 2007/10/18, Luis <lcres...@gmail.com>:
>
>
>
> > > 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.
>
> 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.

Dirk Traulsen

10/19/2007 5:31:00 PM

0

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