How to Quickly Reorder a Ruby Array Given an Order

How do I quickly reorder a Ruby Array given an order?

data =  ["0", "1", "2", "3", "4", "5"]

order = [3, 1, 2, 0, 4, 5]

> order.map{|x| data[x]}
=> ["3", "1", "2", "0", "4", "5"]

If you are not sure if the indices are correct, you can do this:

> order.map{|x| data.fetch(x)}     # will raise an exception if index out of bounds
=> ["3", "1", "2", "0", "4", "5"]

How to sort an array in Ruby to a particular order?

Array#sort_by is what you're after.

a.sort_by do |element|
b.index(element)
end

More scalable version in response to comment:

a=["one", "two", "three"]
b=["two", "one", "three"]

lookup = {}
b.each_with_index do |item, index|
lookup[item] = index
end

a.sort_by do |item|
lookup.fetch(item)
end

sorting and rearranging an array of hashes based on multiple conditions

Here is another option using what you already have as a base (Since you were basically all the way there)

a = [
{ "name" => "X", "year" => "2013-08"},
{ "name" => "A", "year" => "2017-01"},
{ "name" => "X", "year" => "2000-08"},
{ "name" => "B", "year" => "2018-05"},
{ "name" => "D", "year" => "2016-04"},
{ "name" => "C", "year" => "2016-04"}
]

a.sort do |a,b|
a_ord, b_ord = [a,b].map {|e| e["name"] == "X" ? 0 : 1 }
[a_ord,b["year"],a["name"] ] <=> [b_ord, a["year"],b["name"]]
end

Here we just make sure that "X" is always in front by assigning it a 0 and everything else a 1. Then since 0 and 0 would be equivalent X will fall back to the same logic you already have applied as will all the others. We can make this a bit fancier as:

a.sort do  |a,b| 
[a,b].map {|e| e["name"] == "X" ? 0 : 1 }.zip(
[b["year"],a["year"]],[a["name"],b["name"]]
).reduce(:<=>)
end

ruby - how to reorder an array based on another array?

Is this what you want?

a = [1,2,3,4,5,6]

n = 2
b = a[-n, n] + a[n..-(n+1)] + a[0,n]

p a # => [1,2,3,4,5,6]
p b # => [5,6,3,4,1,2]

How to sort an array in descending order in Ruby

It's always enlightening to do a benchmark on the various suggested answers. Here's what I found out:


#!/usr/bin/ruby

require 'benchmark'

ary = []
1000.times {
ary << {:bar => rand(1000)}
}

n = 500
Benchmark.bm(20) do |x|
x.report("sort") { n.times { ary.sort{ |a,b| b[:bar] <=> a[:bar] } } }
x.report("sort reverse") { n.times { ary.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } }
x.report("sort_by -a[:bar]") { n.times { ary.sort_by{ |a| -a[:bar] } } }
x.report("sort_by a[:bar]*-1") { n.times { ary.sort_by{ |a| a[:bar]*-1 } } }
x.report("sort_by.reverse!") { n.times { ary.sort_by{ |a| a[:bar] }.reverse } }
end

user system total real
sort 3.960000 0.010000 3.970000 ( 3.990886)
sort reverse 4.040000 0.000000 4.040000 ( 4.038849)
sort_by -a[:bar] 0.690000 0.000000 0.690000 ( 0.692080)
sort_by a[:bar]*-1 0.700000 0.000000 0.700000 ( 0.699735)
sort_by.reverse! 0.650000 0.000000 0.650000 ( 0.654447)

I think it's interesting that @Pablo's sort_by{...}.reverse! is fastest. Before running the test I thought it would be slower than "-a[:bar]" but negating the value turns out to take longer than it does to reverse the entire array in one pass. It's not much of a difference, but every little speed-up helps.


Please note that these results are different in Ruby 1.9

Here are results for Ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-darwin10.8.0]:

                           user     system      total        real
sort 1.340000 0.010000 1.350000 ( 1.346331)
sort reverse 1.300000 0.000000 1.300000 ( 1.310446)
sort_by -a[:bar] 0.430000 0.000000 0.430000 ( 0.429606)
sort_by a[:bar]*-1 0.420000 0.000000 0.420000 ( 0.414383)
sort_by.reverse! 0.400000 0.000000 0.400000 ( 0.401275)

These are on an old MacBook Pro. Newer, or faster machines, will have lower values, but the relative differences will remain.


Here's a bit updated version on newer hardware and the 2.1.1 version of Ruby:

#!/usr/bin/ruby

require 'benchmark'

puts "Running Ruby #{RUBY_VERSION}"

ary = []
1000.times {
ary << {:bar => rand(1000)}
}

n = 500

puts "n=#{n}"
Benchmark.bm(20) do |x|
x.report("sort") { n.times { ary.dup.sort{ |a,b| b[:bar] <=> a[:bar] } } }
x.report("sort reverse") { n.times { ary.dup.sort{ |a,b| a[:bar] <=> b[:bar] }.reverse } }
x.report("sort_by -a[:bar]") { n.times { ary.dup.sort_by{ |a| -a[:bar] } } }
x.report("sort_by a[:bar]*-1") { n.times { ary.dup.sort_by{ |a| a[:bar]*-1 } } }
x.report("sort_by.reverse") { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse } }
x.report("sort_by.reverse!") { n.times { ary.dup.sort_by{ |a| a[:bar] }.reverse! } }
end

