1

I have some code that reads a file into an array of lines, and then parses those lines to get at the structured data. The input file has various different data types that need to be handled differently, additionally there are major sections for various accounts (mobile numbers).

I loop through the lines looking for the account line, identify the account, and then I want to use that account until I encounter the next account line. The lines in between potentially represent various types of data belonging to that account. The problem is, after I find the account line and set a local variable (cur_num), the variable is set to nil when I want to use it. Why, how is this happening? I am learning Ruby so I want more than a fix - I want to understand why it works this way.

Here is my code:

  count = 0
  cur_num = ""
  lines.each do |line|
  unless (line.strip.eql?(""))  # edited due to comment from normalocity
    if (line.slice(0,15) == "Mobile Number:,")
      cur_num = line.slice(15,12)
      count = 1
      puts "Current Number: #{cur_num}"
      #puts "Object Type: #{cur_num.class}"
    else 
      data = line.strip.split(',')
      if (data.length > 8)
        data.unshift(cur_num)
        #if (count.modulo(10) == 0 || count == 1)
          puts "[#{cur_num}] #{data.inspect}"
          #pp data
        #end
        count += 1
      end
    end
  end
end

An overview of the input data structure would look like this:

Account 1
    Data Section A
        data line 1
        data line 2
     Data Section B
        data line 1
        data line 2
Account 2
    Data Section A
        data line 1
        data line 2
     Data Section B
        data line 1
        data line 2

end

adding code to duplicate lines array you should paste this above code sample if you are attempting to duplicate. I'm putting it here at the end to try to make my question more readable:

