Ruby passing arguments: difference between Array and Set - ruby

I'm trying to put an array to another existing array and moreover to put all its items to an existing set. Here's the minimal example:
require "set"
def add(myarr, bigarr, myset)
bigarr << myarr
myset |= Set.new(myarr)
end
bigarr = []
myset = Set.new
add([1, 2], bigarr, myset)
Which yields bigarr = [1, 2] .. OK, but myset = {} .. is empty. I know little about passing arguments in Ruby (should be by-value) -- in case of array the value should be a reference to its content, then I have no clue what could be the value of set.
The questions are:
What is the substantial difference between Array and Set which causes this behavior?
Is there any way to force Ruby pass-by-reference or is there a different recommended way how to solve the problem with referencing?
Thanks in advance!

This doesn't have anything to do with the difference between arrays and sets. You're modifying the array with the << method, but you're reassigning the myset variable with the |= operator. You never modify the set you passed in. What you want is probably myset.merge(myarr).

The problem here actually comes from this particular line:
myset |= Set.new(myarr)
Here you're creating new object on old variable name. You're replacing one pointer with another, however this only modifies local copy of it. Original object will still exists in memory and outside function, pointer will point to old object (empty set) (tbh: I would not really encourage this kind of writing in ruby with side effects).
If you change it to
require "set"
def add(myarr, bigarr, myset)
bigarr << myarr
myset.add(myarr)
end
bigarr = []
myset = Set.new
add([1, 2], bigarr, myset)
It's working properly - because you modify existing object and don't create new one.
There is great answer that goes more thoroughly into this, right here: https://stackoverflow.com/a/16464640/1975116

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"}

Reference to array cell in Ruby?

Can I have a reference to an array cell in Ruby? In C++, I can do something like:
int& ref = arr[x][y];
and later work with the variable ref without the need of typing the whole arr[x][y].
I want to do this as I need to access one and the same cell multiple times throughout a function (I'm doing memoization) and typing unnecessary indexes may only lead to errors.
All values in ruby are references, so this is certainly possible, but with some important limitations. One caveat is that ruby doesn't DIRECTLY support multidimensional arrays, but you can implement one as an array of arrays or as a hash keyed by tuples.
You can achieve this in cases where the value at (x, y) has already been set by assigning to the value at the given coordinates. If no value currently exists at that location, then you must initialize that value before you can have a reference to it:
# if x and y are indices and a is your "multidimensional array"
a[x][y] = 'First Value' # Initial value at (x, y)
ref = a[x][y] # take a reference to the value referenced by a[x][y]
ref.gsub! 'First', 'Second'
a[x][y] # => 'Second Value'
Keep in mind that the assignment operator in ruby generally means "make the reference on the left side refer to the value on the right". This means that if you use the assignment operator on your reference, then you're actually making it refer to a new value:
a[x][y] = 1 # Initialize value with 1
ref = a[x][y] # Take the reference
ref += 1 # Assignment
ref # => 2
a[x][y] # => 1
You might have better success by using a Hash and keying the hash with tuples of your coordinates, and then using these tuples to get references to specific locations:
a = {}
loc = [x, y]
a[loc] = 'First Value' # Initial value
a[[x,y]] # => 'First Value'
a[loc] = 'Second Value' # Assignment
a[[x,y]] # => 'Second Value'
a[loc] = 1 # Assignment
a[loc] += 1 # Assignment
a[[x,y]] # => '2'
Ruby is considered pass by value so to answer your question (not pass by reference like C++), it's not directly possible to do what you're asking.
There's a really good post in this answer by Abe that you should read through:
Is Ruby pass by reference or by value?
For ref to continue to point to the actual data of arr[x][y] at any given time, one possibiliy is to write it as a method :
def ref
ar[1][1]
end
In a high level language like ruby, all variables are references and there is no "pointers" or levels of indirections like C or C++, you should create objects to hold this references to get similar behavior
This is what I would do on ruby
Suppose you need to save a "pointer" to a ruby array, then you create a Class to access the array in a given index (there is no such thing like getting a "pointer" to a value in ruby)
class ArrayPointer
def initialize(array, index)
#array = array
#index = index
end
def read
#array[index]
end
def write(value)
#array[index] = value
end
end
Then, you use the clase this way
array = [1, 2, 3]
pointer = ArrayPointer.new(array, 1)
pointer.write(20)
puts array # [1, 20, 3]
You also can get "pointers" to local variables, but is too weird and uncommon in ruby world and it almost doesn't make sense
Note this kind of code is weird and not common in ruby, but it is interesting from the didactic point of view to compare two great languages like Ruby and C
In the Object Oriented nature of ruby, is preferable to design good abstractions (e.g. instead of using an array to represent your data, if preferable to define a class with methods like the ruby way) before only using elemental structures such as Array or Hash to represent the data used by your program (the last approach common in C, is not the ruby way)

Ruby: using strings to dynamically create objects

How do I use strings or symbols to create new variables || objects? Say I want 5 unique objects of an already made item class;
for x in 1..5
item_x = item.new() #where x is obviously the number value of the iterator
end
I have tried using eval() in this manner:
for x in 1..5
eval( "item_" << x << "= item.new()")
end
Hoping that putting the string I want executed into eval would have it executed as if I put it into the code.
I have searched for dynamic object creation and have not found anyone with this problem, sorry if this is mundane stuff. I have found references to people using .const_get and Openstruct but these don't seem to fix my problem in a manner I can understand.
Is there any reason you can't store your objects in an array? That would make them easier to both create and access:
items = []
5.times do
items << Item.new()
end
Then instead of item_1 through item_5, you have item[0] through item[4].
That said, if you really need/want to do the hard thing, instance_variable_set is an option if instance variables (with #) are okay:
for x in 1..5
self.instance_variable_set("#item_#{x}", Item.new())
end
I'd recommend just sticking with the array.
Update: From your comment, it sounds like your actual use case involves desired variable names that aren't consecutive. In that case, I'd use a Hash rather than an Array.
Think of it this way: whatever string you wish to use as the name of a local variable, just use it as a key in a local Hash. I can't think of any instance where that wouldn't be better than what you're trying, even if what you're trying worked.
You can use instance_variable_set to create instance variables in the manner you depict:
for x in 1..5
variable_name = "item_#{x}"
instance_variable_set("##{variable_name}", "value to assign to variable")
end
puts #item_1 #=> "value assigned to item_1"
Note, however, that this will not create local variables.

Resources