5

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
  • I think you want a Hash, not an array. This way you can reference each book (as keys) and the weekly sales of the books as the value. Commented Dec 1, 2009 at 22:14
  • yes, this does seem to be the consensus among the answers. Commented Dec 1, 2009 at 22:18

6 Answers 6

7
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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.
2

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

Ruby Docs->Array->push

Comments

1
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

Comments

1

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

Comments

0

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

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.