How to sort only specific elements in an array? - ruby

I have an array of mixed elements, e.g. integers and strings:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
and I want to apply a sort but only to specific elements. In the above example, the elements to-be-sorted are the integers (but it could be anything). However, even though they should be sorted, they have to stay in their "integer spots". And the strings have to remain in their exact positions.
The sort should work like this:
[3, "foo", 2, 5, "bar", 1, "baz", 4] # before
[1, "foo", 2, 3, "bar", 4, "baz", 5] # after
"foo" is still at index 1, "bar" is at index 4 and "baz" is at index 6.
I could partition the array into integers and non-integers along with their positions:
a, b = ary.each_with_index.partition { |e, i| e.is_a?(Integer) }
a #=> [[3, 0], [2, 2], [5, 3], [1, 5], [4, 7]]
b #=> [["foo", 1], ["bar", 4], ["baz", 6]]
sort the integers:
result = a.map(&:first).sort
#=>[1, 2, 3, 4, 5]
And re-insert the non-integers at their original positions:
b.each { |e, i| result.insert(i, e) }
result
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
But this seems rather clumsy. I particular dislike having to deconstruct and rebuild the array one string at a time. Is there a more elegant or more direct approach?

Possible solution
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
integers = ary.select(&->(el) { el.is_a?(Integer) }).sort
ary.map { |n| n.is_a?(Integer) ? integers.shift : n }
# => [1, "foo", 2, 3, "bar", 4, "baz", 5]

I am not proficient with Ruby. Though I'd like to take a shot at what I could come up with.
The idea is as evoked in my comment.
get the indices of the integers
sort the values of the indices
insert the sorted values back into array
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
=> [3, "foo", 2, 5, "bar", 1, "baz", 4]
indices = ary.map.with_index { |item,idx| idx if item.is_a?(Integer) }.compact
=> [0, 2, 3, 5, 7]
values = ary.values_at(*indices).sort
=> [1, 2, 3, 4, 5]
indices.zip(values).each { |idx, val| ary[idx]=val}
=> [[0, 1], [2, 2], [3, 3], [5, 4], [7, 5]]
ary
=> [1, "foo", 2, 3, "bar", 4, "baz", 5]

I have assumed that, as in the example, if arr = ary.dup and arr is modified ary is not mutated. If that is not the case one must work with a deep copy of ary.
A helper:
def sort_object?(e)
e.class == Integer
end
sort_object?(3)
#=> true
sort_object?(-3e2)
#=> true
sort_object?("foo")
#=> false
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
obj_to_idx = ary.zip((0..ary.size-1).to_a).to_h
#=> {3=>0, "foo"=>1, 2=>2, 5=>3, "bar"=>4, 1=>5, "baz"=>6, 4=>7}
to_sort = ary.select { |k| sort_object?(k) }
#=> [3, 2, 5, 1, 4]
sorted = to_sort.sort
#=> [1, 2, 3, 4, 5]
new_idx_to_orig_idx = to_sort.map { |n| obj_to_idx[n] }
.zip(sorted.map { |n| obj_to_idx[n] })
.to_h
#=> {0=>5, 2=>2, 3=>0, 5=>7, 7=>3}
new_idx_to_orig_idx.each_with_object(ary.dup) do |(new_idx,orig_idx),a|
a[new_idx] = ary[orig_idx]
end
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
Some of these statements may of course be chained if desired.

In-Place Re-Assignment at Designated Array Indices
You could certainly make this shorter, and perhaps even skip converting things to and from Hash objects, but the intermediate steps there are to show my thought process and make the intent more explicit and debugging a bit easier. Consider the following:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
ints = ary.each_with_index.select { |elem, idx| [elem, idx] if elem.kind_of? Integer }.to_h
ordered_ints = ints.keys.sort.zip(ints.values).to_h
ints.keys.sort.zip(ints.values).each { |elem, idx| ary[idx] = elem }
ary
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
The idea here is that we:
Select just the items of the type we want to sort, along with their index within the current Array using #each_with_index. If you don't like #kind_of? you could replace it with #respond_to? or any other selection criteria that makes sense to you.
Sort Array values selected, and then #zip them up along with the index locations we're going to modify.
Re-assign the sorted elements for each index that needs to be replaced.
With this approach, all unselected elements remain in their original index locations within the ary Array. We're only modifying the values of specific Array indices with the re-ordered items.

