tags:

views:

67

answers:

3

I am not sure what is the best strategy for this. I have a class, where I can search the filesystem for a certain pattern of files. I want to execute Find.find("./") only once. how would I approach this:

  def files_pattern(pattern)
    Find.find("./") do |f| 
      if f.include? pattern
           @fs << f
      end
    end
  end
A: 

how about system "find / -name #{my_pattern}"

Jed Schneider
use back ticks if you want the results instead of it just going to stdout.
Jed Schneider
That would fail in Windows for sure and in any other platform that may not have find available or taking the same parameters. I would do this only as the last measure.
J. Pablo Fernández
I actually deleted this answer immediately after posting (but apparently it didn't get deleted) bc there is more problems with it than mentioned after I read the question a second time.
Jed Schneider
+3  A: 

Remembering the (usually computationally intensive) result of a method call so that you don't need to recalculate it next time the method is called is known as memoization so you will probably want to read more about that.

One way of achieving that it Ruby is to use a little wrapper class that stores the result in an instance variable. e.g.

class Finder
  def initialize(pattern)
    @pattern = pattern
  end

  def matches
    @matches ||= find_matches
  end

  private

  def find_matches
    fs = []
    Find.find("./") do |f| 
      if f.include? @pattern
        fs << f
      end
    end
    fs
  end
end

And then you can do:

irb(main):089:0> f = Finder.new 'xml'
=> #<Finder:0x2cfc568 @pattern="xml">
irb(main):090:0> f.matches
find_matches
=> ["./example.xml"]
irb(main):091:0> f.matches # won't result in call to find_matches
=> ["./example.xml"]

Note: the ||= operator performs an assignment only if the variable on the left hand side does evaluates to false. i.e. @matches ||= find_matches is shorthand for @matches = @matches || find_matches where find_matches will only be called the first time due to short circuit evaluation. There are lots of other questions explaining it on Stackoverflow.


Slight variation: You could change your method to return a list of all files and then use methods from Enumerable such as grep and select to perform multiple searches against the same list of files. Of course, this has the downside of keeping the entire list of files in memory. Here is an example though:

def find_all
  fs = []
  Find.find("./") do |f| 
    fs << f
  end
  fs
end

And then use it like:

files = find_all
files.grep /\.xml/
files.select { |f| f.include? '.cpp' }
# etc
mikej
that is OK, but if I now want to find first .xml, then .cpp, .c, .h and I do this over a large part of the filesystem, I end up with calling Find.find("./") many times. Or?
poseid
hm.. I think I would need to store all entries of the filessystem in a textfile, and apply the Finder class to the textfile, that should be faster than find() everytime, ofcourse, I must update the textfile before I do the filterting.
poseid
@poseid I have added a variation to the end of the answer. See if that is useful at all.
mikej
+1  A: 

If I understand your question correctly you want to run Find.find to assign the result to an instance variable. You can move what is now the block to a separate method and call that to return only files matching your pattern.

Only problem is that if the directory contains many files, you are holding a big array in memory.

Michael Kohl
thanks. i basically missed the point to translate the block back into a method.
poseid