Saving the order of a shuffled array in Ruby - ruby

If I say...
a = [1,2,3]
a.shuffle
puts a
...it gives [1,2,3]. If instead I say...
a=[1,2,3]
a.shuffle!
puts a
...it gives me a new order each time I say puts a. So my question is how do I save the order a shuffled array was put into? If it returns [3,1,2] the first time it should keep this order. Does this make sense?
Here is an example of what I'm talking about. Each time I call b I get a different result.
> a=[1,2,3]
=> [1, 2, 3]
> b=a.shuffl­e!
=> [1, 3, 2]
> b
=> [1, 2, 3]
> b
=> [2, 1, 3]
> b
=> [1, 3, 2]
> b
=> [3, 2, 1]
"b" seems to refer to the function a.shuffle instead of the results of the shuffle itself.
The answer:
The problem occured when using the online interpreter on TryRuby.org. Using the interactive Ruby interpreter on my PC gives the correct result. Thanks to everyone for their help!

Do you need somethinglike this:
a = [1,2,3]
b = a.shuffle
puts b

The problem occured when using the online interpreter on TryRuby.org. Using the interactive Ruby interpreter on my PC gives the correct result. Thanks to everyone for their help!

a.shuffle returns a shuffling of a, but does not change a itself, so you'd need to assign the result to a different variable (or to a if that's what you want). You need to call a.shuffle! if you want the method to change a directly.
See Why are exclamation marks used in Ruby methods?

Related

Ruby variable behaviour when calling a method