I will throw my hat in this ring as well.
It appears the other answers rely on the assumption of uniqueness so I took a different route by building a positional transliteration Hash
(Update: took it a little further than necessary)
def sort_only(ary, sort_klass: Integer)
raise ArgumentError unless sort_klass.is_a?(Class) && sort_klass.respond_to?(:<=>)
# Construct a Hash of [Object,Position] => Object
translator = ary
.each_with_index
.with_object({}) {|a,obj| obj[a] = a.first}
.tap do |t|
# select the keys where the value (original element) is a sort_klass
t.filter_map {|k,v| k if v.is_a?(sort_klass)}
.then do |h|
# sort them and then remap these keys to point at the sorted value
h.sort.each_with_index do |(k,_),idx|
t[h[idx]] = k
end
end
end
# loop through the original array with its position index and use
# the transliteration Hash to place them in the correct order
ary.map.with_index {|a,i| translator[[a,i]]}
end
Example: (Working Example)
sort_only([3, "foo", 2, 5, "bar", 1, "baz", 4])
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]
sort_only([3, 3, "foo", 2, 5, "bar", 1, "baz",12.0,5,Object, 4,"foo", 7, -1, "qux"])
#=> [-1, 1, "foo", 2, 3, "bar", 3, "baz", 12.0, 4, Object, 5, "foo", 5, 7, "qux"]
sort_only([3, "foo", 2, 5,"qux", "bar", 1, "baz", 4], sort_klass: String)
#=> [3, "bar", 2, 5, "baz", "foo", 1, "qux", 4]
For Reference the Second Example produces the following transliteration:
{[3, 0]=>-1, [3, 1]=>1, ["foo", 2]=>"foo", [2, 3]=>2, [5, 4]=>3,
["bar", 5]=>"bar", [1, 6]=>3, ["baz", 7]=>"baz", [12.0, 8]=>12.0,
[5, 9]=>4, [Object, 10]=>Object, [4, 11]=>5, ["foo", 12]=>"foo",
[7, 13]=>5, [-1, 14]=>7, ["qux", 15]=>"qux"}

Below code simply creates two new arrays: numbers and others. numbers are Integer instances from original array, later sorted in descending order. others looks like original array but number spots replaced with nil. at the end push that sorted numbers to nil spots
arr.inject([[], []]) do |acc, el|
el.is_a?(Integer) ? (acc[0]<<el; acc[1]<<nil) : acc[1]<<el; acc
end.tap do |titself|
numbers = titself[0].sort_by {|e| -e }
titself[1].each_with_index do |u, i|
titself[1][i] = numbers.pop unless u
end
end.last

I'm going to answer my own question here with yet another approach to the problem. This solution delegates most work to Ruby's built-in methods and avoids explicit blocks / loops as far as possible.
Starting with the input array:
ary = [3, "foo", 2, 5, "bar", 1, "baz", 4]
You could build a hash of position => element pairs:
hash = ary.each_index.zip(ary).to_h
#=> {0=>3, 1=>"foo", 2=>2, 3=>5, 4=>"bar", 5=>1, 6=>"baz", 7=>4}
extract the pairs having integer value: (or whatever you want to sort)
ints_hash = hash.select { |k, v| v.is_a?(Integer) }
#=> {0=>3, 2=>2, 3=>5, 5=>1, 7=>4}
sort their values: (any way you want)
sorted_ints = ints_hash.values.sort
#=> [1, 2, 3, 4, 5]
build a new mapping for the sorted values:
sorted_ints_hash = ints_hash.keys.zip(sorted_ints).to_h
#=> {0=>1, 2=>2, 3=>3, 5=>4, 7=>5}
update the position hash:
hash.merge!(sorted_ints_hash)
#=> {0=>1, 1=>"foo", 2=>2, 3=>3, 4=>"bar", 5=>4, 6=>"baz", 7=>5}
And voilà:
hash.values
#=> [1, "foo", 2, 3, "bar", 4, "baz", 5]

Related

Operate on array elements without changing index

