tags:

views:

267

answers:

6

Is there a way to dynamically create arrays in Ruby? For example, let's say I wanted to loop through an array of books as input by a user:

books = gets.chomp

The user inputs:

"The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five, 
The Adventures of Huckleberry Finn"

I turn this into an array:

books_array = books.split(", ")

Now, for each book the user input, I'd like to Ruby to create an array. Pseudo-code to do that:

 x = 0

 books_array.count.times do
    x += 1
    puts "Please input weekly sales of #{books_array[x]} separated by a comma."
    weekly_sales = gets.chomp.split(",")
 end

Obviously this doesn't work. It would just re-define weekly_sales over and over again. Is there a way to achieve what I'm after, and with each loop of the .times method create a new array?

+2  A: 
weekly_sales = {}
puts 'Please enter a list of books'
book_list = gets.chomp
books = book_list.split(',')

books.each do |book|
  puts "Please input weekly sales of #{book} separated by a comma."
  weekly_sales[book] = gets.chomp.split(',') 
end

In ruby, there is a concept of a hash, which is a key/value pair. In this case, weekly_sales is the hash, we are using the book name as the key, and the array as the value.

A small change I made to your code is instead of doing books.count.times to define the loop and then dereference array elements with the counter, each is a much nicer way to iterate through a collection.

Matt Briggs
this makes sense. i knew there must be a different way of looking at this problem that i wasn't quite getting.this works, and is a good base to build out other aspects of the script.
michaelmichael
+2  A: 

The "push" command will append items to the end of an array.

Ruby Docs->Array->push

Derek
A: 

Are you happy to end up with an array of arrays? In which this might be useful:

book_sales = books_array.collect do |book|
  puts "Please input weekly sales of #{books_array[0]} separated by a comma."
  gets.chomp.split(",").collect{ |s| s.to_i }
end

Looking at it, you might prefer a hash, keyed by book. Something like this:

book_sales = books_array.inject({}) do |hash, book|
  puts "Please input weekly sales of #{books_array[0]} separated by a comma."
  weekly_sales = gets.chomp.split(",").collect{ |s| s.to_i }
  hash[book] = weekly_sales
end
Mike Woodhouse
A: 

This solution assumes that there will never be a duplicate book title. I figure that is pretty safe, yes?

input = "A list of words"
hash = {}
input.split(/\s+/).collect { |word| hash[word] = [] }

# Now do whatever with each entry
hash.each do |word,ary|
   ary << ...
end
ezpz
A: 
result = "The Great Gatsby, Crime and Punishment, Dracula, Fahrenheit 451,
  Pride and Prejudice, Sense and Sensibility, Slaughterhouse-Five, 
  The Adventures of Huckleberry Finn".split(/,\s*/).map do |b|
    puts "Please input weekly sales of #{b} separated by a comma."
    gets.chomp.split(',') # .map { |e| e.to_i }
end
p result

Remove the comment if you would like the input strings converted to numbers

DigitalRoss
+1  A: 

One way or another you need a more powerful data structure.

Your post gravitates toward the idea that weekly_sales would be an array paralleling the books array. The drawback of this approach is that you have to maintain the parallelism of these two arrays yourself.

A somewhat better solution is to use the book title as a key to hash of arrays, as several answers have suggested. For example: weekly_sales['Fahrenheit 451'] would hold an array of sales data for that book. This approach hinges on the uniqueness of the book titles and has other drawbacks.

A more robust approach, which you might want to consider, is to bundle together each book's info into one package.

At the simplest end of the spectrum would be a list of hashes. Each book would be a self-contained unit along these lines:

books = [
    {
        'title' => 'Fahrenheit 451',
        'sales' => [1,2,3],
    },
    {
        'title' => 'Slaughterhouse-Five',
        'sales' => [123,456],
    },
]

puts books[1]['title']

At the other end of the spectrum would be to create a proper Book class.

And an intermediate approach would be to use a Struct (or an OpenStruct), which occupies a middle ground between hashes and full-blown objects. For example:

# Define the attributes that a Book will have.
Book = Struct.new(:title, :weekly_sales)
books = []

# Simulate some user input.
books_raw_input = "Fahrenheit 451,Slaughterhouse-Five\n"
sales_raw_input = ['1,2,3', '44,55,66,77']
books_raw_input.chomp.split(',').each do |t|
    ws = sales_raw_input.shift.split(",")
    # Create a new Book.
    books.push Book.new(t, ws)        
end

# Now each book is a handy bundle of information.
books.each do |b|
    puts b.title
    puts b.weekly_sales.join(', ')
end
FM