Hash not being added to array - ruby

I give up, I have no idea why the hashes I'm creating are not being added to the end of the array. When I pp the hash it is correct, but for some reason the first hash is getting duplicated, while the second hash isn't being added..
The result I'm getting is this:
[{:id=>"36757153479", :quantity=>1, :status=>"new"},
{:id=>"36757153479", :quantity=>1, :status=>"new"}]
#notice that the id is the same
While what I want is this:
[{:id=>"36767751239", :quantity=>1, :status=>"new"},
{:id=>"36757153479", :quantity=>1, :status=>"new"}]
The incoming array looks like this:
me = [{"id"=>36767751239, "quantity"=>1,"vendor"=>"Martha
Stewart", "product_id"=>9707911431, "gift_card"=>false}, {"id"=>36757153479,
"quantity"=>1, "vendor"=>"Naturalizer", "product_id"=>9707504007,
"gift_card"=>false}]
And my code that steps thru it is this:
incoming_cart_array = []
incoming_cart_hash = {}
unless me.nil?
me.each do |product|
incoming_cart_hash[:id] = product['variant_id'].to_s
incoming_cart_hash[:quantity] = product['quantity']
incoming_cart_hash[:status] = "new"
incoming_cart_array << incoming_cart_hash
end
end
I've done this sort of thing 100's of times, but somehow this isn't working. Its probably something right in front of me, I just can't see it.
Thanks

I seemed to be able to solve it as
incoming_cart_array = []
unless me.nil?
me.each do |product|
incoming_cart_hash = {}
incoming_cart_hash[:id] = product['id'].to_s
incoming_cart_hash[:quantity] = product['quantity']
incoming_cart_hash[:status] = "new"
incoming_cart_array << incoming_cart_hash
end
end
However, I cannot seem to find the reason, that it cannot overwrite incoming_cart_hash[:id] when it isn't defined in the same scope.
I'll dig into it, and update my answer if I figure it out!
Edit: My first initial though after a little debugging is, that when the hash isn't a local variable, it's defined (In the Ruby Source, which is C-based), as a pointer to the hash-type. Therefore in the array << hash line, you're inserting a pointer to the hash in the array. When you're running me.each n-times (2 in this case), the hash is updated, thus you'll have n-pointers in the array, all pointing to the same element. The hash which you're updating. It's seen as the same ruby object.
If you're outputting incoming_cart_hash.object_id inside the loop, each time, you'll see that the object_id is the same every time, when the hash-definition is outside the loop. However, when it's inside - defined as a new local variable every time, it'll differ, as it is a new and redefined object every time.
I found a bit of notes about it here: Ruby - Parameters by reference or by value?

Your code creates only one {} ever, so you get an array with n times the same hash.
You must create a new {} for each iteration.
incoming_cart_array = []
unless me.nil?
me.each do |product|
incoming_cart_hash = {}
...
end
end
Pro tipp โ€” best practice in Ruby is to use map to create and array from an array. And you can use literal syntax to create a new hash, and use && to check for the nil case.
incoming_cart_array = me && me.map do | product |
{
id: product['id'].to_s,
quantity: product['quantity'],
status: "new",
}
end

Related

Rails: Accessing a variable where the name of the variable was passed into the function as an argument

