Consistent sorting across multiple arrays - ruby

I have the following data structure in Ruby (a hash where keys are strings, and values are arrays).
X = { "id": [2, 4, 1], "name": ["a", "b", "c"], "time": [1, 0, 2]}
I would like to sort the array associated with the field "time", but I would like all other arrays to be sorted in a consistent manner. Example: after sorting, X should look like this.
X = {"id": [4, 2, 1], "name": ["b", "a", "c"], "time": [0, 1, 2]}
I solved this in a really ugly way (because I'm not sure how to do it). What I did was create a copy of time, then zip id and time, and sort it, then zip name and time_copy and sort it. Then unzip. I'm pretty sure this is an awful way to do it. Could someone else teach me a better method?

I think you should seriously consider changing your data structure from a hash of arrays to an array of hashes if the three pieces of data are supposed to belong together. Otherwise you can get into all sorts of trouble (what would happen if you accidentally made the arrays unequal lengths, for example) - indeed, as you have found, it makes sorting rather difficult.
If you are stuck with the hash as an input format, you can convert as follows
hash = {id: [2, 4, 1], name: ["a", "b", "c"], time: [1, 0, 2]}
array = hash.map{|k,v| [k].product(v)}.transpose.map{|h| Hash[h]}
# => [{id: 2, name: "a", time: 1}, ...]
In the array of hashes format you can sort on a field extremely easily
array.sort_by{|h| h[:time]}

Using #tokland's answer to another question and applying values_at to the result:
h = { id: [2, 4, 1], name: ["a", "b", "c"], time: [1, 0, 2]}
time_indices = h[:time].each_with_index.sort_by(&:first).map(&:last)
h.values.each{|ar| ar.replace(ar.values_at(*time_indices))}
#=> {:id=>[4, 2, 1], :name=>["b", "a", "c"], :time=>[0, 1, 2]}

Almost the same as steenslag's, but I think map.with_index should be used.
permutation = X["time"].map.with_index{|*xi| xi}.sort_by(&:first).map(&:last)
X.values.each{|a| a.replace(a.values_at(*permutation))}

Related

Shuffle array with exceptions

Is there a way to shuffle all elements in an array with the exception of a specified index using the shuffle function?
Without having to manually write a method, does Ruby support anything similar?
For example, say I have an array of integers:
array = [1,2,3,4,5]
and I want to shuffle the elements in any random order but leave the first int in its place. The final result could be something like:
=> [1,4,3,2,5]
Just as long as that first element remains in its place. I've obviously found workarounds by creating my own methods to do this, but I wanted to see if there was some sort of built in function that could help cut down on time and space.
The short answer is no. Based on the latest Ruby documentation of Array.shuffle the only argument it accepts is random number generator. So you will need to write your own method - here's my take on it:
module ArrayExtender
def shuffle_except(index)
clone = self.clone
clone.delete_at(index)
clone.shuffle.insert(index, self[index])
end
end
array = %w(a b c d e f)
array.extend(ArrayExtender)
print array.shuffle_except(1) # => ["e", "b", "f", "a", "d", "c"]
print array.shuffle_except(2) # => ["e", "a", "c", "b", "f", "d"]
There is no built in function. It's still pretty easy to do that:
first element
arr = [1, 2, 3, 4, 5]
hold = arr.shift
# => 1
arr.shuffle.unshift(hold)
# => [1, 4, 5, 2, 3]
specific index
arr = [1, 2, 3, 4, 5]
index = 2
hold = arr.delete_at(index)
# => 3
arr.shuffle.insert(index, hold)
# => [5, 1, 3, 2, 4]

Ruby array of two dimensional arrays, search/lookup?

This may be very simple, but I don't know all of Ruby's array functions.
If I have a given array like:
values = [["a", 1], ["b", 3], ["c", 7], ... etc ]
I would like two functions:
A function that, when I give it "b", gives me 3.
The other way around, a function that when I give it 3, gives me "b".
There must be an easy way?
Hash[values]["b"] # => 3
Hash[values.map(&:reverse)][3] # => "b"
My first question is: Does this have to be an array? Hash is designed for this and has key / value lookup built-in.
You can create a Hash from an array by doing:
hash = Hash[values]
Then use hash["a"] # => 1
For the reverse, do: hash.key(1) # => "a"
The first is easy to achieve, by converting your array to a Hash with:
value_hash = Hash[values]
And access this with:
value_hash['b'] # => 3
For the other way around I would first like to know if you are sure that is is a unique request? So are both 'a','b','c',... and 1,3,7... etc. unique?
hash = array.to_h => Converts your array to a hash
hash[key] = value => Get the value associated with the key
hash.invert[key] = value => This method inverts your hash and you can select values
Yeah a hash is the answer, if you don't have duplicate keys of course. Otherwise you can use Array#assoc#rassoc which searches an array of arrays matching the first and last elements respectively:
ary = [["A", 1], ["B", 2], ["C", 3], ["D", 4], ["E", 5], ["F", 6], ["G", 6]]
ary.assoc('A') => ["A", 1]
ary.rassoc('3') => ["C", 3]
Note: these methods return the first matching array, not all of them.
See more at http://www.ruby-doc.org/core-2.1.2/Array.html
I see no point in creating a hash to locate a single value. Why not the simple, direct approach?
values = [["a", 1], ["b", 3], ["c", 7]]
values.find { |l,n| l=='b' }.last #=> 3
values.find { |l,n| n==3 }.first #=> "b"
Of course, neither of these deal with multiple values.

removing duplicates in array of arrays in Ruby

I have an array of arrays, like this:
aa = [ [a,d], [a,d1], [a,d], [b,d], [b,d2], [b,d3], [b,d2], [a,d2] ]
I would like to have a unique array of arrays, not just on the first element - which I can do by doing something like aa.uniq(&:first) - but rather remove the inner arrays if BOTH values match. So the result would be:
aa = [ [a,d], [a,d1], [a,d2], [b,d], [b,d2], [b,d3] ]
Can anyone assist in pointing me to an efficient way of doing this? I have large nr of arrays - in the order of 1 million - that I need to process.
Any help appreciated! John
If you need to maintain a collection of elements where each element is unique and their order is not important. You should use a Set. For instance,
require 'set'
my_set = Set.new
my_set << [1, 'a']
my_set << [1, 'a']
my_set << [1, 'b']
my_set.each { |elem| puts "#{elem}" }
It will give you
[1, "a"]
[1, "b"]
If the order is important, then use the uniq! on you array
aa.uniq!
If you want to get unique elements from an array, which will remove duplicate element, you can try this:
a = [[1, 2], [2, 3], [1, 2], [2, 3], [3, 4]]
a & a #=> [[1, 2], [2, 3], [3, 4]]
Try like this:
aa = [ ["a","d"], ["a","d1"], ["a","d"], ["b","d"] ]
aa.uniq
aa=[["a", "d"], ["a", "d1"], ["b", "d"]]
You missed double quotations ("). Inside of array, variables a, d, a, d1, etc. are strings. So, you should put them inside of double quotations ("").

Sorting by a value in an object in an array in Ruby

I have a bunch of objects in an array and would like to sort by a value that each object has. The attribute in question in each object is a numeric value.
For example:
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],]
would become:
[[1, ..bunch of other stuff],[3, ""],[5, ""],[12, ""],]
I want to sort by the numerical value stored in each of the objects.
[5, 3, 4, 1, 2] becomes [1, 2, 3, 4, 5], however these numbers are stored inside objects.
The other answers are good but not minimal. How about this?
lst.sort_by &:first
The sort method can take a block to use when comparing elements:
lst = [[1, 'foo'], [4, 'bar'], [2, 'qux']]
=> [[1, "foo"], [4, "bar"], [2, "qux"]]
srtd = lst.sort {|x,y| x[0] <=> y[0] }
=> [[1, "foo"], [2, "qux"], [4, "fbar"]]
Assuming that you want to sort only according to the first element,
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],].
sort_by{|n, *args| n}
or
[[1, ..bunch of other stuff],[5, ""],[12, ""],[3, ""],].
sort_by{|n, args| n}
When sorting objects and complex structures use sort_by. Sort_by performs a "Schwartzian Transform" which can make a major difference in sort speed.
Because you didn't provide enough information to be usable I'll recommend you read the docs linked above. You'll find its very easy to implement and can make a big difference.

Ruby removing duplicates in enumerable lists

Is there a good way in ruby to remove duplicates in enumerable lists (i.e. reject, etc.)
For array you can use uniq() method
a = [ "a", "a", "b", "b", "c" ]
a.uniq #=> ["a", "b", "c"]
so if you just
(1..10).to_a.uniq
or
%w{ant bat cat ant}.to_a.uniq
because anyway almost every methods you do implement will return as an Array class.
Well the strategy would be to convert them to arrays and remove the duplicates from the arrays. By the way lists are arrays in ruby in any case so I'm not sure what you mean by "enumerable lists"
You can do a conversion to a Set, if element order is not important.
http://www.ruby-doc.org/core/classes/Set.html
I like using the set logic operators, if the object doesn't have a .uniq method.
a = [2,3,3,5,5,5,6] # => [2, 3, 3, 5, 5, 5, 6]
a | a # => [2, 3, 5, 6]

Resources