Ruby modify hash in "each" block. Pass-by-value subtlety ? [duplicate] - ruby

This question already has an answer here:
Ruby - Parameters by reference or by value? [duplicate]
(1 answer)
Closed 8 years ago.
According to this question, ruby is strictly pass-by-value. I came across a case though, which consists of modifying a hash like so:
h = {"one" => ["un", "ein"], "two"=> ["deux", "zwei"]}
h.each { |k,v| v << "overriden"}
resulting in:
{"one"=>["un", "ein", "overriden"], "two"=>["deux", "zwei", "overriden"]}
However, the following behaves differently:
h = {"one" => "un", "two"=> "deux"}
h.each { |k,v| v = "overriden"}
resulting in:
{"one"=>"un", "two"=>"deux"}
How could I have predicted this?

If you read the next paragraph in the answer you linked, it says this:
Ruby doesn't have any concept of a pure, non-reference value, so you certainly can't pass one to a method. Variables are always references to objects. In order to get an object that won't change out from under you, you need to dup or clone the object you're passed, thus giving an object that nobody else has a reference to.
The << operator modifies an array in place. You have a reference to the array, the hash has a reference to that array, and when the array changes, they both point at the same array.
When you use = you are assigning a value to a variable. In a way, you are telling the variable to refer to something else, instead of doing something to the thing the variable references.

Related

Pointer in Ruby