I'm trying to operate on certain elements of an array while referencing their index in the block. Operating on the whole array is easy
arr = [1, 2, 3, 4, 5, 6, 7, 8]
arr.each_with_index { |num, index| puts "#{num}, "#{index}" }
But what if I want to work just with elements 4, 6 to return
4, 3
6, 5
I can create a new array composed of certain elements of the original and run the block on that, but then the index changes.
How can I select the elements and their index?
Just put a condition on it:
indice = [3, 5]
arr.each_with_index do
|num, index| puts "#{num}, #{index}" if indice.include?(index)
end
This is another style:
indice = [3, 5]
arr.each_with_index do
|num, index|
next unless indice.include?(index)
puts "#{num}, #{index}"
end
I cannot tell from the question whether you are given values in the array and want to obtain their indices, or vice-versa. I therefore will suggest one method for each task. I will use this array for examples:
arr = [1, 2, 3, 4, 5, 6, 7, 8]
Values to Indices
If you are given values:
vals = [4, 6]
you can retrieve the number-index pairs like this:
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [6, 5]]
or print them directly:
vals.each { |num| puts "#{num}, #{arr.index(num)}" }
# 4, 3
# 6, 5
#=> [4, 6]
If an element of vals is not present in arr:
vals = [4, 99]
vals.map { |num| [num, arr.index(num)] }
#=> [[4, 3], [99, nil]]
Indices to Values
If you are given indices:
indices = [3, 5]
you can retrieve the index-value pairs like this:
indices.zip(arr.values_at(*indices))
#=> [[3, 4], [5, 6]]
and then print in whatever format you like.
If an index is out-of-range, nil will be returned:
indices.zip(arr.values_at(*[3, 99]))
#=> [[3, 4], [5, nil]]

Conditionally inserting data into an array

Alright, can someone help me how to properly iterate a dynamic size array?
Here's what I mean:
my_array = [1, 2, 3, 4, 2, 5, 15, 2] # <= there are three 2 inside my_array
Then I would like to add "good" everytime the iteration hit integer 2, I tried several method but not find the way, here's the best method I've tried(still not resulting in what I want)
# First Method
for i in 0..(my_array.length - 1)
my_array.insert(i + 1, "good") if my_array[i] == 2
end
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2]
# Second Method
for i in 0..(my_array.length - 1)
my_array[i + 1] = "good" if my_array[i] == 2
end
p my_array # => [1, 2, "good", 4, 2, "good", 15, 2, "good"]
The first method is not good because it's not showing "good" after the last 2, I guess this because the iteration could not reach the last integer 2(in the last array) and that is expected because the array size is changed bigger everytime "good" is inserted.
The second one is also bad, because I replace the data after every 2 with "good" string.
Now can someone point it out to me how can I doing this properly so I can produce it like this:
p my_array # => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
All "good" is added without replacing any data.
Any help is appreciated, thank you very much.
You'd have a better time transforming this into a new array than modifying in-place:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
def add_good(a)
a.flat_map do |value|
case (value)
when 2
[ 2, 'good' ]
else
value
end
end
end
puts add_good(my_array).inspect
# => [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
The flat_map method is useful for situations where you want to create zero or more entries in the resulting array. map is a 1:1 mapping, flat_map is a 1:N.
It's also possible to make this much more generic:
def fancy_insert(a, insert)
a.flat_map do |value|
if (yield(value))
[ value, insert ]
else
value
end
end
end
result = fancy_insert(my_array, 'good') do |value|
value == 2
end
puts result.inspect
That way you can pass in an arbitrary block and value to be inserted.
Why not using the .each_with_index method:
arr = [1, 2, 3, 4, 5, 2, 3, 5]
arr.each_with_index {|e, i| arr.insert(i+1, "good") if e == 2}
Fast and furious.
Here's another way you can do it, using an Enumerator:
my_array = [1, 2, 3, 4, 2, 5, 15, 2]
enum = my_array.each
#=> #<Enumerator: [1, 2, 3, 4, 2, 5, 15, 2]:each>
my_array = []
loop do
x = enum.next
my_array << x
my_array << "good" if x == 2
end
my_array
#=> [1, 2, "good", 3, 4, 2, "good", 5, 15, 2, "good"]
Enumerator#next raises a StopInteration exception when the enumerator is already on the last element. Kernel#loop handles the exception by breaking the loop. That's why you will often see loop used when stepping through an enumerator.

