简体   繁体   中英

How do I turn a string into the name of an array?

I've think I've created multiple arrays from strings but if I try to inspect the array I receive an error.

File.open("livestock.txt", "r") do |file|
    file.readlines.each do |x|

        if x.match(/:*:/)
            # puts x.inspect
            # strip string
            x.gsub!(/[^A-Za-z]/, '')
            x.downcase!
            puts x.inspect
            x = Array.new(){Hash.new}
            # puts x.inspect
            pigs.inspect
        else
            # puts "no"
        end

    end
end


animals.rb:12:in `block (2 levels) in <main>': undefined local variable or method `pigs' for main:Object (NameError)
    from animals.rb:2:in `each'
    from animals.rb:2:in `block in <main>'
    from animals.rb:1:in `open'
    from animals.rb:1:in `<main>'

Ideally I want to create pigs =[] then add hashes to this array such as:

pigs = [{"name"=>"peggy", "id"=>1, "owner"=>"wolcott farms"},
{"name"=>"sue", "id"=>2, "owner"=>"blue moon farms"},
{"name"=>"eddie", "id"=>3, "owner"=>"sunrise farms"}
]

and the same for cows, etc.

my text file animals.txt is

::pigs::  
name, id, owner
peggy, 1, wolcott farms 
sue, 2, blue moon farms
eddie, 3, sunrise farms

::cows:: 
name, id, owner 
dee, 3, black hat farms 
sunny, 2, blue moon farms 
bess, 4, wolcott farms