I just solved some tasks about linked lists using Ruby. It was very interesting, but it requires a couple of new lines. Because if I pass head in some function, and change the head of the list, I have to return new head from method and reassign it to the variable.
Because if I have a variable and I pass it to method, reassign a inside, outside a dose not changes:
it "dose not changes if reassign variable in method" do
a = [1,2]
def reasign array
array = [1]
array
end
assert_equal [1], reasign(a)
assert_equal [1,2], a
end
Of course I able to warp head of list in Hash or Array and save this Hash thus when I change something in object. The variable outside still pointing on object. And this works. But again requires couple of lines.
it "method changes the data into a object" do
a = [1,2]
def change_object object
object.push 3
object
end
assert_equal [1,2,3], change_object(a)
assert_equal [1,2,3], a
end
Is there way in Ruby to use C-like pointers or PHP-like references?
All ruby variable references are essentially pointers (but not pointers-to-pointers), in C parlance.
You can mutate an object (assuming it's not immutable), and all variables that reference it will thus be pointing at the same (now mutated) object. But the only way to change which object a variable is referring to is with direct assignment to that variable -- and each variable is a separate reference; you can't alias a single reference with two names.

Dynamically Modify Ruby Instance Variable in Array

I have an array ["moniker", #moniker] where the moniker can be any one of around 100 instance variables and its string representation. I want to change what the instance variable located at index 1 is referencing (not that data itself, which may very well be immutable). Just doing array[1] = newData doesn't work because it just changes whats in the array. I know this would be simple in C, but I'm struggling to find a way to do this in Ruby.
Your struggle is because you are thinking like a C programmer, where you have access to the underlying pointers, and where everything is mutable. In C, The array would store a pointer to a mutable integer, and you could change the integer whenever you want. In Ruby, every variable is a reference to an object, and numbers are immutable objects. So, #moniker is a reference to an object, the integer 4. When you create the array, you copy that reference into the array, so now the integer 4 has two references: One from #moniker, and one from the array. As you have found, changing the reference in the array does not change the reference named #moniker--it still refers to the object 4.
"Box" a reference in an array
This is not really a Ruby way of doing things. I'm showing it because it might help to illustrate how Ruby works with references.
You can box a reference in an array:
#moniker = [4]
a = ["moniker", #moniker]
This requires you to deference the array when you want access to the underlying object:
#moniker.first
a[1].first
But now you can change the underlying integer in #moniker and the array will see the change:
#moniker[0] = 42
p a[1].first # => 42
Encapsulate the number in a mutable object.
Being an object oriented language, you might encapsulate that number in a mutable object.
class Moniker
attr_accessor :value
def initialize(value)
#value = value
end
end
(attr_accessor :value builds reader and writer methods for the instance variable #value).
#moniker = Moniker.new(4)
a = ["monikier", #moniker]
#moniker.value = 42
p a[1].value # => 42
You would obviously chose a better name than "value." I couldn't because I don't know what the value represents.
Why these two solutions work
This was a comment by Jörg W Mittag, but it deserves to be part of the answer:
It may seem obvious, but I wanted to mention it explicitly: the two solutions are the same solution. The first uses an already existing class with generic semantics, the the second defines a new class with precise semantics for the specific encapsulated value. But in both cases, it's about wrapping the immutable value in a mutable value and mutating the "outer" value.
#moniker never got into the array but its value did.
In IRB:
#moniker = 4
a = ["moniker", #moniker]
=> ["moniker", 4]
You're just working with the value in the array anyway so just change it and you're good to go:
a[1] = 5
a
=> ["moniker", 5]
You might want to consider a hash:
h = {:moniker => #moniker}
=> {:moniker=>4}
h[:moniker] = 5
h
=> {:moniker=>5}

Ruby array changes by changing a 'copy' of one of its elements

I'm trying to confirm whether my understanding is correct of these six lines of code:
string="this is a sentence"
words=string.split
first_word=words[0]
first_word[0]=first_word[0].upcase
out=words.join(" ")
puts(out)
which prints "This is a sentence" (with the first letter capitalized).
It would appear that changing the "first_word" string, which is defined as the first element of the "words" array, also changes the original "words" array. Is this indeed Ruby's default behavior? Does it not make it more difficult to track where in the code changes to the array take place?
You just need need to distinguish between a variable and an object. Your string is an object. first_word is a variable.
Look for example
a = "hello"
b = a
c = b
now all variables contain the same object, a string with the value "hello". We say they reference the object. No copy is made.
a[0] = 'H'
This changes the first character of the object, a string which now has the value "Hello". Both b and c contain the same, now changed object.
a = "different"
This assigns a new object to the variable a. b and c still hold the original object.
Is this Rubys default behaviour? yes. And it also works like this in many other programming languages.
Does it make it difficult to track changes? Sometimes.
If you takes an element from an array (like your first_word), you need to know:
If you change the object itself, no matter how you access it,
all variables will still hold your object, which just happened to be changed.
But if you replace the object in the array, like words[0] = "That", then all your other variables will still hold the original object.
This behavior is caused by how ruby does pass-by-value and pass-by-reference.
This is probably one of the more confusing parts of Ruby. It is well accepted that Ruby is a pass-by-value, high level programming language. Unfortunately, this is slightly incorrect, and you have found yourself a perfect example. Ruby does pass-by-value, however, most values in ruby are references. When Ruby does an assignment of a simple datatypes, integers, floats, strings, it will create a new object. However, when assigning objects such as arrays and hashes, you are creating references.
original_hash = {name: "schylar"}
reference_hash = original_hash
reference_hash[:name] = "!schylar"
original_hash #=> "!schylar"
original_array = [1,2]
reference_array = original_array
reference_array[0] = 3
reference_array #=> [3,2]
original_fixnum = 1
new_object_fixnum = original_fixnum
new_object_fixnum = 2
original_fixnum #=> 1
original_string = "Schylar"
new_object_string = original_string
new_object_string = "!Schylar"
original_string #=> "Schylar'
If you find yourself needing to copy by value, you may re-think the design. A common way to pass-by-value complex datatypes is using the Marshal methods.
a = {name: "Schylar"}
b = Marshal.load(Marshal.dump(a))
b[:name] = "!!!Schylar"
a #=> {:name => "Schylar"}

undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)

I'm trying to return a list of values based on user defined arguments, from hashes defined in the local environment.
def my_method *args
#initialize accumulator
accumulator = Hash.new(0)
#define hashes in local environment
foo=Hash["key1"=>["var1","var2"],"key2"=>["var3","var4","var5"]]
bar=Hash["key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"]]
baz=Hash["key6"=>["var13","var14","var15","var16"]]
#iterate over args and build accumulator
args.each do |x|
if foo.has_key?(x)
accumulator=foo.assoc(x)
elsif bar.has_key?(x)
accumulator=bar.assoc(x)
elsif baz.has_key?(x)
accumulator=baz.assoc(x)
else
puts "invalid input"
end
end
#convert accumulator to list, and return value
return accumulator = accumulator.to_a {|k,v| [k].product(v).flatten}
end
The user is to call the method with arguments that are keywords, and the function to return a list of values associated with each keyword received.
For instance
> my_method(key5,key6,key1)
=> ["var10","var11","var12","var13","var14","var15","var16","var1","var2"]
The output can be in any order. I received the following error when I tried to run the code:
undefined method `assoc' for #<Hash:0x10f591518> (NoMethodError)
Please would you point me how to troubleshoot this? In Terminal assoc performs exactly how I expect it to:
> foo.assoc("key1")
=> ["var1","var2"]
I'm guessing you're coming to Ruby from some other language, as there is a lot of unnecessary cruft in this method. Furthermore, it won't return what you expect for a variety of reasons.
`accumulator = Hash.new(0)`
This is unnecessary, as (1), you're expecting an array to be returned, and (2), you don't need to pre-initialize variables in ruby.
The Hash[...] syntax is unconventional in this context, and is typically used to convert some other enumerable (usually an array) into a hash, as in Hash[1,2,3,4] #=> { 1 => 2, 3 => 4}. When you're defining a hash, you can just use the curly brackets { ... }.
For every iteration of args, you're assigning accumulator to the result of the hash lookup instead of accumulating values (which, based on your example output, is what you need to do). Instead, you should be looking at various array concatenation methods like push, +=, <<, etc.
As it looks like you don't need the keys in the result, assoc is probably overkill. You would be better served with fetch or simple bracket lookup (hash[key]).
Finally, while you can call any method in Ruby with a block, as you've done with to_a, unless the method specifically yields a value to the block, Ruby will ignore it, so [k].product(v).flatten isn't actually doing anything.
I don't mean to be too critical - Ruby's syntax is extremely flexible but also relatively compact compared to other languages, which means it's easy to take it too far and end up with hard to understand and hard to maintain methods.
There is another side effect of how your method is constructed wherein the accumulator will only collect the values from the first hash that has a particular key, even if more than one hash has that key. Since I don't know if that's intentional or not, I'll preserve this functionality.
Here is a version of your method that returns what you expect:
def my_method(*args)
foo = { "key1"=>["var1","var2"],"key2"=>["var3","var4","var5"] }
bar = { "key3"=>["var6"],"key4"=>["var7","var8","var9"],"key5"=>["var10","var11","var12"] }
baz = { "key6"=>["var13","var14","var15","var16"] }
merged = [foo, bar, baz].reverse.inject({}, :merge)
args.inject([]) do |array, key|
array += Array(merged[key])
end
end
In general, I wouldn't define a method with built-in data, but I'm going to leave it in to be closer to your original method. Hash#merge combines two hashes and overwrites any duplicate keys in the original hash with those in the argument hash. The Array() call coerces an array even when the key is not present, so you don't need to explicitly handle that error.
I would encourage you to look up the inject method - it's quite versatile and is useful in many situations. inject uses its own accumulator variable (optionally defined as an argument) which is yielded to the block as the first block parameter.

RubyKoans - Confusing hash example [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How does shovel (<<) operator work in Ruby Hashes?
I'm making my way through the RubyKoans, and one of the examples regarding hashes is really confusing me. The example is in the about_hashes.rb file. Here is the code that is confusing me:
hash = Hash.new([])
hash[:one] << "uno"
hash[:two] << "dos"
assert_equal __, hash[:one]
For those unfamiliar with RubyKoans, you are supposed to fill in the correct value for the double-underscore.
In this example, I see that the variable named hash is using the Hash object constructor, which replaces the default value of nil with an empty Array. Then, two items are added to hash, each with one array element, using the << Ruby operator. I would expect that hash[:one] would return an array of value ["uno"], but Ruby is telling me that it actually is ["uno", "dos"]. What gives?
The Hash constructor is using a reference to the same array as the default value, so all hash values will actually be initialized to the same array by default until you use the []= operator on a hash key (e.g., hash[:one] = some_new_object). This is why you see both strings in the array.
According to this answer to another question, you need to use a block form for the Hash constructor. That block will be executed for every new hash key:
hash = Hash.new { |h,k| h[k] = [] }
As for the colon-prefixed tokens, those are literals for what are called symbols. Symbols are much like strings, but have a few crucial differences. One is that the same symbol value will always reference the same object in memory (which is not necessarily true of strings). Symbols have other characteristics that make them perform slightly better than constant strings. Symbols are otherwise very much like string literals.

Resources