#string = "I love long strolls on the beach"
def keyword(string)
#keyword_array = ["ball","sand","spade"]
ball = ["red","green","blue"]
sand = ["beach","playbox","sea"]
spade = ["garden","beach","cards"]
#keyword_array.each do |item|
new_array = item
puts item # on the first loop for example this returns 'ball'
end
end
As you can see in the example above when I iterate through the first loop it returns the string ball (just using the first loop as an example, its the same on all loops) but I want to assign new_array with the value of ["red","green","blue"] from the ball array as I will be passing this array to a function.
So basically instead of assigning the ball array, it assigns the name of the array as a string.
I tried a ton of things including using #{item}, instance_variable_set but nothing works. I have a feeling I do not know the correct terminology for this example so I can't find the right answer.
I had a similar situation where I was passing the method name as a variable and I did this to fix it:
return method("#{name}").call
but that was for a def/method. How would I do it for a variable?
Go easy on me, new to rails :)
I think that what you are trying to accomplish can be done using eval, but be careful about how you use it since this can execute any code within that expression, so be sure you only use it on flows where you have full control (avoid the use of this when user input is part of the eval call).
a = 'pepe'
pepe = 'pepito'
p a
p eval a
The first put will print 'pepe', the second one will print 'pepito' (the value of the pepe variable), you can test that snippet here.
Hope this helps! ๐Ÿ‘
Do the contents of #keyword_array have to be a string?
We can avoid doing tricky things like call or send if we just store the references to the arrays in #keyword_array.
#string = "I love long strolls on the beach"
def keyword(string)
ball = ["red","green","blue"]
sand = ["beach","playbox","sea"]
spade = ["garden","beach","cards"]
#keyword_array = [ball, sand, spade]
#keyword_array.each do |item|
new_array = item
puts item
end
end

Unexpected FrozenError when appending elements via <<