Ruby enumerator chaining

In this example,
[1, 2, 3].each_with_index.map{|i, j| i * j}
# => [0, 2, 6]
my understanding is that, since each_with_index enumerator is chained to map, map behaves like each_with_index by passing an index inside the block, and returns a new array.
For this,
[1, 2, 3].map.each_with_index{|i, j| i * j}
# => [0, 2, 6]
I'm not sure how to I interpret it.
In this example,
[1, 2, 3, 4].map.find {|i| i == 2}
# => 2
I was expecting the the output to be [2], assuming that map is chained to find, and map would return a new array.
Also, I see this:
[1, 2, 3, 4].find.each_with_object([]){|i, j| j.push(i)}
# => [1]
[1, 2, 3, 4].each_with_object([]).find{|i, j| i == 3}
# => [3, []]
Can you let me know how to interpret and understand enumerator chains in Ruby?
You might find it useful to break these expressions down and use IRB or PRY to see what Ruby is doing. Let's start with:
[1,2,3].each_with_index.map { |i,j| i*j }
Let
enum1 = [1,2,3].each_with_index
#=> #<Enumerator: [1, 2, 3]:each_with_index>
We can use Enumerable#to_a (or Enumerable#entries) to convert enum1 to an array to see what it will be passing to the next enumerator (or to a block if it had one):
enum1.to_a
#=> [[1, 0], [2, 1], [3, 2]]
No surprise there. But enum1 does not have a block. Instead we are sending it the method Enumerable#map:
enum2 = enum1.map
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:each_with_index>:map>
You might think of this as a sort of "compound" enumerator. This enumerator does have a block, so converting it to an array will confirm that it will pass the same elements into the block as enum1 would have:
enum2.to_a
#=> [[1, 0], [2, 1], [3, 2]]
We see that the array [1,0] is the first element enum2 passes into the block. "Disambiguation" is applied to this array to assign the block variables the values:
i => 1
j => 0
That is, Ruby is setting:
i,j = [1,0]
We now can invoke enum2 by sending it the method each with the block:
enum2.each { |i,j| i*j }
#=> [0, 2, 6]
Next consider:
[1,2,3].map.each_with_index { |i,j| i*j }
We have:
enum3 = [1,2,3].map
#=> #<Enumerator: [1, 2, 3]:map>
enum3.to_a
#=> [1, 2, 3]
enum4 = enum3.each_with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:each_with_index>
enum4.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum4.each { |i,j| i*j }
#=> [0, 2, 6]
Since enum2 and enum4 pass the same elements into the block, we see this is just two ways of doing the same thing.
Here's a third equivalent chain:
[1,2,3].map.with_index { |i,j| i*j }
We have:
enum3 = [1,2,3].map
#=> #<Enumerator: [1, 2, 3]:map>
enum3.to_a
#=> [1, 2, 3]
enum5 = enum3.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:map>:with_index>
enum5.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum5.each { |i,j| i*j }
#=> [0, 2, 6]
To take this one step further, suppose we had:
[1,2,3].select.with_index.with_object({}) { |(i,j),h| ... }
We have:
enum6 = [1,2,3].select
#=> #<Enumerator: [1, 2, 3]:select>
enum6.to_a
#=> [1, 2, 3]
enum7 = enum6.with_index
#=> #<Enumerator: #<Enumerator: [1, 2, 3]:select>:with_index>
enum7.to_a
#=> [[1, 0], [2, 1], [3, 2]]
enum8 = enum7.with_object({})
#=> #<Enumerator: #<Enumerator: #<Enumerator: [1, 2, 3]:
# select>:with_index>:with_object({})>
enum8.to_a
#=> [[[1, 0], {}], [[2, 1], {}], [[3, 2], {}]]
The first element enum8 passes into the block is the array:
(i,j),h = [[1, 0], {}]
Disambiguation is then applied to assign values to the block variables:
i => 1
j => 0
h => {}
Note that enum8 shows an empty hash being passed in each of the three elements of enum8.to_a, but of course that's only because Ruby doesn't know what the hash will look like after the first element is passed in.
Methods you are mentioning are defined on Enumerable objects. These methods behave differently depending on whether you pass a block or not.
When you do not pass a block, they typically return an Enumerator object, to which you can chain further methods like each_with_index, with_index, map, etc.
When you pass a block to these methods, they return different kinds of object depending on what will make sense for that particular method.
For methods like find, its purpose is to find the first object that satisfies a condition, and it does not make particular sense to wrap that in an array, so it returns that object bare.
For methods like select or reject, their purpose is to return all relevant objects, so they cannot return a single object, and they have to be wrapped in an array (even when the relevant object happens to be a single object).

