What kind of variable is ARGV in ruby? - ruby

From what I have read ARGV should be a constant since it is all uppercase, but I was able to write a quick program that changed one of the values in ARGV without error. So what type of variable is ARGV?
p ARGV
ARGV[0] = "Not the orginal"
p ARGV

ARGV is an array. Keep in mind that "constant" just means that the variable shouldn't be reassigned, not that the object itself can't change. You may be confusing it with the idea of a const object in C++. That is more equivalent to a frozen object in Ruby. (And note that even "constants shouldn't be reassigned" is a weak guarantee in Ruby. Reassigning a constant doesn't fail; it just prints a warning. It is a bad practice, though.)
To illustrate the difference:
ruby-1.9.2-p0 > CONSTANT = [1,2,3]
=> [1, 2, 3]
ruby-1.9.2-p0 > frozen = [1,2,3].freeze
=> [1, 2, 3]
ruby-1.9.2-p0 > CONSTANT << 4
=> [1, 2, 3, 4]
ruby-1.9.2-p0 > frozen << 4
RuntimeError: can't modify frozen array

ARGV is a constant, but it's an Array. Values in a constant array can be freely changed without any warnings, like any usual array element.
irb(main)> ARGV.class
=> Array
irb(main)> QWERTY = [1, 2, 3, 4]
=> [1, 2, 3, 4]
irb(main)> QWERTY[1] = 5
=> 5
irb(main)> QWERTY
=> [1, 5, 3, 4]
irb(main)> QWERTY << 6
=> [1, 5, 3, 4, 6]
irb(main)> QWERTY = 3
(irb): warning: already initialized constant QWERTY
=> 3

Related

Why a new call of a method with exclamation mark affects all previous calls of that method?

I'm sorry if this is a duplicate - I couldn't find anything similar in the existing posts.
I understand the difference between methods like shuffle and shuffle!. However, I am confused why calling the method more than once would result in changing the variables of all objects that previously referred to it? I'd expect once we apply a method, that the variable gets a value and we're done with it. Not that it continues to refer to the method call and the argument passed and that it would get re-evaluated later on.
I thought it's best to demonstrate with an example:
irb(main):001:1* def shuffle(arr)
irb(main):002:1* arr.shuffle!
irb(main):003:0> end
=> :shuffle
irb(main):004:0> arr = [1,2,3,4]
=> [1, 2, 3, 4]
irb(main):005:0> one = shuffle(arr)
=> [4, 2, 3, 1]
irb(main):006:0> two = shuffle(arr)
=> [1, 2, 4, 3]
irb(main):007:0> one
=> [1, 2, 4, 3]
So, here I'd expect one to stay [4, 2, 3, 1]. However, with each new call, all previous ones would get equated to the latest result of the method call. I realise it should have something to do with calling it with the same argument arr, but still doesn't quite make sense.
Array#shuffle! shuffles the array in-place and returns its receiver:
ary = [1, 2, 3, 4]
ary.equal?(ary.shuffle!) #=> true
Assigning the result from shuffle! to another variable doesn't change this. It merely results in two variables referring to the same array:
a = [1, 2, 3, 4]
b = a.shuffle!
a #=> [2, 4, 1, 3]
b #=> [2, 4, 1, 3]
a.equal?(b) #=> true
You probably want a new array. That's what Array#shuffle (without !) is for:
a = [1, 2, 3, 4]
b = a.shuffle
a #=> [1, 2, 3, 4]
b #=> [2, 4, 1, 3]
Even if shuffle returns the element in the original order, you'll get another array instance:
a = [1, 2, 3, 4]
b = a.shuffle until b == a
a #=> [1, 2, 3, 4]
b #=> [1, 2, 3, 4]
a.equal?(b) #=> false

How to stop shift from affecting other instances of an array

I need to shift through an array and keep a copy of the original array for future.
I tried creating another variable using a = b, but both are affected when I shift a.
rb(main):001:0> a = [1,2,3,4,5]
# => [1, 2, 3, 4, 5]
irb(main):002:0> b = a
# => [1, 2, 3, 4, 5]
irb(main):003:0> c = a.shift
# => 1
irb(main):004:0> a
# => [2, 3, 4, 5]
irb(main):005:0> b
# => [2, 3, 4, 5]
irb(main):006:0> c
# => 1
Is there a way to keep this from happening?
In Ruby it's important to remember variables are object references which behave a lot like pointers, so b = a does not make a copy, it is another reference to the same object.
To make a copy you must be explicit and use dup or clone to achieve this:
b = a.dup
If you're ever confused by Ruby's behaviour, stop and look at the objects you're dealing with:
a = [ 1 ]
b = a
a.object_id == b.object_id
# => true
They're exactly the same object, but when cloned:
b = a.dup
a.object_id == b.object_id
# => false
Now they're independent, at least on the top-level.
Note that this comes with some caveats, as this is only a shallow copy:
a = [ [ 1 ] ]
b = a.dup
b[0].object_id == a[0].object_id
# => true
This is where deep_clone tools come in handy if you need a complete clone, something available from various gems but most popularly ActiveSupport from Rails.
One thing you'll find in Ruby is it tends to steer towards a more functional style, as in if you wanted to strip an element from a and avoid mangling b:
a = [ 1, 2, 3, 4, 5 ]
b = a
a = a.drop(1)
# => [2, 3, 4, 5]
Where drop skips over the first N entries and returns the rest as a copy:
b
# => [1, 2, 3, 4, 5]

How to remember swapped array elements in another array?

This is driving me crazy! I've been trying to write a Ruby method to find all permutations, to solve Project Euler's problem 24. When I swap the elements of an array, they are swapped properly. But when I try to STORE this swapped array in a DIFFERENT array, this new array only remembers the latest copy of my swapped array! It won't remember the older version.
When I print out a during the loop, it shows all permutations properly. But when I print out perm (which I use to store all different permutations of a), it only shows 1 version of a repeated several times. How do I fix this?
a = [0, 1, 2, 3]
perms = []
p "a = #{a}" # output: "a = [0, 1, 2, 3]"
perms << a # add a to perms array
p "perms = #{perms}" # output: "perms = [[0, 1, 2, 3]]"
a[0], a[1] = a[1], a[0] # swap 1st 2 elements of a
p "a = #{a}" # output: "a = [1, 0, 2, 3]"
perms << a # add a to perms array
p "perms = #{perms}" # "perms = [[1, 0, 2, 3], [1, 0, 2, 3]]"
a[1], a[2] = a[2], a[1] # swap 2nd 2 elements of a
p "a = #{a}" # "a = [1, 2, 0, 3]"
perms << a # add a to perms array
p "perms = #{perms}" # "perms = [[1, 2, 0, 3], [1, 2, 0, 3], [1, 2, 0, 3]]"
Thanks to Sawa below, both "dup" and "clone" methods solved my problem! Why doesn't my original way work? When would I use "dup" vs. "clone"? Please give me some code examples.
a[0], a[1] = a[1], a[0] # swap 1st 2 elements of a
p "a = #{a}" # output: "a = [1, 0, 2, 3]"
b = a.dup (or a.clone)
perms << b
p "perms = #{perms}" # "perms = [[0, 1, 2, 3], [1, 0, 2, 3]]" *** it remembers!
a[1], a[2] = a[2], a[1] # swap 2nd 2 elements of a
p "a = #{a}" # "a = [1, 2, 0, 3]"
b = a.dup (or a.clone)
perms << b
p "perms = #{perms}" # "perms = [[0, 1, 2, 3], [1, 0, 2, 3], [1, 2, 0, 3]]"
Variables in Ruby (with some exceptions, such as variables bound to integers) contain references to objects, not values. Here's an example from running "irb":
1.9.3p374 :021 > str1="hi"
=> "hi"
1.9.3p374 :022 > str2=str1
=> "hi"
1.9.3p374 :023 > str1.replace("world")
=> "world"
1.9.3p374 :024 > str2
=> "world"
You'll notice that once I replace the value for str1, str2's "value" changes as well. That's because it contains a reference to the str1 object. I know one difference between dup and clone has to do with the "freeze" method. If I had called str1.freeze, then it would prevent the object str1 references from being modified, e.g.,
1.9.3p374 :055 > str1.freeze
=> "hi"
1.9.3p374 :056 > str1[0]="b"
RuntimeError: can't modify frozen String
from (irb):56:in `[]='
from (irb):56
from /.rvm/rubies/ruby-1.9.3-p374/bin/irb:13:in `<main>
"Dup"-ing a frozen object doesn't create a frozen object whereas cloning does.
EDIT: just a slight update....When assigning an object on the right to a variable on the left (e.g., str = Object.new), the variable receives an object reference. When assigning one variable to another, the left-hand side variable receives a copy of the reference that the variable on the right contains. In either case, you are still storing object references in the left-hand side variable.
Your original didn't work because you kept modifying the same array instance a.
Take a dup of the original array each time before you modify it into a different array. Or, create a new instance of Array by not relying on a destructive method.
a = original_array
b = a.dup
... # do some modifications to `b`
perms << b
c = a.dup
... # do some modifications to `c`
perms << c
...
If you don't like reinventing the wheel, you can use the facets gem.
gem install facets
https://github.com/rubyworks/facets/blob/d96ec0d700d1d7180ccbb5452e0a926386ec0b32/lib/backport/facets/array/permutation.rb
require 'facets'
[1, 2, 3].permutation
#=> [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

Confusion with splat operator and Range in Ruby

I was trying to see how splat operator worked with range in Ruby. To do so ran the below code in my IRB:
*a = (1..8)
#=> 1..8
When the above is fine, what happened with below? means why a gives []?
*a,b = (1..8)
#=> 1..8
b
#=> 1..8
a
#=> []
means why b gives []?
a,*b = (1..8)
#=> 1..8
a
#=> 1..8
b
#=> []
What precedence took place in the below Rvalues ?
a,*b = *(2..8),*3,*5
# => [2, 3, 4, 5, 6, 7, 8, 3, 5]
b
# => [3, 4, 5, 6, 7, 8, 3, 5]
a
# => 2
Here is another try to the splat operator(*) :-
While I know that in parallel assignment we couldn't use multiple splatted variable, but why not the same when splat is used with Rvalues?
*a,*b = [1,2,3,4,5]
SyntaxError: (irb):1: syntax error, unexpected tSTAR
*a,*b = [1,2,3,4,5]
^
from /usr/bin/irb:12:in `<main>'
The above is as expected.
a = *2,*3,*5
#=> [2, 3, 5]
But couldn't understand the above.
I think of parallel assignment as setting an array of variables equal to another array with pattern matching.
One point is that a range is a single value until you convert it to an array or splat it. For instance [1..5] which is a one element array of the range 1..5 and not [1,2,3,4,5]. To get the array of ints you need to do (1..5).to_a or [*(1..5)]
The first one i think is the trickiest. If the splatted var is assigned to one element, the var itself must be a one-element array:
*a = 5
a
# => [ 5 ]
For the next two, splat takes 0 or more not already assigned values into an array. So the following makes sense:
*a, b = (1..8)
is like
*a, b = "hey"
which is like
*a, b = [ "hey" ]
so *a is [] and b is "hey" and by the same logic that if *a is nothing, a must be an empty array. Same idea for
a, *b = (1..5)
For the next one, the range is splatted, so the assignment makes a lot of sense again:
[*(2..4), 9, 5]
# => [2, 3, 4, 9, 5]
And parallel assignment with a splat again. Next one is similar:
[*3, *4, *5]
# => [3, 4, 5]
So that's like
a = 3, 4, 5
which is like
a = [3, 4, 5]
splat has a very low precedence, almost anything will be executed earlier than the splat.
The code is splatting but the result is thrown away: b = *a = (1..8); p b #=> [1, 2, 3, 4, 5, 6, 7, 8]

How do I check an array for duplicates? [duplicate]

This question already has answers here:
How to find and return a duplicate value in array
(23 answers)
Closed 8 years ago.
I've got an array A. I'd like to check if it contains duplicate values. How would I do so?
Just call uniq on it (which returns a new array without duplicates) and see whether the uniqed array has less elements than the original:
if a.uniq.length == a.length
puts "a does not contain duplicates"
else
puts "a does contain duplicates"
end
Note that the objects in the array need to respond to hash and eql? in a meaningful for uniq to work properly.
In order to find the duplicated elements, I use this approach (with Ruby 1.9.3):
array = [1, 2, 1, 3, 5, 4, 5, 5]
=> [1, 2, 1, 3, 5, 4, 5, 5]
dup = array.select{|element| array.count(element) > 1 }
=> [1, 1, 5, 5, 5]
dup.uniq
=> [1, 5]
If you want to return the duplicates, you can do this:
dups = [1,1,1,2,2,3].group_by{|e| e}.keep_if{|_, e| e.length > 1}
# => {1=>[1, 1, 1], 2=>[2, 2]}
If you want just the values:
dups.keys
# => [1, 2]
If you want the number of duplicates:
dups.map{|k, v| {k => v.length}}
# => [{1=>3}, {2=>2}]
Might want to monkeypatch Array if using this more than once:
class Array
def uniq?
self.length == self.uniq.length
end
end
Then:
irb(main):018:0> [1,2].uniq?
=> true
irb(main):019:0> [2,2].uniq?
=> false

Resources