I have a hash containing names and categories:
hash = {
'Dog' => 'Fauna',
'Rose' => 'Flora',
'Cat' => 'Fauna'
}
and I want to reorganize it so that the names are grouped by their corresponding category:
{
'Fauna' => ['Dog', 'Cat'],
'Flora' => ['Rose']
}
I am adding each names via <<:
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = name
end
end
But I am being told that this operation is being performed on a frozen element:
`<<' : Canโ€™t modify frozen string (FrozenError)
I suppose this is because each yields frozen objects. How can I restructure this code so the '.each' doesn't provide frozen variables?
I needed to add the first name to an array and then that array to the hash.
new_hash = Hash.new
hash.each do |name , category|
if new_hash.key?(category)
new_file[category] << name
else
new_hash[category] = [name] # <- must be an array
end
end
How can I restructure this code so the '.each' doesn't provide frozen variables?
Short answer: you can't.
Hash#each doesn't "provide frozen variables".
First off, there is no such thing as a "frozen variable". Variables aren't frozen. Objects are. The distinction between variables and objects is fundamental, not just in Ruby but in any programming language (and in fact pretty much everywhere else, too). If I have a sticker with the name "Seamus" on it, then this sticker is not you. It is simply a label that refers to you.
Secondly, Hash#each doesn't provide "variables". In fact, it doesn't provide anything that is not in the hash already. It simply yields the objects that are already in the hash.
Note that, in order to avoid confusion and bugs, strings are automatically frozen when used as keys. So, you can't modify string keys. You can either make sure they are correct from the beginning, or you can create a new hash with new string keys. (You can also add the new keys to the existing hash and delete the old keys, but that is a lot of complexity for little gain.)

I an getting an "Undefined method 'new' for.... (A number that changes each time)"

I made a simple program with a single method and I'm trying to test it, but I keep getting this weird error, and I have no idea why it keeps happening.
Here's my code for the only method I wrote:
def make_database(lines)
i = 0
foods = hash.new()
while i < lines.length do
lines[i] = lines[i].chomp()
words = lines[i].split(',')
if(words[1].casecmp("b") == 0)
foods[words[0]] = words[3]
end
end
return foods
end
And then here's what I have for calling the method (Inside the same program).
if __FILE__ == $PROGRAM_NAME
lines = []
$stdin.each { |line| lines << line}
foods = make_database(lines).new
puts foods
end
I am painfully confused, especially since it gives me a different random number for each "Undefined method 'new' for (Random number)".
It's a simple mistake. hash calls a method on the current object that returns a number used by the Hash structure for indexing entries, where Hash is the hash class you're probably intending:
foods = Hash.new()
Or more succinctly:
foods = { }
It's ideal to use { } in place of Hash.new unless you need to specify things like defaults, as is the case with:
Hash.new(0)
Where all values are initialized to 0 by default. This can be useful when creating simple counters.
Ruby classes are identified by leading capital letters to avoid confusion like this. Once you get used to the syntax you'll have an easier time spotting mistakes like that.
Note that when writing Ruby code you will almost always omit braces/brackets on empty argument lists. That is x() is expressed simply as x. This keeps code more readable, especially when chaining, like x.y.z instead of x().y().z()
Other things to note include being able to read in all lines with readlines instead of what you have there where you manually compose it. Try:
make_database($stdin.readlines.map(&:chomp))
A more aggressive refactoring of your code looks like this:
def make_database(lines)
# Define a Hash based on key/value pairs in an Array...
Hash[
# ...where these pairs are based on the input lines...
lines.map do |line|
# ...which have comma-separated components.
line.split(',')
end.reject do |key, flag, _, value|
# Pick out only those that have the right flag.
flag.downcase == 'b'
end.map do |key, flag, _, value|
# Convert to a simple key/value pair array
[ key, value ]
end
]
end
That might be a little hard to follow, but once you get the hang of chaining together a series of otherwise simple operations your Ruby code will be a lot more flexible and far easier to read.

ruby: search an multi-dimensional array for a value and update if needed

Trying to look through an array and see if a particular value is set and if it is, update the numbers attached to it.
Example:
test = [['test',1,2],['watch',1,2],['fish',1,2]]
So I'd like to search this array for 'test' - if it exists, amend the values '1,2', if it doesn't exist, just add the new search term into the array.
New to ruby and having trouble searching inside a multi-dimensional array and getting the key back
I'd go for the hash method suggested in the comments, but if you're really wanting to store your data in the multidimensional array like that I suppose you could do something like:
search_term = "test"
search_item= nil
test.each do |item|
if item.include? search_term
search_item = item
end
end
if search_item.nil?
test << [search_term]
else
search_item << [1,2]
end
I think that would do it (although I'm a little fuzzy on what you were wanting to do after you found the item).

Ruby arrays and non-incremental indexes

I have an array in ruby, and I am setting the index to id of object like below.
My first question is:
This code works:
#array = Array.new(#objects.size)
for i in 0...#objects.size
#array[i] = #objects[i].value
end
but when I do:
#array[#objects[i].id] = #objects[i].value
it says:
undefined method [] for nil::NilClass
I tried putting 100 or 1000 instead of i to make sure it's not about "index out of range", but those worked, I tried converting id to int by using to_i even though it should already be an int, but it still doesn't work. I don't get it.
My second question is:
If I get to make the ids work, does saying Array.new(#objects.size) become usless?
I am not using indexes 0 to size but IDs, so what is happening? Is it initializing indexes 0...size to nil or is it just creating a space for up to x objects?
EDIT:
So I've been told it is better to use Hash for this, and I agree, But I still seem to have the same error in the same situation (just changed Array.new(#objects.size)toHash.new)
Thats not how Arrays work in Ruby. You can however use a hash to do this, and look them up using the method you want:
#lookup_hash = Hash.new
for i in 0...#objects.size
#lookup_hash[#objects[i].id] = #objects[i].value
end
Now you can do:
#lookup_hash[#some_object.id]
And it will return that object's value as you have stored it.
Additional Info
You could also rewrite your loop like this, since you dont need the index anymore:
#lookup_hash = Hash.new
#objects.each do |obj|
#lookup_hash[obj.id] = obj.value
end
A little bit more readable in my opinion.
Your're trying to use an array like a hash. Try this:
Hash[#objects.map{|o| [o.id, o.value] }]
Take a look at the Array and Hash documentations.
#array = #objects.map { |obj| obj.value }
You can, but you don't need to specify the size when creating an array. Anyway, try to use the functional capabilities of Ruby (map, select, inject) instead of C-like imperative loops.
You could use map to do this in a rubyish way:
#array = #objects.map { |o| o.value }

Resources