lines = []
lines.push("ATT Wireless Bill")
lines.push("")
lines.push("Mobile Number:,770-555-1212")
lines.push("item,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge")
lines.push("")
lines.push("1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00")
lines.push("")
lines.push("2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00")
lines.push("")
lines.push("1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00")
lines.push("")
lines.push("Mobile Number:,770-555-1213")
lines.push("item,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge")
lines.push("")
lines.push("1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00")
lines.push("")
lines.push("2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00")
lines.push("")
lines.push("1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00")
lines.push("")
Scott
  • 674
  • 1
  • 7
  • 15
  • Just a code style note, instead of writing `if (line.strip == "") elsif ...`, use `unless line.strip.eql?("") ...` 1) It eliminates an empty if statement, and 2) you need to use the `.eql?` method to compare the content of Strings. http://ruby-doc.org/core/classes/String.html#M001122 – jefflunt Sep 17 '11 at 15:53
  • What is `good_lines`? Just an implementation of a stack? I can't get the code to work on my side, because it's undefined (i.e. you're not providing the code, or telling us what it is). – jefflunt Sep 17 '11 at 16:01
  • Sorry - for not providing complete code - trying to keep it relevant and concise. good_lines is not needed for my problem, nor is count. I will edit original post. – Scott Sep 17 '11 at 16:26
  • Not sure if anyone can run the code, since they don't have the data file, which I cannot post because it contains sensitive data. I can create some code to replicate the data read into an array of text lines, but I was hoping that my oversight would be so obvious to others as to jump out just by providing my code sample. – Scott Sep 17 '11 at 16:33
  • @normalocity Thanks for the code and style advice. Very helpful and very much appreciated! I am not sure of etiquette - do I alter my code to reflect your advice thus making your comment appear incongruous, or leave it stand? – Scott Sep 17 '11 at 16:33
  • It's up to you. I usually change my code if I think the advice is good, but I certainly don't have any ego attached to whether or not you change it. So long as you get working code when you're done with your question, I'm happy. – jefflunt Sep 17 '11 at 16:45
  • Can you post an actual data file (even if it only has sample data)? I'm actually running the code on my side, and would like to have valid test data to play with. – jefflunt Sep 17 '11 at 16:49
  • 2
    You don't need ``eql?`` to compare strings. Also, you could just use ``empty?`` in this case. – Mon ouïe Sep 17 '11 at 16:59
  • I tried your code with your test date (lines). I found no nil there. Are the data showing your problem, or are they just some data? – knut Sep 17 '11 at 18:17
  • I just tried pasting the code from this question into a new .rb file, and lo and behold - it works as expected. Now I must figure out what I changed because my original code reports nil for cur_num in the puts "[#{cur_num}] #{data.inspect}" line. Obviously no one can assist at this point. – Scott Sep 17 '11 at 18:39
  • One hint, what could be the fault: If the string is shorter then slice starts, then you get nil. Example: `'xx'.slice(15,12)`. Your `cur_num = line.slice(15,12)` would return nil, if line is shorter then 15 characters (it's not possible in the code you posted, but perhaps in a previous version). – knut Sep 17 '11 at 19:19

2 Answers2

1

Answer to the answer Why / How is my variable getting set to nil

Without cur_num = "" you don't initialize cur_num outside the loop (lines.each). So cur_num is initialized in each loop.

I would expect a undefined local variable or method error, but it seems cur_num is created in each loop, even, if the if-branch is not executed. So you have cur_num, but without a value (or better: it is nil).

I added a short example to show, that a variable is created, even if the code is not executed:

if false
  a = 1
else
  p a #works fine, a was created, but it is nil
  p b #undefined local variable or method `b' 
end

Addendum II:

The following code shows, that a variable inside a loop is 'loop internal'. The 2nd (and following) cycle(s) starts without the variable a.

#~ a = 0  # uncomment to compare
5.times{
  if defined? a
    puts "a is defined as #{a.inspect}"
  else
    puts "a is not defined"
    a = 1 #define it now
  end
}

Related questions: The scope is confusing


I redesigned your code a bit. When I parse texts like yours, I prefer to use a case-statement with regular expressions:

count = 0
cur_num = ""
DATA.each_line do |line|
  case line
    when /\A\s\Z*/ #skip empty lines
    when /Mobile Number:,(.{12})/
      cur_num = $1
      count = 1
      puts "Current Number: #{cur_num}"
      #puts "Object Type: #{cur_num.class}"
    else 
      data = line.strip.split(',')
      if data.length > 8
        data.unshift(cur_num)
        #if (count.modulo(10) == 0 || count == 1)
          puts "[#{cur_num}] #{data.inspect}"
          #pp data
        #end
        count += 1
      end #(data.length > 8)
  end #case line
end

__END__
ATT Wireless Bill

Mobile Number:,770-555-1212
item,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge

1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00

2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00

1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00

Mobile Number:,770-555-1213
item,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge

1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00

2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00

1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00
Community
  • 1
  • 1
knut
  • 27,320
  • 6
  • 84
  • 112
0

After staring at this for over an hour, comparing two nearly identical versions, one that worked, one that didn't, I am able to make it fail or succeed by commenting out a single line. here is the exact code I am running: [Edit note, altered the code to prevent vertical scroll bar in code sample]

#!/usr/local/bin/ruby
my_input ="ATT Wireless Bill\n\nMobile Number:,770-555-1212\n\nitem,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge\n\n1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00\n\n"
my_input << "2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00\n\n1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00\n\nMobile Number:,770-555-1213\n\nitem,date,time,number called,rate period,plan type,minutes,airtime charge,ld charge,total charge\n\n"
my_input << "1,2011-01-02,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00\n\n2,2011-01-03,6:56AM,404-555-1212,NW,UNW,4,0.00,0.00,0.00\n\n1,2011-01-03,7:56AM,404-555-1213,DT,UM2M,5,0.00,0.00,0.00\n\n"
lines = my_input.split("\n")
count = 0
cur_num = "" # Line  7 - Comment out this line to see failure
lines.each do |line|
unless (line.strip.eql?(""))
  if (line.slice(0,15) == "Mobile Number:,")
    cur_num = line.slice(15,12)
    count = 1
    puts "Current Number: #{cur_num}"
    #puts "Object Type: #{cur_num.class}"
  else 
    data = line.strip.split(',')
    if (data.length > 8)
      data.unshift(cur_num)
      #if (count.modulo(10) == 0 || count == 1)
        puts "[#{cur_num}] #{data.inspect}"
        #pp data
      #end
      count += 1
    end
    end
  end
end

If I comment out line 7, then cur_val is nil on lines 18 and 20.

Can someone please explain why?

Scott
  • 674
  • 1
  • 7
  • 15
  • Without `cur_num = "" ` you don't initialize cur_num (it is nil). Your loop start without `Current Number:`, so you go to the else-branch (with cur_num = nil). – knut Sep 17 '11 at 20:24
  • I do not understad. cur_num gets initiated before the unshift statement of line 18 - it should not be nil at that point. And why, if I set to an empty string before the each iteration, is it not then an empty string at line 18. This appears inconsistent to me, and I suspect that this an important concept necessary to understanding Ruby. – Scott Sep 17 '11 at 20:51
  • I made a bit a longer answer in http://stackoverflow.com/questions/7455939/why-how-is-my-variable-getting-set-to-nil/7457697#7457697 Could you adapt your question, so the answer is the answer to the question and not to another answer? – knut Sep 17 '11 at 20:52
  • To state my previous comment somewhat differently - why, if the variable is initiated as an empty string prior to the each iteration, does the assignment in line 11 seem to be "sticky", whereas when I do not initiate prior to the each iteration, the assignment appears fleeting. – Scott Sep 17 '11 at 20:54
  • See http://stackoverflow.com/questions/6323421/the-scope-is-confusing or my addendum II in my answer. I hope this explains it. – knut Sep 17 '11 at 21:41