Returning all maximum or minimum values that can be multiple

Enumerable#max_by and Enumerable#min_by return one of the relevant elements (presumably the first one) when there are multiple max/min elements in the receiver. For example, the following:
[1, 2, 3, 5].max_by{|e| e % 3}
returns only 2 (or only 5).
Instead, I want to return all max/min elements and in an array. In the example above, it would be [2, 5] (or [5, 2]). What is the best way to get this?
arr = [1, 2, 3, 5]
arr.group_by{|a| a % 3} # => {1=>[1], 2=>[2, 5], 0=>[3]}
arr.group_by{|a| a % 3}.max.last # => [2, 5]
arr=[1, 2, 3, 5, 7, 8]
mods=arr.map{|e| e%3}
find max
max=mods.max
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(max)}
arr.select.with_index{|a,i| indices.include?(i)}
find min
min = mods.min
indices = []
mods.each.with_index{|m, i| indices << i if m.eql?(min)}
arr.select.with_index{|a,i| indices.include?(i)}
Sorry for clumsy code, will try to make it short.
Answer by #Sergio Tulentsev is the best and efficient answer, found things to learn there. +1
This is the hash equivalent of #Serio's use of group_by.
arr = [1, 2, 3, 5]
arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }.max.last
#=> [2, 5]
The steps:
h = arr.each_with_object(Hash.new { |h,k| h[k] = [] }) { |e,h| h[e%3] << e }
#=> {1=>[1], 2=>[2, 5], 0=>[3]}
a = h.max
#=> [2, [2, 5]]
a.last
#=> [2, 5]

How to make an array containing a duplicate of an array

I couldn't find a way to build an array such as
[ [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] , [1,2,3] ]
given [1,2,3] and the number 5. I guess there are some kind of operators on arrays, such as product of mult, but none in the doc does it. Please tell me. I missed something very simple.
Array.new(5, [1, 2, 3]) or Array.new(5) { [1, 2, 3] }
Array.new(size, default_object) creates an array with an initial size, filled with the default object you specify. Keep in mind that if you mutate any of the nested arrays, you'll mutate all of them, since each element is a reference to the same object.
array = Array.new(5, [1, 2, 3])
array.first << 4
array # => [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
Array.new(size) { default_object } lets you create an array with separate objects.
array = Array.new(5) { [1, 2, 3] }
array.first << 4
array #=> [[1, 2, 3, 4], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
Look up at the very top of the page you linked to, under the section entitled "Creating Arrays" for some more ways to create arrays.
Why not just use:
[[1, 2, 3]] * 5
# => [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
The documentation for Array.* says:
...returns a new array built by concatenating the int copies of self.
Typically we'd want to create a repeating single-level array:
[1, 2, 3] * 2
# => [1, 2, 3, 1, 2, 3]
but there's nothing to say we can't use a sub-array like I did above.
It looks like mutating one of the subarrays mutates all of them, but that may be what someone wants.
It's like Array.new(5, [1,2,3]):
foo = [[1, 2, 3]] * 2
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
foo = Array.new(2, [1,2,3])
foo[0][0] = 4
foo # => [[4, 2, 3], [4, 2, 3]]
A work-around, if that's not the behavior wanted is:
foo = ([[1, 2, 3]] * 2).map { |a| [*a] }
foo[0][0] = 4
foo # => [[4, 2, 3], [1, 2, 3]]
But, at that point it's not as convenient, so I'd use the default Array.new(n) {…} behavior.

Resources