How to shift the values in a hash to another key - ruby

I am having trouble figuring the best approach to shift some values to another key. Every value most go to the next key, and the last key's values need to be completely removed. For example
hash1 = { a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] }
desired_hash = hash1.some_method
desired_hash === { a: [], b: [1, 2, 3], c: [4, 5, 6] }
My thought is to rename the hash keys but was not sure if this was the best approach.

hash1 = { a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] }
keys = hash1.keys
=> [:a, :b, :c]
values = hash1.values
=> [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
keys.zip(values.unshift([])).to_h
=> {:a=>[], :b=>[1, 2, 3], :c=>[4, 5, 6]}

pv = []
hash1.each_with_object({}) do |(k,v),h|
h[k] = pv
pv = v
end
#=> {:a=>[], :b=>[1, 2, 3], :c=>[4, 5, 6]}

Assuming the hash keys are "in correct order" already.
You can try this:
def shift(input)
output = {}
keys = input.keys
0.upto(keys.size - 1) do |index|
current = keys[index]
if index > 0
previous = keys[index - 1]
output[current] = input[previous]
else
output[current] = []
end
end
output
end
input = { a: [1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] }
p shift(input)
it's not very elegant, there is probably a much nicer solution but it's a starting point.

Related

Problems using `elsif` in a `sort` block

I have this array:
ary = [[1, 6, 7], [1, 4, 9], [1, 8, 3]]
I want to sort it by the first odd number, or the last number if they are all even, in each subarray.
Since the first element in each array is the same object 1 for this particular ary, I can solve this like this:
ary2 = ary.sort_by { |a, b, c| b.odd? ? b : c }
But when I try a more general one:
arr2 = ary.sort_by { |a, b, c| a.odd? ? a : b.odd? ? b : c }
ary2 comes back unsorted.
I tried removing the ternary operators like this:
ary2 = ary.sort_by do |a, b, c|
if a.odd?
a
elsif b.odd?
b
else
c
end
end
with the same effect (i.e., none).
Is there some reason that elsif can't be used in blocks passed to the sort_by method?
Edit: Axiac pointed out the problem with my logic. It looks like conditional logic has to deal with all of the possible permutations of odd and even values. This works:
arr2 = arr.sort_by do |a, b, c|
if a.odd?
if b.odd?
if c.odd?
[a, b, c]
else
[a, b]
end
elsif c.odd?
[a, c]
else
a
end
elsif b.odd?
if c.odd?
[b, c]
else
b
end
else
c
end
end
Maybe there's a more succinct and less brittle way to do it, but it's probably a good idea to do it this way instead:
arr2 = arr.sort_by do |sub_arr|
temp = sub_arr.select do |e|
e.odd?
end
temp.empty? ? Array(sub_arr.last) : temp
end
I'll see myself out.
Regarding your original question, just as axiac points out in the comment, the result of the sorting should be exactly the same as the input array because they are all sorted by the first odd element in each subarray, which is 1, and the sort method is stable in MRI.
Regarding your question after the edit, my answer would be:
ary.sort_by{|a| a[0...-1].select(&:odd?) << a.last}
# => [[1, 8, 3], [1, 6, 7], [1, 4, 9]]
I am pretty confident that this is what you wrote after the edit that you wanted, but I am not sure if this is what you wanted since the sorting mechanism looks strange to me.
I find the statement of the question ambiguous. I will give an answer that is consist with one interpretation. If that is not what you want, please clarify hte question.
def my_sort(arr)
arr.sort_by {|a| a.any?(&:odd?) ? a.map {|e| e.odd? ? e : Float::INFINITY} : [a.last]}
end
my_sort [[1, 6, 7], [1, 4, 9], [1, 2, 3]]
#=> [[1, ∞, 7], [1, ∞, 9], [1, ∞, 3]] (sort_by)
#=> [[1, 2, 3], [1, 6, 7], [1, 4, 9]]
my_sort [[3, 6, 7], [4, 1, 9], [5, 8, 1]]
#=> [[3, ∞, 7], [∞, 1, 9], [5, ∞, 1]] (sort_by)
#=> [[3, 6, 7], [5, 8, 1], [4, 1, 9]]
my_sort [[2, 6, 8], [4, 1, 4], [8, 6, 2]]
#=> [[8], [∞, 1, ∞], [2]] (sort_by)
#=> [[8, 6, 2], [2, 6, 8], [4, 1, 4]]
my_sort [[8, 6, 2], [5, 1, 1], [6, 8, 4]]
#=> [[2], [5, 1, 1], [4] (sort_by)
#=> [[8, 6, 2], [6, 8, 4], [5, 1, 1]]
For each example I've shown the arrays used by sort_by to produce the sort shown on the following line.

Split an array into arrays

I'm new to Ruby and would like to know if there is a better way to solve the following problem.
I have an array that looks like this:
[6, 1, 3, 6, 2, 4, 1, 3, 2, 3]
I'd like to turn it into this:
[ [1,1], [2,2], [3,3,3], [4], [], [6,6] ]
This is my current solution (again, I'm new to Ruby):
def split_array_into_arrays(array)
max_num = array.max
arrays = Array.new(max_num) { Array.new }
array.each do |num|
arrays[num-1] << num
end
arrays
end
arrays = split_array_into_arrays([6, 1, 3, 6, 2, 4, 1, 3, 2, 3])
puts arrays.inspect
Produces:
[[1, 1], [2, 2], [3, 3, 3], [4], [], [6, 6]]
Note: I realize I am not handling possible errors.
How might an experienced Ruby developer implement this?
ar = [6, 1, 3, 6, 2, 4, 1, 3, 2, 3]
(1..ar.max).map{|n| [n]*ar.count(n)}
# => [[1, 1], [2, 2], [3, 3, 3], [4], [], [6, 6]]

comparing 2 arrays in every position

So what Im trying to accomplish is write a (shorter) condition that makes sure each element is different from the other array. This is confusing but I hope this example clears it up.
array = [1, 2, 3]
new_array = array.shuffle
until array[0] != new_array[0] &&
array[1] != new_array[1] &&
array[2] != new_array[2]
new_array = array.shuffle
end
So what Im doing is making sure that every single element/index pair does not match in the other array.
# [1, 2, 3] => [3, 1, 2] yayyyy
# [1, 2, 3] => [3, 2, 1] not what I want because the 2 didnt move
Is there a better way to do what I want to do? Ive looked up the .any? and .none? but I cant seem to figure out how to implement them. Thanks!
I would do this:
array.zip(new_array).all? { |left, right| left != right }
Here are two approaches that do not involve repeated sampling until a valid sample is obtained:
Sample from the population of valid permutations
Construct the population from which you are sampling:
array = [1, 2, 3, 4]
population = array.permutation(array.size).reject do |a|
a.zip(array).any? { |e,f| e==f }
end
#=> [[2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2],
# [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1]]
Then just choose one at random:
10.times { p population.sample }
# [4, 3, 1, 2]
# [3, 4, 1, 2]
# [3, 4, 1, 2]
# [4, 3, 1, 2]
# [2, 1, 4, 3]
# [2, 1, 4, 3]
# [4, 1, 2, 3]
# [2, 1, 4, 3]
# [4, 3, 1, 2]
# [3, 4, 1, 2]
Sequentially sample for each position in the array
def sample_no_match(array)
a = array.each_index.to_a.shuffle
last_ndx = a[-1]
a.dup.map do |i|
if a.size == 2 && a[-1] == last_ndx
select = a[-1]
else
select = (a-[i]).sample
end
a.delete(select)
array[select]
end
end
10.times.each { p sample_no_match(array) }
# [2, 4, 3, 1]
# [4, 3, 1, 2]
# [2, 1, 3, 4]
# [1, 3, 4, 2]
# [1, 3, 2, 4]
# [1, 3, 2, 4]
# [1, 4, 3, 2]
# [3, 4, 2, 1]
# [1, 3, 4, 2]
# [1, 3, 4, 2]
I have been unable to prove or disprove that the second method produces a random sample. We can, however, determine relative frequencies of outcomes:
n = 500_000
h = n.times.with_object(Hash.new(0)) { |_,h| h[sample_no_match(array)] += 1 }
h.keys.each { |k| h[k] = (h[k]/(n.to_f)).round(4) }
h #=> {[1, 2, 3, 4]=>0.0418, [2, 1, 3, 4]=>0.0414, [1, 4, 2, 3]=>0.0418,
# [3, 4, 2, 1]=>0.0417, [4, 3, 2, 1]=>0.0415, [3, 1, 4, 2]=>0.0419,
# [2, 3, 1, 4]=>0.0420, [4, 2, 3, 1]=>0.0417, [3, 2, 1, 4]=>0.0413,
# [4, 2, 1, 3]=>0.0417, [2, 1, 4, 3]=>0.0419, [1, 3, 2, 4]=>0.0415,
# [1, 2, 4, 3]=>0.0418, [1, 3, 4, 2]=>0.0417, [2, 4, 1, 3]=>0.0414,
# [3, 4, 1, 2]=>0.0412, [1, 4, 3, 2]=>0.0423, [4, 1, 3, 2]=>0.0411,
# [3, 2, 4, 1]=>0.0411, [2, 4, 3, 1]=>0.0418, [3, 1, 2, 4]=>0.0419,
# [4, 3, 1, 2]=>0.0412, [4, 1, 2, 3]=>0.0421, [2, 3, 4, 1]=>0.0421}
avg = (h.values.reduce(:+)/h.size.to_f).round(4)
#=> 0.0417
mn, mx = h.values.minmax
#=> [0.0411, 0.0423]
([avg-mn,mx-avg].max/avg).round(6)
#=> 0.014388
which means that the maximum deviation from the average was only 1.4% percent of the average.
This suggests that the second method is a reasonable way of producing pseudo-random samples.
Initially, the first line of this method was:
a = array.each_index.to_a
By looking at the frequency distribution for outcomes, however, it was clear that that method did not produce a pseudo-random sample; hence, the need to shuffle a.
Here's one possibility:
until array.zip(new_array).reject{ |x, y| x == y }.size == array.size
new_array = array.shuffle
end
Note, though, that it will break for arrays like [1] or [1, 1, 1, 2, 3], where the number of instances of 1 exceeds half the size of the array. Recommend Array#uniq or similar, along with checking for arrays of sizes 0 or 1, depending on how trustworthy your input is!

Swapping two numbers with while loop in Ruby

I'd like to get [[2, 1, 3], [1, 3, 2]] from [1, 2, 3] in Ruby.
For [1, 2, 3, 4], I'd like to get [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
Rule: Within two numbers, if left one is smaller then it swap the position.
I have the following codes so far but it returns [[2, 3, 1], [2, 3, 1]]
What am I doing wrong here? I appreciate any inputs.
In amidakuji.rb
class Amidakuji
def initialize(column, rung)
#column = column
#rung = rung
#myarr = []
#per_arr = []
#build_arr = []
end
def build_initial
#arr = (1..#column).to_a
end
def swap_element
i = 0
arr = build_initial
while i < #column - 1 do
#build_arr << swap(arr, i)
i += 1
end
#build_arr
end
def swap(arr, a)
if arr[a] < arr[a + 1]
arr[a], arr[a + 1] = arr[a + 1], arr[a]
end
arr
end
end
In amidakuji_spec.rb
it 'should create an array with swapped elements' do
expect(#kuji1.swap_element).to eq ([[2, 1, 3], [1, 3, 2]])
end
Results
Failures:
expected: [[2, 1, 3], [1, 3, 2]]
got: [[2, 3, 1], [2, 3, 1]]
You can do this quite compactly by using the methods Enumerable#each_cons and Enumerable#map.
Code
def doit(arr)
(0...arr.size).each_cons(2).map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
end
Examples
doit([1,2,3]) #=> [[2, 1, 3], [1, 3, 2]]
doit([1,2,3,4]) #=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
doit([1,2,3,4,5]) #=> [[2, 1, 3, 4, 5], [1, 3, 2, 4, 5],
#=> [1, 2, 4, 3, 5], [1, 2, 3, 5, 4]]
Explanation
arr = [1,2,3,4]
b = (0...arr.size).each_cons(2)
#=> #<Enumerator: 0...4:each_cons(2)>
To view the contents of this enumerator:
b.to_a
#=> [[0, 1], [1, 2], [2, 3]]
Lastly
b.map do |i,j|
a = arr.dup
a[i], a[j] = a[j], a[i]
a
end
#=> [[2, 1, 3, 4], [1, 3, 2, 4], [1, 2, 4, 3]]
In the last step, consider the first element of b that is passed to map, which assigns the following values to the block variables:
i => 0
j => 1
We then make a copy of arr, swap the elements offsets 0 and 1, making
a => [2, 1, 3, 4]
and then enter a at the end of the block, causing map to replace [0, 1] with that array.
Given what you're trying to accomplish and the output you're getting, it looks like you're reusing the same array when you want distinct arrays instead. Specifically this line:
#build_arr << swap(arr, i)
is always passing the same 'arr' to swap.
So first time, it swaps the 1 and the 2 to give you [2, 1, 3]
Second time, it swaps the 1 and the 3 give you [2, 3, 1]
You push the same array onto #build_arr twice, which is why it repeats.

ruby array element grouping

here is the problem
I have an array:
a = [1, 2, 3, 4]
and want to get:
b = [[1, 2, 3], [1, 3, 4], [1, 2, 4], [2, 3, 4]]
what is the best way? thanks!
You are looking for all unique sets of 3 elements out of a set of 4.
Use Array#combination method:
a = [1, 2, 3, 4]
b = a.combination(3).to_a
output:
=> [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]
More info:
Array#combination
Wikipedia Combination
Here would be my first implementation. (But performance suckz i guess)
array = [1,2,­3,4]
b = []
array.each­{|e| c = array­.clone; c.del­ete(e); b << c}
# b.sort!

Resources