Ruby arrays and non-incremental indexes - ruby

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 }

Related

Why isn't the following code working the way it should?

array = ["car","carrs"]
array.each { |x|
x.capitalize
}
I have tried doing with do too by removing the curly braces and adding do after .each, I have also tried for each in array, but that didnt work too. Am i doing something wrong because nothing gets capitalized?
String#capitalize returns a copy of the object with the first letter capitalized. What you're basically doing is looping through your array and generating new copies of the strings, but then immediately throwing them away.
You have a couple of ways to approach this:
You can use #map rather than #each to take each result of your loop block body and collect it into a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
Or, if you actually want to mutate the original strings, use String#capitalize! rather than capitalize, which mutates the input object, rather than returning a new object:
array = ["car","carrs"]
array.each { |x| x.capitalize! }
While it may seem tempting to use the mutative version, it is frequently a good idea to use non-mutative methods to produce transformations of your data, so you don't lose your original input data. Mutate-in-place can introduce subtle bugs by making the state of the data harder to reason about.
You have to understand the difference between map vs each. You can read it here.
For those who don't want to read that:
Each is like a more primitive version of map. It gives you every element so you can work with it, but it doesn’t collect the results. Each always returns the original, unchanged object. While map does the same thing, but. It returns a new array with the transformed elements.
So, you have to use map in order to return a new array:
array = ["car","carrs"]
capitalized_array = array.map { |x| x.capitalize }
# or
array = ["car","carrs"]
array.map! { |x| x.capitalize }
Now, what is the different between map and map!? We need to read the documentation
map invokes the given block once for each element of self. Creates a new array containing the values returned by the block. While map! invokes the given block once for each element of self, replacing the element with the value returned by the block.

Hash not being added to array

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

Ruby pushing to a hash

I have a two part question and I apologize in advance if it is confusing at all. I'm trying to put user input into an empty hash. I know with an array you use the << to push the info to it. Is there a hash equivalent to this?
2nd part: Say I was just looping them the same question until a condition is met. The user input is going to be the value. Is there a way/method to make the key automatically change per the user input? So it would look something like:
{str1 => "example string", str2 => "example string2", str3 => "example string3"}
Or is there a way to have ruby assign a key on its own?
Sorry again if the second part is confusing. I know an array would be better but the little challenge I am working is asking for a hash.
Another way to add element to ruby hash store(key, value)
hash = {}
hash.store("first", 42)
hash #=> {"first"=>42}
With an array you use << to push a single element.
With a hash you are tracking not one element but two (both the key and value).
So for example:
my_key = "foo"
my_val = "bar"
my_hash = {}
my_hash[key] = val
Sure, you can do this in a loop.
I would recommend RubyMonk to learn more about this but their website is down. So I can recommend this gist which shows some examples or simply read the Hash section of any ruby tutorial.
Here are the two ways to add to a Hash
hash[str1] = "example string"
hash.merge!(str1 => "example string")
If you don't care about indexing on a key, as a Hash is intrinsically a key/value store, you probably want a Set:
require 'set'
set = Set.new
set << gets.chomp
A set is like a keyless hash, it's an un-ordered collection of things but with the side benefit that lookups for elements in the set are quick and they're also automatically uniqued, adding the same thing twice has no effect.
The alternative here is to put something in the Hash with the value as the key and any other value as a placeholder:
values = { }
values[input.gets] = true
This is like a Set but is probably less efficient to use if you don't care about values.
Ok, it isn't array so '<<' can't be work.
You should use this:
your_hash = {}
hash_key = "x"
hash_value = "y"
your_hash[:hash_key] = hash_value
It's all.

Ruby Iterator - define a method that accepts an array and a string

I want to define a method that accept an array, and a string, then it should find all the strings in the array that starts with the string that was supplied: example array["Jamaica","Japan","USA","China"]; if the string supplied is Ja, then it should return Jamaica and Japan
Try using keep_if and regex :
["Jamaica","Japan","USA","China"].keep_if { |c| c =~ /^ja/i }
# returns ["Jamaica", "Japan"]
It's a static example. To create the regex dynamically, do Regexp.new("^#{your_var}", true)
The most important methods for an Array are not found in the documentation for Array, but on that for Enumerable. You are searching for a method which finds all elements which comply to some condition.
This condition was that the string starts with some characters. Wouldn't it be nice if a string object had a method for that?
This is overkill but it would work quickly for huge arrays. Lucky for you, I just yesterday stumbled upon a new high speed trie library for ruby called TRIEZ. Run gem install triez then from their example switched up a little bit:
require 'triez'
countries = ["Jamaica","Japan","USA","China"]
t = Triez.new
countries.each do |word|
t[word] = 1
end
ja_countries = []
t.search_with_prefix 'Ja' do |suffix|
ja_countries += "Ja#{suffix}"
end
It's implemented in C so I bet it's fast as hell on huge arrays.
You can do this using many different things, if you don' want to modify the array you can use the select method from Array Class.
["Jamaica","Japan","USA","China"].select{|item| item.match("Ja")}
The Array will be intact.

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources