Consider the following code:
I receive an object from datamapper which contains Values from my select:
user = User.first()
puts user.name
# John
puts user.surname
# Doe
puts user.age
# 42
In a user defined Array I have an Order for these Values to be displayed
dataordering = ["age", "surname", "name"]
So how do I get my values ordered as in my Array?
dataordering.each do |sequence|
puts user.sequence
# this, of course, fails
end
I don't want to use eval(). nope.
Maybe there's even a better way to store an ordering of values?
You can pick values from record this way:
user_attributes = user.attributes
dataordering.each do |attribute|
puts user_attributes[attribute.to_sym]
end
Or use send method:
dataordering.each do |attribute|
puts user.send attribute.to_sym
end
As an ordering solution, I can offer you this code:
dataordering.map { |attr| user.send attribute.to_sym }
Related
#session is formatted as
[
['time','action','user'],
['time','action','user'],
...
]
and I'm trying to create an array that has those array elements but as hashes of {:time=>"time, :action=>"action", :user=>"user"}. The puts sessions line outputs each line as I desire, but when I try to capture those hashes into sessions_array I receive an array of only one hash repeated many times and not the unique hashes that puts is outputting.
sessions = Hash.new
sessions_array = Array.new
#session.each_with_index { |element, index|
next_element = #session[index+1]
sessions[:time] = element[0]
sessions[:action] = element[1]
sessions[:user] = element[2]
sessions_array << sessions
puts sessions
}
puts sessions_array
Create sessions inside of the each_with_index block instead of outside:
sessions_array = []
#session.each do |element|
sessions = {
time: element[0],
action: element[1],
user: element[2],
}
sessions_array << sessions
end
puts sessions_array
However, this can be done much more succinctly. When you're turning an array into another array with the same number of elements you almost always want to use map. Also, in a Ruby block you can extract the elements from an array by specifying multiple names in its arguments (|foo, bar, ...|).
This code is equivalent to the above:
sessions_array = #session.map do |time, action, user|
{ time: time, action: action, user: user }
end
You can see both of these snippets in action on repl.it here: https://repl.it/#jrunning/NavyImmaculateShockwave
Perhaps you are looking for something like the following.
Code
def hashify(data, keys)
data.map { |row| keys.zip(row).to_h }
end
Example
data = [
%w| 11:00 pummel Billy-Bob |,
%w| 02:00 maim Trixie |,
%w| 19:00 kill Bill |
]
#=> [["11:00", "pummel", "Billy-Bob"],
# ["02:00", "maim", "Trixie"],
# ["19:00", "kill", "Bill"]]
keys = [:time, :action, :user]
hashify(data, keys)
#=> [{:time=>"11:00", :action=>"pummel", :user=>"Billy-Bob"},
# {:time=>"02:00", :action=>"maim", :user=>"Trixie"},
# {:time=>"19:00", :action=>"kill", :user=>"Bill"}]
I have chosen to make data and keys arguments of the method so that those parameters can be modified without affecting the method itself.
Note that each of the three elements of:
data.map { |row| keys.zip(row) }
#=> [[[:time, "11:00"], [:action, "pummel"], [:user, "Billy-Bob"]],
# [[:time, "02:00"], [:action, "maim"], [:user, "Trixie"]],
# [[:time, "19:00"], [:action, "kill"], [:user, "Bill"]]]
is converted to a hash using the method Array#to_h. See also Array#zip.
I have a small method that will be called on a initialized 'Order' class object, to get user input, asking them what item they would like to add from a menu to their order. The method is:
def add_to_order
#order = []
while true
puts "Item:"
#item = gets.chomp
break if #item.empty?
puts "Qty?"
#qty = gets.chomp
#order << [#item, #qty]
end
end
I have the menu stored in an array, as a 'hash' of objects, created previously from initializing new objects from a class called 'Dish'. These were added to an instance variable called #menu, which is:
#menu=
[[#<Dish:0x007fa1f8953b10 #dish={:name=>"Chicken Stew", :price=>4.5}>,
#<Dish:0x007fa1f883daf0 #dish={:name=>"Beef & Ale Pie", :price=>5.0}>,
#<Dish:0x007fa1f89d86f8 #dish={:name=>"Chicken & Leek Pie", :price=>4.0}>,
#<Dish:0x007fa1f8b5b048 #dish={:name=>"Vegetable Pie", :price=>3.5}>,
#<Dish:0x007fa1f8d03378 #dish={:name=>"Fish Pie", :price=>5.5}>,
#<Dish:0x007fa1f8df1c08 #dish={:name=>"Chips", :price=>2.0}>,
#<Dish:0x007fa1f9993a70 #dish={:name=>"Mushy Peas", :price=>1.5}>]]
My question is - when getting user input, how can I check whether or not the item they type in, is on the list contained in the #menu, when this #menu is not a normal array, but if more an 'array of object information? I tried the following but it did not work.
def add_to_order
#order = []
while true
raise 'ERROR!' if #menu[0].include?(#item)
.......
end
You could use find (or its alias detect) that takes an block:
#menu[0].find { |dish| dish.name == #item }
This would return the first dish from the menu which name matches #item or nil if there wasn't a matching dish.
Hint: You might want to consider storing your menu in a hash instead of an array, because a hash would allow faster lookups.
I have an array with words that I will use as keys. I iterate trough this array and put the words as keys and user input as value simplified like this:
input = Hash.new()
array.each do |a|
input[a] = gets.strip
end
for example array ["one", "twoo", "three"]
and the user inputs first: three
I want to pass {"one" => "three"}
So the current key and value
Now I want to pass this "line" of the array to a method how do I do this ?
It is still unclear what you want to achieve. The way I see it, you can mean one of three things:
"I want to pass the key and value after the user enters them to a method"
array.each do |key|
input[key] = gets.strip
my_method(key, input[key])
end
A possible spin on that is "But I want to pass them as a single hash"
array.each do |key|
input[key] = gets.strip
my_method({key, input[key]})
end
"I want to pass the entire hash after the user fills all the values"
array.each do |key|
input[key] = gets.strip
end
my_method(input)
Just call that method and pass your "line":
input = Hash.new()
array.each do |a|
input[a] = gets.strip
# Send a line that user has inputed
your_method(input[a])
# Send key and the line that user has inputed
your_method(a, input[a])
end
I got an impression that you are not trying to pass the entire hash at a time. If so, here is the one line answer
arr.each{|x| my_method({x => gets.strip})}
If I am wrong and you want to pass it at a time. Here is the one line answer
my_method(Hash[arr.zip(arr.map{gets.strip})])
I'm trying to combine large data sets that I've filtered out from a single hash. I've tried various things such as merge, but don't seem to be able to get the data to combine the way I'm envisioning. Here are the things I'm trying to combine:
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '','=========GET IDs'
story_ids = print_data['Comments']['Comment'].map {|entry| entry['General']} #filter for story ids and story name
puts story_ids
puts '','=========GET COMMENTS'
comment_description = print_data['Comments']['Comment'].map {|words| words['Description']} #get all comments, these are in the same order as the story ids
puts comment_description
Ultimately what I would like it to look like is:
story_id 1 + comment_description 1
story_id 2 + comment_description 2
etc.
Any help would be greatly appreciated.
I ended up realizing that the hash had some other nested structures I could use. In this example I use a nested hash, then store it as an array (I ultimately need this for other work) and then output.
puts '','=========GET INFO'
print_data = targetprocess.comments_with_ids #get the hash
puts print_data #show the hash for verification
puts '=========COMPLETE', ''
#=========HASH OF USEFUL DATA
results = {}
print_data['Comments']['Comment'].each{|entry|
results[entry['Id'].chomp] = {:parent_id => entry['General']['Id'].chomp, :description => entry['Description'].chomp}}
#=========STORE HASH AS AN ARRAY
csv_array = []
results.each{|key,value|
csv_array << [key, value[:parent_id], value[:description]]
#=======FRIENDLY OUTPUT
puts "Story_Id #{value[:parent_id]}, Comment_Id #{key}, Comment #{value[:description]}"}
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?
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.
The "push" command will append items to the end of an array.
Ruby Docs->Array->push
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
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
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
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