From: Matthew Kerwin Date: 2013-02-03T15:49:44+09:00 Subject: Re: Class for data analysis: File.open once --047d7b2e40b89a689204d4cc616d Content-Type: text/plain; charset=ISO-8859-1 On 3 February 2013 11:13, Soichi Ishida wrote: > ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-darwin12.2.1] > > I am trying to analyze a file and extract necessary data from it. > First I need to count the total number of lines and the blank lines. the > class and methods follow. > > class DataExtractor > attr_reader :file_name > def initialize(file) > @file_name = file > end > > def total_lines > @lines = 0 > f = File.open(@file_name, 'r') > f.read.each_line do |l| > @lines += 1 > end > f.close > @lines > end > > def total_blank_lines > regEx = /^[\s]*$\n/ > @total_blank_lines = 0 > f = File.open(@file_name, 'r') > f.read.each_line do | line | > if line =~ regEx > @total_blank_lines += 1 > end > end > f.close > @total_blank_lines > end > .... > > The problem is that each method has 'File.open...each_line do ' so the > whole loop gets executed separately. > > Don't you think this is inefficient? Is there better ways to develop > methods so that the loop is needed only once? > Yep, sure do; and yep, sure is. The fact that you're using @variables inside your methods is already a hint at how to improve things -- have the object remember them. My first approach would be to do all the file analysis in the constructor, sort of like this (using your code; it would look a little different if I wrote it for myself): class DataExtractor attr_reader :file_name, :lines, :total_blank_lines def initialize(file) @file_name = file @lines = 0 @total_blank_lines = 0 regEx = /^[\s]*$\n/ f = File.open(@file_name, 'r') f.read.each_line do | line | if line =~ regEx @total_blank_lines += 1 end @lines += 1 end f.close end end My next approach would be to lazy-initialise the data, because that's just a thing I like to do. It means the lines aren't counted until they're needed, in case that's an improvement. (Again, rough code, untested.) class DataExtractor attr_reader :file_name def initialize(file) @file_name = file @lines = -1 @total_blank_lines = -1 end def lazy_init_lines regEx = /^[\s]*$\n/ f = File.open(@file_name, 'r') f.read.each_line do | line | if line =~ regEx @total_blank_lines += 1 end @lines += 1 end f.close end def lines lazy_init_lines if @lines < 0 @lines end def total_blank_lines lazy_init_lines if @total_blank_lines < 0 @total_blank_lines end end In either case you perform a single open-read-close loop, and can access the totals over and over again. Various further optimisations and improvements are available to be made at your discretion. -- Matthew Kerwin, B.Sc (CompSci) (Hons) http://matthew.kerwin.net.au/ ABN: 59-013-727-651 "You'll never find a programming language that frees you from the burden of clarifying your ideas." - xkcd --047d7b2e40b89a689204d4cc616d Content-Type: text/html; charset=ISO-8859-1 Content-Transfer-Encoding: quoted-printable
On 3 February 2013 11:13, Soichi Ishida = <lists@ruby-fo= rum.com> wrote:
ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-darwin1= 2.2.1]

I am trying to analyze a file and extract necessary data from it.
First I need to count the total number of lines and the blank lines. the class and methods follow.

class DataExtractor
=A0 =A0 attr_reader :file_name
=A0 =A0 def initialize(file)
=A0 =A0 =A0 =A0 @file_name =3D file
=A0 =A0 end

=A0 =A0 def total_lines
=A0 =A0 =A0 =A0 @lines =3D 0
=A0 =A0 =A0 =A0 f =3D File.open(@file_name, 'r')
=A0 =A0 =A0 =A0 f.read.each_line do |l|
=A0 =A0 =A0 =A0 =A0 =A0 @lines +=3D 1
=A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0 f.close
=A0 =A0 =A0 =A0 @lines
=A0 =A0 end

=A0 =A0 def total_blank_lines
=A0 =A0 =A0 =A0 regEx =3D /^[\s]*$\n/
=A0 =A0 =A0 =A0 @total_blank_lines =3D 0
=A0 =A0 =A0 =A0 f =3D File.open(@file_name, 'r')
=A0 =A0 =A0 =A0 f.read.each_line do | line |
=A0 =A0 =A0 =A0 =A0 =A0 if line =3D~ regEx
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 @total_blank_lines +=3D 1
=A0 =A0 =A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0 f.close
=A0 =A0 =A0 =A0 @total_blank_lines
=A0 =A0 end
=A0 =A0 ....