I'm pretty good at getting answers from google, but I just don't get this. In the following code, why does variable 'b' get changed after calling 'addup'? I think I understand why 'a' gets changed (although its a bit fuzzy), but I want to save the original array 'a' into 'b', run the method on 'a' so I have two arrays with different content. What am I doing wrong?
Thanks in advance
def addup(arr)
i=0
while i< arr.length
if arr[i]>3
arr.delete_at(i)
end
i += 1
end
return arr
end
a = [1,2,3,4]
b = a
puts "a=#{a}" # => [1,2,3,4]
puts "b=#{b}" # => [1,2,3,4]
puts "addup=#{addup(a)}" # => [1,2,3]
puts "a=#{a}" # => [1,2,3]
puts "b=#{b}" # => [1,2,3]
Both a and b hold a reference to the same array object in memory. In order to save the original array in b, you'd need to copy the array.
a = [1,2,3,4] # => [1, 2, 3, 4]
b = a # => [1, 2, 3, 4]
c = a.dup # => [1, 2, 3, 4]
a.push 5 # => [1, 2, 3, 4, 5]
a # => [1, 2, 3, 4, 5]
b # => [1, 2, 3, 4, 5]
c # => [1, 2, 3, 4]
For more information on why this is happening, read Is Ruby pass by reference or by value?
but I want to save the original array 'a' into 'b'
You are not saving the original array into b. Value of a is a reference to an array. You are copying a reference, which still points to the same array. No matter which reference you use to mutate the array, the changes will be visible through both references, because, again, they point to the same array.
To get a copy of the array, you have to explicitly do that. For shallow arrays with primitive values, simple a.dup will suffice. For structures which are nested or contain references to complex objects, you likely need a deep copy. Something like this:
b = Marhal.load(Marshal.dump(a))
In the following code, why does variable 'b' get changed after calling 'addup'?
The variable doesn't get changed. It still references the exact same array it did before.
There are only two ways to change a variable in Ruby:
Assignment (foo = :bar)
Reflection (Binding#local_variable_set, Object#instance_variable_set, Module#class_variable_set, Module#const_set)
Neither of those is used here.
I think I understand why 'a' gets changed (although its a bit fuzzy)
a doesn't get changed either. It also still references the exact same array it did before. (Which, incidentally, is the same array that b references.)
The only thing which does change is the internal state of the array that is referenced by both a and b. So, if you really understand why the array referenced by a changes, then you also understand why the array referenced by b changes, since it is the same array. There is only one array in your code.
The immediate problem with your code is that, if you want a copy of the array, then you need to actually make a copy of the array. That's what Object#dup and Object#clone are for:
b = a.clone
Will fix your code.
BUT!
There are some other problems in your code. The main problem is mutation. If at all possible, you should avoid mutation (and side-effects in general, of which mutation is only one example) as much as possible and only use it when you really, REALLY have to. In particular, you should never mutate objects you don't own, and this means you should never mutate objects that were passed to you as arguments.
However, in your addup method, you mutate the array that is passed to you as arr. Mutation is the source of your problem, if you didn't mutate arr but instead returned a new array with the modifications you want, then you wouldn't have the problem in the first place. One way of not mutating the argument would be to move the cloneing into the method, but there is an even better way.
Another problem with your code is that you are using a loop. In Ruby, there is almost never a situation where a loop is the best solution. In fact, I would go so far as to argue that if you are using a loop, you are doing it wrong.
Loops are error-prone, hard to understand, hard to get right, and they depend on side-effects. A loop cannot work without side-effects, yet, we just said we want to avoid side-effects!
Case in point: your loop contains a serious bug. If I pass [1, 2, 3, 4, 5], the result will be [1, 2, 3, 5]. Why? Because of mutation and manual looping:
In the fourth iteration of the loop, at the beginning, the array looks like this:
[1, 2, 3, 4, 5]
# ↑
# i
After the call to delete_at(i), the array looks like this:
[1, 2, 3, 5]
# ↑
# i
Now, you increment i, so the situation looks like this:
[1, 2, 3, 5]
# ↑
# i
i is now greater than the length of the array, ergo, the loop ends, and the 5 never gets removed.
What you really want, is this:
def addup(arr)
arr.reject {|el| el > 3 }
end
a = [1, 2, 3, 4, 5]
b = a
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
puts "addup=#{addup(a)}" # => [1, 2, 3]
puts "a=#{a}" # => [1, 2, 3, 4, 5]
puts "b=#{b}" # => [1, 2, 3, 4, 5]
As you can see, nothing was mutated. addup simply returns the new array with the modifications you want. If you want to refer to that array later, you can assign it to a variable:
c = addup(a)
There is no need to manually fiddle with loop indices. There is no need to copy or clone anything. There is no "spooky action at a distance", as Albert Einstein called it. We fixed two bugs and removed 7 lines of code, simply by
avoiding mutation
avoiding loops

what's a clean way of finding which common elements exist in a set of n arrays?

I'm going through a table in my database that has a lot of optional columns. I'm wanting to find the columns which have data in every record in my database.
A simplified example of what I'm trying to do is as follows:
[1,2,3,4,5] & [1,2,3,4] & [1,2,3] & [1,2]
#=> [1,2]
However, what I'm trying to do is run this type of operation for thousands of records. What's a clean way to accomplish this? I have the feeling that ruby might have some bespoke methods for this sort of thing.
Here's what I was about to do before I decided to write this question:
sets_of_columns_with_data = TableName.all.map(&:attributes).map do |attrs|
attrs.select {|k,v| v}
end.map(&:keys)
So at this point, if you were following the above code, columns_with_data is now the equivalent of this:
sets_of_columns_with_data = [
[1,2,3,4,5],
[1,2,3,4],
[1,2,3]
[1,2]
]
A messy way to do this I guess would look something like this:
always_used = sets_of_columns_with_data.first
sets_of_columns_with_data.each do |columns_with_data|
always_used = always_used & columns_with_data
end
What's the clean, ruby-way to do something like this?
Thanks
NOTE:
I'm keeping the business logic for sake of clarity, but generally this is not the best solution when you have SQL available to you.
I'm not sure if this solves the actual problem, but to apply a binary operation, you can use reduce:
sets_of_columns_with_data = [
[1, 2, 3, 4, 5],
[1, 2, 3, 4],
[1, 2, 3],
[1, 2]
]
sets_of_columns_with_data.reduce(:&) #=> [1, 2]

Block with two parameters

I found this code by user Hirolau:
def sum_to_n?(a, n)
a.combination(2).find{|x, y| x + y == n}
end
a = [1, 2, 3, 4, 5]
sum_to_n?(a, 9) # => [4, 5]
sum_to_n?(a, 11) # => nil
How can I know when I can send two parameters to a predefined method like find? It's not clear to me because sometimes it doesn't work. Is this something that has been redefined?
If you look at the documentation of Enumerable#find, you see that it accepts only one parameter to the block. The reason why you can send it two, is because Ruby conveniently lets you do this with blocks, based on it's "parallel assignment" structure:
[[1,2,3], [4,5,6]].each {|x,y,z| puts "#{x}#{y}#{z}"}
# 123
# 456
So basically, each yields an array element to the block, and because Ruby block syntax allows "expanding" array elements to their components by providing a list of arguments, it works.
You can find more tricks with block arguments here.
a.combination(2) results in an array of arrays, where each of the sub array consists of 2 elements. So:
a = [1,2,3,4]
a.combination(2)
# => [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
As a result, you are sending one array like [1,2] to find's block, and Ruby performs the parallel assignment to assign 1 to x and 2 to y.
Also see this SO question, which brings other powerful examples of parallel assignment, such as this statement:
a,(b,(c,d)) = [1,[2,[3,4]]]
find does not take two parameters, it takes one. The reason the block in your example takes two parameters is because it is using destruction. The preceding code a.combination(2) gives an array of arrays of two elements, and find iterates over it. Each element (an array of two elements) is passed at a time to the block as its single parameter. However, when you write more parameters than there is, Ruby tries to adjust the parameters by destructing the array. The part:
find{|x, y| x + y == n}
is a shorthand for writing:
find{|(x, y)| x + y == n}
The find function iterates over elements, it takes a single argument, in this case a block (which does take two arguments for a hash):
h = {foo: 5, bar: 6}
result = h.find {|k, v| k == :foo && v == 5}
puts result.inspect #=> [:foo, 5]
The block takes only one argument for arrays though unless you use destructuring.
Update: It seems that it is destructuring in this case.

Adding two arrays in Ruby when the array length will always be the same

So, I need to add two arrays together to populate a third. EG
a = [1,2,3,4]
b = [3,4,5,6]
so that:
c = [4,6,8,10]
I read the answer given here: https://stackoverflow.com/questions/12584585/adding-two-ruby-arrays
but I'm using the codecademy labs ruby editor and it's not working there, plus the lengths of my arrays are ALWAYS going to be equal. Also, I don't have any idea what the method ".with_index" is or does and I don't understand why it's necessary to use ".to_i" when the value is already an integer.
It seems like this should be really simple?
a = [1,2,3,4]
b = [3,4,5,6]
a.zip(b).map { |i,j| i+j } # => [4, 6, 8, 10]
Here
a.zip(b) # => [[1, 3], [2, 4], [3, 5], [4, 6]]
and map converts each 2-tuple to the sum of its elements.
OPTION 1:
For a pure Ruby solution, try the transpose method:
a = [1,2,3,4]
b = [3,4,5,6]
c = [a, b].transpose.map{|x, y| x + y}
#=> [4,6,8,10]
OPTION 2:
If you're in a Rails environment, you can utilize Rails' sum method:
[a, b].transpose.map{|x| x.sum}
#=> [4,6,8,10]
EXPLANATION:
transpose works perfectly for your scenario, since it raises an IndexError if the sub-arrays don't have the same length. From the docs:
Assumes that self is an array of arrays and transposes the rows and columns.
If the length of the subarrays don’t match, an IndexError is raised.

How to find which array element is not present in another

Here is the scenario
a = [1,2,3]
b = [2,5,6]
I would like to find out which elements of b are not present in a. I could use include? to check which ones are. But I am looking for something entirely opposite here.
> a - b
=> [1, 3]
> b - a
=> [5, 6]
(I can't quite tell which way you want to go.)
a = [1,2,3]
b = [2,5,6]
b - a #=> [5, 6]

Resources