Join an array with a block natively - ruby

Is there a native way to join all elements of an array into a unique element like so:
[
{a: "a"},
{b: "b"}
].join do | x, y |
x.merge(y)
end
To output something like:
{
a: "a",
b: "b"
}
The fact that I used hashes into my array is an example, I could say:
[
0,
1,
2,
3
].join do | x, y |
x + y
end
Ends up with 6 as a value.

Enumerable#inject covers both of these cases:
a = [{a: "a"}, {b: "b"}]
a.inject(:merge) #=> {:a=>"a", :b=>"b"}
b = [0, 1, 2, 3]
b.inject(:+) #=> 6
inject "sums" an array using the provided method. In the first case, the "addition" of the sum and the current element is done by merging, and in the second case, through addition.
If the array is empty, inject returns nil. To make it return something else, specify an initial value (thanks #Hellfar):
[].inject(0, :+) #=> 0

[
{a: "a"},
{b: "b"}
].inject({}){|sum, e| sum.merge e}

Related

How to merge hash of hashes and set default value if value don't exists

I need to merge values of hash a into out with sort keys in a.
a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
out = [
{"X": [4, 1]},
{"Y": [5, 0]},
{"Z": [0, 5]},
]
I would do something like this:
a = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
sorted_keys = a.values.flat_map(&:keys).uniq.sort
#=> [11, 12]
a.map { |k, v| { k => v.values_at(*sorted_keys).map(&:to_i) } }
#=> [ { "X" => [4, 1] }, { "Y" => [5, 0] }, { "Z" => [0, 5] }]
Code
def modify_values(g)
sorted_keys = g.reduce([]) {|arr,(_,v)| arr | v.keys}.sort
g.each_with_object({}) {|(k,v),h| h[k] = Hash.new(0).merge(v).values_at(*sorted_keys)}
end
Example
g = {"X"=>{12=>1, 11=>4}, "Y"=>{11=>5}, "Z"=>{12=>5}}
modify_values(g)
#=> {"X"=>[4, 1], "Y"=>[5, 0], "Z"=>[0, 5]}
Explanation
The steps are as follows (for the hash a in the example). First obtain an array of the unique keys from g's values (see Enumerable#reduce and Array#|), then sort that array.
b = a.reduce([]) {|arr,(_,v)| arr | v.keys}
#=> [12, 11]
sorted_keys = b.sort
#=> [11, 12]
The first key-value pair of a, together with an empty hash, is passed to each_with_object's block. The block variables are computed using parallel assignment:
(k,v),h = [["X", {12=>1, 11=>4}], {}]
k #=> "X"
v #=> {12=>1, 11=>4}
h #=> {}
The block calculation is then performed. First an empty hash with a default value 0 is created:
f = Hash.new(0)
#=> {}
The hash v is then merged into f. The result is hash with the same key-value pairs as v but with a default value of 0. The significance of the default value is that if f does not have a key k, f[k] returns the default value. See Hash::new.
g = f.merge(v)
#=> {12=>1, 11=>4}
g.default
#=> 0 (yup)
Then extract the values corresponding to sorted_keys:
h[k] = g.values_at(*sorted_keys)
#=> {12=>1, 11=>4}.values_at(11, 12)
#=> [4, 1]
When a's next key-value pair is passed to the block, the calculations are as follows.
(k,v),h = [["Y", {11=>5}], {"X"=>[4, 1]}] # Note `h` has been updated
k #=> "Y"
v #=> {11=>5}
h #=> {"X"=>[4, 1]}
f = Hash.new(0)
#=> {}
g = f.merge(v)
#=> {11=>5}
h[k] = g.values_at(*sorted_keys)
#=> {11=>5}.values_at(11, 12)
#=> [5, 0] (Note h[12] equals h's default value)
and now
h #=> {"X"=>[4, 1], "Y"=>[5, 0]}
The calculation for the third key-value pair of a is similar.

How to merge two hashes that have same keys in ruby

I have a two hashes that should have same keys like:
a = {a: 1, b: 2, c: 3}
b = {a: 2, b: 3, c: 4}
And I want to sum up each values like this:
if a.keys == b.keys
a.values.zip(b.values).map{|a, b| a+b}
end
But this code doesn't work if the order of keys are different like b = {a: 2, c: 4, b: 3}.
How can I write the code taking into account about order of keys?
Use Hash#merge or Hash#merge!:
a = {a: 1, b: 2, c: 3}
b = {a: 2, c: 4, b: 3}
a.merge!(b) { |k, o, n| o + n }
a # => {:a=>3, :b=>5, :c=>7}
The block is called with key, old value, new value. And the return value of the block is used as a new value.
If you're using Active Support (Rails), which adds Hash#transform_values, I really like this easy-to-read solution when you have n hashes:
hashes = [hash_1, hash_2, hash_3] # any number of hashes
hashes.flat_map(&:to_a).group_by(&:first).transform_values { |x| x.sum(&:last) }

Reposition an element to the front of an array in Ruby

Even coming from javascript this looks atrocious to me:
irb
>> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
>> a.unshift(a.delete('c'))
=> ["c", "a", "b"]
Is there a more legible way placing an element to the front of an array?
Edit my actual code:
if #admin_users.include?(current_user)
#admin_users.unshift(#admin_users.delete(current_user))
end
Maybe this looks better to you:
a.insert(0, a.delete('c'))
Maybe Array#rotate would work for you:
['a', 'b', 'c'].rotate(-1)
#=> ["c", "a", "b"]
This is a trickier problem than it seems. I defined the following tests:
describe Array do
describe '.promote' do
subject(:array) { [1, 2, 3] }
it { expect(array.promote(2)).to eq [2, 1, 3] }
it { expect(array.promote(3)).to eq [3, 1, 2] }
it { expect(array.promote(4)).to eq [1, 2, 3] }
it { expect((array + array).promote(2)).to eq [2, 1, 3, 1, 2, 3] }
end
end
sort_by proposed by #Duopixel is elegant but produces [3, 2, 1] for the second test.
class Array
def promote(promoted_element)
sort_by { |element| element == promoted_element ? 0 : 1 }
end
end
#tadman uses delete, but this deletes all matching elements, so the output of the fourth test is [2, 1, 3, 1, 3].
class Array
def promote(promoted_element)
if (found = delete(promoted_element))
unshift(found)
end
self
end
end
I tried using:
class Array
def promote(promoted_element)
return self unless (found = delete_at(find_index(promoted_element)))
unshift(found)
end
end
But that failed the third test because delete_at can't handle nil. Finally, I settled on:
class Array
def promote(promoted_element)
return self unless (found_index = find_index(promoted_element))
unshift(delete_at(found_index))
end
end
Who knew a simple idea like promote could be so tricky?
Adding my two cents:
array.select{ |item| <condition> } | array
Pros:
Can move multiple items to front of array
Cons:
This will remove all duplicates unless it's the desired outcome.
Example - Move all odd numbers to the front (and make array unique):
data = [1, 2, 3, 4, 3, 5, 1]
data.select{ |item| item.odd? } | data
# Short version:
data.select(&:odd?) | data
Result:
[1, 3, 5, 2, 4]
Another way:
a = [1, 2, 3, 4]
b = 3
[b] + (a - [b])
=> [3, 1, 2, 4]
If by "elegant" you mean more readable even at the expense of being non-standard, you could always write your own method that enhances Array:
class Array
def promote(value)
if (found = delete(value))
unshift(found)
end
self
end
end
a = %w[ a b c ]
a.promote('c')
# => ["c", "a", "b"]
a.promote('x')
# => ["c", "a", "b"]
Keep in mind this would only reposition a single instance of a value. If there are several in the array, subsequent ones would probably not be moved until the first is removed.
In the end I considered this the most readable alternative to moving an element to the front:
if #admin_users.include?(current_user)
#admin_users.sort_by{|admin| admin == current_user ? 0 : 1}
end
If all the elements in the array are unique you can use array arithmetic:
> a = ['a', 'b', 'c']
=> ["a", "b", "c"]
> a -= "c"
=> ["a", "b"]
> a = ["c"] + a
=> ["c", "a", "b"]
Building on above:
class Array
def promote(*promoted)
self - (tail = self - promoted) + tail
end
end
[1,2,3,4].promote(5)
=> [1, 2, 3, 4]
[1,2,3,4].promote(4)
=> [4, 1, 2, 3]
[1,2,3,4].promote(2,4)
=> [2, 4, 1, 3]

Reordering an array in the same order as another array was reordered

I have two arrays a, b of the same length:
a = [a_1, a_2, ..., a_n]
b = [b_1, b_2, ..., b_n]
When I sort a using sort_by!, the elements of a will be arranged in different order:
a.sort_by!{|a_i| some_condition(a_i)}
How can I reorder b in the same order/rearrangement as the reordering of a? For example, if a after sort_by! is
[a_3, a_6, a_1, ..., a_i_n]
then I want
[b_3, b_6, b_1, ..., b_i_n]
Edit
I need to do it in place (i.e., retain the object_id of a, b). The two answers given so far is useful in that, given the sorted arrays:
a_sorted
b_sorted
I can do
a.replace(a_sorted)
b.replace(b_sorted)
but if possible, I want to do it directly. If not, I will accept one of the answers already given.
One approach would be to zip the two arrays together and sort them at the same time. Something like this, perhaps?
a = [1, 2, 3, 4, 5]
b = %w(a b c d e)
a,b = a.zip(b).sort_by { rand }.transpose
p a #=> [3, 5, 2, 4, 1]
p b #=> ["c", "e", "b", "d", "a"]
How about:
ary_a = [ 3, 1, 2] # => [3, 1, 2]
ary_b = [ 'a', 'b', 'c'] # => ["a", "b", "c"]
ary_a.zip(ary_b).sort{ |a,b| a.first <=> b.first }.map{ |a,b| b } # => ["b", "c", "a"]
or
ary_a.zip(ary_b).sort_by(&:first).map{ |a,b| b } # => ["b", "c", "a"]
If the entries are unique, the following may work. I haven't tested it. This is partially copied from https://stackoverflow.com/a/4283318/38765
temporary_copy = a.sort_by{|a_i| some_condition(a_i)}
new_indexes = a.map {|a_i| temporary_copy.index(a_i)}
a.each_with_index.sort_by! do |element, i|
new_indexes[i]
end
b.each_with_index.sort_by! do |element, i|
new_indexes[i]
end

How to return a part of an array in Ruby?

With a list in Python I can return a part of it using the following code:
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
half = len(foo) / 2
foobar = foo[:half] + bar[half:]
Since Ruby does everything in arrays I wonder if there is something similar to that.
Yes, Ruby has very similar array-slicing syntax to Python. Here is the ri documentation for the array index method:
--------------------------------------------------------------- Array#[]
array[index] -> obj or nil
array[start, length] -> an_array or nil
array[range] -> an_array or nil
array.slice(index) -> obj or nil
array.slice(start, length) -> an_array or nil
array.slice(range) -> an_array or nil
------------------------------------------------------------------------
Element Reference---Returns the element at index, or returns a
subarray starting at start and continuing for length elements, or
returns a subarray specified by range. Negative indices count
backward from the end of the array (-1 is the last element).
Returns nil if the index (or starting index) are out of range.
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
If you want to split/cut the array on an index i,
arr = arr.drop(i)
> arr = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
> arr.drop(2)
=> [3, 4, 5]
You can use slice() for this:
>> foo = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> bar = [10,20,30,40,50,60]
=> [10, 20, 30, 40, 50, 60]
>> half = foo.length / 2
=> 3
>> foobar = foo.slice(0, half) + bar.slice(half, foo.length)
=> [1, 2, 3, 40, 50, 60]
By the way, to the best of my knowledge, Python "lists" are just efficiently implemented dynamically growing arrays. Insertion at the beginning is in O(n), insertion at the end is amortized O(1), random access is O(1).
Ruby 2.6 Beginless/Endless Ranges
(..1)
# or
(...1)
(1..)
# or
(1...)
[1,2,3,4,5,6][..3]
=> [1, 2, 3, 4]
[1,2,3,4,5,6][...3]
=> [1, 2, 3]
ROLES = %w[superadmin manager admin contact user]
ROLES[ROLES.index('admin')..]
=> ["admin", "contact", "user"]
another way is to use the range method
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
a = foo[0...3]
b = bar[3...6]
print a + b
=> [1, 2, 3, 40, 50 , 60]
I like ranges for this:
def first_half(list)
list[0...(list.length / 2)]
end
def last_half(list)
list[(list.length / 2)..list.length]
end
However, be very careful about whether the endpoint is included in your range. This becomes critical on an odd-length list where you need to choose where you're going to break the middle. Otherwise you'll end up double-counting the middle element.
The above example will consistently put the middle element in the last half.

Resources