The problem is that each method has 'File.open...each_line do ' so = the
whole loop gets executed separately.

Don't you think this is inefficient? =A0Is there better ways to develop=
methods so that the loop is needed only once?

Yep, sure do; and yep, sure is. =A0The fact that you're using @v= ariables inside your methods is already a hint at how to improve things -- = have the object remember them. =A0My first approach would be to do all the = file analysis in the constructor, sort of like this (using your code; it wo= uld look a little different if I wrote it for myself):

=A0 =A0 class DataExtractor
=A0 = =A0 =A0 attr_reader :file_name, :lines, :total_blank_lines
= =A0 =A0 =A0 def initialize(file)
=A0 =A0 =A0 =A0 @file_name= =3D file
=A0 =A0 =A0 =A0 @lines =3D 0
=A0 =A0 =A0 =A0 @total_blank_l= ines =3D 0
=A0 =A0 =A0 =A0 regEx =3D /^[\s]*$\n/
=A0 =A0 =A0 =A0 f = =3D File.open(@file_name, 'r')
=A0 =A0 =A0 =A0 f.read.each_line = do | line |
=A0 =A0 =A0 =A0 =A0 if line =3D~ regEx
=A0 =A0 =A0 =A0 =A0 =A0 @total_blank_lines +=3D 1
=A0 =A0 =A0 =A0 =A0 en= d
=A0 =A0 =A0 =A0 =A0 @lines +=3D 1
=A0 = =A0 =A0 =A0 end
=A0 =A0 =A0 =A0 f.close
=A0 =A0 =A0 end
=A0 =A0 end

My next approach would be to laz= y-initialise the data, because that's just a thing I like to do. =A0It = means the lines aren't counted until they're needed, in case that&#= 39;s an improvement. (Again, rough code, untested.)

=A0 =A0 class DataExtra= ctor
=A0 =A0 =A0 attr_reader :file_name
=A0 =A0=A0
=A0 =A0 =A0 def initialize(file)
=A0 =A0 =A0 =A0 @file_name= =3D file
=A0 =A0 =A0 =A0 @lines =3D -1
=A0 =A0 =A0 =A0 @total_blank_lines =3D -1
=A0 = =A0 =A0 end
=A0 =A0=A0
=A0 =A0 =A0 def lazy_init_= lines
=A0 =A0 =A0 =A0 regEx =3D /^[\s]*$\n/
=A0 =A0 =A0 =A0 f =3D F= ile.open(@file_name, 'r')
=A0 =A0 =A0 =A0 f.read.each_line do | = line |
=A0 =A0 =A0 =A0 =A0 if line =3D~ regEx
=A0 =A0 =A0 =A0 =A0 =A0 @total_bl= ank_lines +=3D 1
=A0 =A0 =A0 =A0 =A0 end
=A0 =A0 =A0 =A0 =A0 @lines +=3D 1
=A0 =A0 =A0 =A0 end
=A0 =A0 =A0 = =A0 f.close
=A0 =A0 =A0 end
=A0 =A0=A0
=A0 = =A0 =A0 def lines
=A0 =A0 =A0 =A0 lazy_init_lines if @lines < 0
=A0 =A0 =A0= =A0 @lines
=A0 =A0 =A0 end
=A0 =A0=A0
=A0 = =A0 =A0 def total_blank_lines
=A0 =A0 =A0 =A0 lazy_init_lines if = @total_blank_lines < 0
=A0 =A0 =A0 =A0 @total_blank_lines
=A0 =A0 =A0 end
=A0 =A0 end

In either case you perform a single open-read-close loop, and can access= the totals over and over again. =A0Various further optimisations and impro= vements are available to be made at your discretion.

--
=A0 Matthew Kerwin, B.Sc (CompSci) (Hons)=
=A0 http://= matthew.kerwin.net.au/
=A0 ABN: 59-013-727-651

=A0 "You&= #39;ll never find a programming language that frees
=A0 you from the burden of clarifying your ideas." - xkcd
--047d7b2e40b89a689204d4cc616d--