# >> Running Ruby 2.1.1
# >> n=500
# >> user system total real
# >> sort 0.670000 0.000000 0.670000 ( 0.667754)
# >> sort reverse 0.650000 0.000000 0.650000 ( 0.655582)
# >> sort_by -a[:bar] 0.260000 0.010000 0.270000 ( 0.255919)
# >> sort_by a[:bar]*-1 0.250000 0.000000 0.250000 ( 0.258924)
# >> sort_by.reverse 0.250000 0.000000 0.250000 ( 0.245179)
# >> sort_by.reverse! 0.240000 0.000000 0.240000 ( 0.242340)

New results running the above code using Ruby 2.2.1 on a more recent Macbook Pro. Again, the exact numbers aren't important, it's their relationships:

Running Ruby 2.2.1
n=500
user system total real
sort 0.650000 0.000000 0.650000 ( 0.653191)
sort reverse 0.650000 0.000000 0.650000 ( 0.648761)
sort_by -a[:bar] 0.240000 0.010000 0.250000 ( 0.245193)
sort_by a[:bar]*-1 0.240000 0.000000 0.240000 ( 0.240541)
sort_by.reverse 0.230000 0.000000 0.230000 ( 0.228571)
sort_by.reverse! 0.230000 0.000000 0.230000 ( 0.230040)

Updated for Ruby 2.7.1 on a Mid-2015 MacBook Pro:

Running Ruby 2.7.1
n=500
user system total real
sort 0.494707 0.003662 0.498369 ( 0.501064)
sort reverse 0.480181 0.005186 0.485367 ( 0.487972)
sort_by -a[:bar] 0.121521 0.003781 0.125302 ( 0.126557)
sort_by a[:bar]*-1 0.115097 0.003931 0.119028 ( 0.122991)
sort_by.reverse 0.110459 0.003414 0.113873 ( 0.114443)
sort_by.reverse! 0.108997 0.001631 0.110628 ( 0.111532)

...the reverse method doesn't actually return a reversed array - it returns an enumerator that just starts at the end and works backwards.

The source for Array#reverse is:

               static VALUE
rb_ary_reverse_m(VALUE ary)
{
long len = RARRAY_LEN(ary);
VALUE dup = rb_ary_new2(len);

if (len > 0) {
const VALUE *p1 = RARRAY_CONST_PTR_TRANSIENT(ary);
VALUE *p2 = (VALUE *)RARRAY_CONST_PTR_TRANSIENT(dup) + len - 1;
do *p2-- = *p1++; while (--len > 0);
}
ARY_SET_LEN(dup, RARRAY_LEN(ary));
return dup;
}

do *p2-- = *p1++; while (--len > 0); is copying the pointers to the elements in reverse order if I remember my C correctly, so the array is reversed.

Seriously stuck on Ruby array reordering

Check out TSort, which comes with the Ruby standard library.

It performs a topological sort, which sounds like what you need. Using your example above:

require 'tsort'

class Hash
include TSort
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end

def deps arr
arr.map { |head, *tail| {head => tail} }.reduce(&:merge).tsort
end

deps [['a'], ['b','c'], ['c','a']]
#=> ['a', 'c', 'b']

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

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"]

Ruby, reorder an array based on another array?

ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]

ary.values_at(*order)
#=> ["c", "d", "a", "b"]

How to sort a ruby array in ascending order but keep zero last

So devise a comparator to do that ...

if x.total == 0
# always consider 0 "largest" and no 0 can be larger than another
# (make sure 0.0 is 0 and not a number really close to 0)
# perhaps x or y should be first for other reasons as well?
1
else
# otherwise lower to higher as normal
x.total <=> y.total
end

Or without comments:

foo.sort {|x, y| if x.total == 0 then 1 else x.total <=> y.total end}

Happy coding.

Ruby reorder an array based on the contents of a subset array

My solution has two steps:

  1. Collect the relevant elements from the original array, and sort them according to the subset order.
  2. Replace them in the original array with the new order.

Here is the code:

mapped_elements = subset.map { |i| original.find { |j| j.keys == i.keys } }

result = original.map do |i|
if subset.find { |j| j.keys == i.keys }
mapped_elements.shift
else
i
end
end

For subset = [{c: "Gill", points: 2}, {b: "Will", points: 1}] the result will be:

[{a: "Bill", points: 4}, {c: "Gill", points: 2}, {b: "Will", points: 3}, {d: "{Pill}", points: 1}]

For subset = [{c: "Gill", points: 3}, {b: "Will", points: 2}, {a: "Bill", points: 1}] the result will be:

[{c: "Gill", points: 3}, {b: "Will", points: 2}, {a: "Bill", points: 4}, {d: "Pill", points: 1}] 


Related Topics



Leave a reply



Submit