Since Ruby v1.8 it has not been possible to create local variables. Instance variables, yes (using Object#instance_variable_set ), local variables, no.

@CodeGnome has given one way to create instance variables with given names. I don't think a collection of instance variables is the appropriate data structure however. I would suggest you consider using a hash instead. You could do that as follows.

Code

def doit(fname)
  File.read(fname).
       split(/\n{2,}/). # split paragraphs
       each_with_object({}) do |s,h|
         outer_key, inner_key_names, *inner_values = s.split(/\n/)
         inner_keys = inner_key_names.split(/,\s+/)
         h[outer_key[/(?<=::)[^:]+/]] =
           inner_values.each_with_object([]) { |values_str, a|
             a << inner_keys.zip(values_str.split(/,\s+/)).to_h }
       end
end

Example

First let's create your file.

FName = "animals.txt"

data =<<_
::pigs::  
name, id, owner
peggy, 1, wolcott farms 
sue, 2, blue moon farms
eddie, 3, sunrise farms

::cows:: 
name, id, owner 
dee, 3, black hat farms 
sunny, 2, blue moon farms 
bess, 4, wolcott farms
_

File.write(FName, data)
  #=> 203

Now execute the method on this file.

doit(FName)
  #=> {"pigs"=>[{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
  #             {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
  #             {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}],
  #    "cows"=>[{"name"=>"dee", "id"=>"3", "owner "=>"black hat farms "},
  #             {"name"=>"sunny", "id"=>"2", "owner "=>"blue moon farms "},
  #             {"name"=>"bess", "id"=>"4", "owner "=>"wolcott farms"}]} 

Explanation

The steps are as follows.

b = File.read(FName).split(/\n{2,}/)
  #=> ["::pigs::  \nname, id, owner\npeggy, 1, wolcott farms \nsue, 2, blue moon farms\neddie, 3, sunrise farms",
  #    "::cows:: \nname, id, owner \ndee, 3, black hat farms \nsunny, 2, blue moon farms \nbess, 4, wolcott farms\n"]

Next we have

enum = b.each_with_object({})
  #=> #<Enumerator: ["::pigs::  \nname, id, owner\npeggy, 1, wolcott farms \nsue, 2, blue moon farms\neddie, 3, sunrise farms",
  #                  "::cows:: \nname, id, owner \ndee, 3, black hat farms \nsunny, 2, blue moon farms \nbess, 4, wolcott farms\n"]:each_with_object({})> 

We can convert enum to an array to see the values it will generate and pass to its block:

enum.to_a
  #=> [["::pigs::  \nname, id, owner\npeggy, 1, wolcott farms \nsue, 2, blue moon farms\neddie, 3, sunrise farms", {}],
  #    ["::cows:: \nname, id, owner \ndee, 3, black hat farms \nsunny, 2, blue moon farms \nbess, 4, wolcott farms\n", {}]] The empty arrays above will be populated as the calculations proceed.

We now pass the first element of enum to the block and assign the block variables

s,h = enum.next
  #=> ["::pigs::  \nname, id, owner\npeggy, 1, wolcott farms \nsue, 2, blue moon farms\neddie, 3, sunrise farms", {}] 
s #=> "::pigs::  \nname, id, owner\npeggy, 1, wolcott farms \nsue, 2, blue moon farms\neddie, 3, sunrise farms" 
h => {} 

The value of h , an empty hash, will be modified by the block, as will be shown.

The block calculations are now performed for these values of s and h .

outer_key, inner_key_names, *inner_values = s.split(/\n/)
  #=> ["::pigs::  ",
  #    "name, id, owner",
  #    "peggy, 1, wolcott farms ",
  #    "sue, 2, blue moon farms", "eddie, 3, sunrise farms"]
outer_key
  #=> "::pigs::  " 
inner_key_names
  #=> "name, id, owner" 
inner_values
  #=> ["peggy, 1, wolcott farms ",
  #    "sue, 2, blue moon farms",
  #    "eddie, 3, sunrise farms"] 
inner_keys = inner_key_names.split(/,\s+/)
  #=> ["name", "id", "owner"]
h[outer_key[/(?<=::)[^:]+/]] =
  inner_values.each_with_object([]) { |values_str, a|
    a << inner_keys.zip(values_str.split(/,\s+/)).to_h }
  # => [{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
  #     {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
  #     {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}] 
h #=> {"pigs"=>[{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
  #   {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
  #   {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}]} 

Let's examine this last calculation more closely.

outer_key[/(?<=::)[^:]+/]
  #=> "pigs"
enum1 = inner_values.each_with_object([])
  #=> #<Enumerator: ["peggy, 1, wolcott farms ", "sue, 2, blue moon farms",
  #                  "eddie, 3, sunrise farms"]:each_with_object([])> 
enum1.to_a
  #=> [["peggy, 1, wolcott farms ", []],
  #    ["sue, 2, blue moon farms", []],
  #    ["eddie, 3, sunrise farms", []]]

The first element generated by enum1 is passed to the block and the block variables are assigned.

values_str, a = enum1.next
  #=> ["peggy, 1, wolcott farms ", []] 
values_str
  #=> "peggy, 1, wolcott farms "
a #=> []

The block calculations are now performed.

b = inner_keys.zip(values_str.split(/,\s+/))
  #=> ["name", "id", "owner"].zip(["peggy", "1", "wolcott farms "])
  #=> [["name", "peggy"], ["id", "1"], ["owner", "wolcott farms "]]
c = b.to_h
  #=> {"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "}
a << c
  #=> [{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "}]

The remaining two elements generated by enum1 are treated similarly, resulting in

h #=> {"pigs"=>[{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
  #             {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
  #             {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}] 

The remaining calculations (for cows) are similar.

@pigs and @cows

If you insist on having those instance variables it is easy to generate them from the hash constructed above.

h = {"pigs"=>[{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
              {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
              {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}],
     "cows"=>[{"name"=>"dee", "id"=>"3", "owner "=>"black hat farms "},
              {"name"=>"sunny", "id"=>"2", "owner "=>"blue moon farms "},
              {"name"=>"bess", "id"=>"4", "owner "=>"wolcott farms"}]} 
h.each { |k,v| instance_variable_set("@#{k}", v) }

We now have:

@pigs
  #=> [{"name"=>"peggy", "id"=>"1", "owner"=>"wolcott farms "},
  #    {"name"=>"sue", "id"=>"2", "owner"=>"blue moon farms"},
  #    {"name"=>"eddie", "id"=>"3", "owner"=>"sunrise farms"}] 
@cows
  #=> [{"name"=>"dee", "id"=>"3", "owner "=>"black hat farms "},
  #    {"name"=>"sunny", "id"=>"2", "owner "=>"blue moon farms "},
  #    {"name"=>"bess", "id"=>"4", "owner "=>"wolcott farms"}] 

Parse Text, Then Assign Using Instance Variables

You can't use local variables, but you can use Object#instance_variable_get and Object#instance_variable_set to do this kind of metaprogramming. For example:

str     = File.read '/tmp/livestock.txt'
records = str.split /\n\n+/
records.map! { |r| r.split /\n/ }
records.map do |r| 
  var    = ?@ << r.shift.strip.delete(?:)
  fields = r.shift.strip.scan /[^,]+/
  hashes = r.map { |e| e.split(?,).flat_map &:strip }.
             map { |e| fields.zip e }.
             map &:to_h
  instance_variable_set var,
    instance_variable_get(var).to_a.push(hashes).flatten!
end;

# The data is now stored correctly in the following instance variables.
@pigs
@cows

Caveat

Note that if @pigs or @cows already exist because you're testing in the REPL, your results may not be what you expect. Make sure you invoke Object#remove_instance_variable , set your variables to nil, or create a new instance of your class between tests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM