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:
- Collect the relevant elements from the original array, and sort them according to the subset order.
- 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
What's the Difference Between /\P{Alpha}/I and /\P{L}/I in Ruby
How to Interleave Arrays of Different Length in Ruby
Require': Cannot Load Such File -- Spec_Helper (Loaderror)
Uninitialized Constant > Actioncable::Server::Configuration::Applicationcable
Finding Common String in Array of Strings (Ruby)
Simple Conversion of String to Utf-8 in Ruby 1.8
Can Someone Please Explain Class << Self to Me
Undefined Method Respond_To in Rails 5 Controller
Install Ruby 2.2 on MAC Osx Catalina with Ruby-Install
How to Quickly Reorder a Ruby Array Given an Order
How to Increment/Decrement a Character in Ruby for All Possible Values
Call Before Methods in Model on Ruby
Bundle Install/Error in Installing Libv8 (3.3.10.4) on Rails (Running on Lion)
Rails Calculate Date Range in Months
How to Understand Sender and Receiver in Ruby