Ruby, reorder an array based on another array? [duplicate] - ruby

This question already has answers here:
How do I quickly reorder a Ruby Array given an order?
(3 answers)
Closed 8 years ago.
I'd like to order ary according to the indices specified in order.
# Ruby
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
# Result I want
ary = ['c', 'd', 'a', 'b']

ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
ary.values_at(*order)
#=> ["c", "d", "a", "b"]

You could do something like this:
ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
sorted_array = []
order.each do |i|
sorted_array.push(ary[i])
end
Though looking at it, Cary's answer is nicer.

ary = ['a', 'b', 'c', 'd']
order = [2, 3, 0, 1]
new_array = []
order.each do |index| #This returns the array you want.
new_array << ary[index]
end

Related

Sort array a by value in array b in Ruby

I have an array
a = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
and another array:
b = [0, 3, 6, 3, 4, 0, 1]
Is it possible to sort array a according to values in array b?
The result should be:
a = ['c', 'e', 'b', 'd', 'g', 'a', 'f']
Something like this doesn't seem to exist in Ruby/Rails:
a.sort_with_index{ |elem, index| b[index] }
Edit: in response to the duplicate marking: the question being referred to has an array with elements having an ID, and the other array references the ID's directly. This is not the same, and the same solution will not apply...
a.sort_by.with_index { |_,i| [-b[i], i] }
#=> ["c", "e", "b", "d", "g", "a", "f"]
This uses the indices of elements in a to break ties. I see from a comment on #tadman's answer that that is desired, though it is not a requirement given in the statement of the question. See the third paragraph of the doc for Array#<=> for an explanation of how arrays are ordered in sorting operations.
You can just combine the two, sort, and strip out the original a values:
a.zip(b).sort_by { |_a, _b| -_b }.map { |_a,_| _a }

Use a list/set/array of values as a hash key

I'm trying to implement a hypergraph. I'd like to use a (frozen) set as the key to a hash. I'd like to do the following or something like it.
set_a = Set.new(["a","b","c"])
set_b = Set.new(["a","b","d"])
set_c = Set.new(["a","b"])
set_d = Set.new(["a","b","e","f"])
set_a.freeze
set_b.freeze
set_c.freeze
set_d.freeze
data = {
set_a => [list of vertices],
set_b.freeze => [list of vertices],
set_c.freeze => [list of vertices],
set_d.freeze => [list of vertices]
}
However, it's not quite working. Doing:
data[some_set.freeze]
seems to cause errors.
And as long as you don't expose or share the data array there is not reason to freeze it.
You could just use instances of Array as keys. But you will have to ensure that the arrays are all sorted in the same way:
data = {
['a', 'b', 'c'] => [1, 2, 3],
['a', 'b', 'd'] => [1, 2, 4],
['a', 'b'] => [1, 2],
['a', 'b', 'e', 'f'] => [1, 2, 5, 6]
}
data[['a', 'b']]
#=> [1, 2]
Or as sawa mentioned in the comments: It might make sense to use Set to avoid ordering the arrays before using with the data hash. With Set your implementation might look like:
require 'set'
data = {
Set.new(['a', 'b', 'c']) => [1, 2, 3],
Set.new(['a', 'b', 'd']) => [1, 2, 4],
Set.new(['a', 'b']) => [1, 2],
Set.new(['a', 'b', 'e', 'f']) => [1, 2, 5, 6]
}
data[Set.new(['b', 'a'])] # Note that the order doesn't match
#=> [1, 2]

Remove nil and blank string in an array in Ruby

I am new to Ruby and stuck with this issue. Let's say I have an array like this:
arr = [1, 2, 's', nil, '', 'd']
and I want to remove nil and blank string from it, i.e. final array should be:
arr = [1, 2, 's', 'd']
I tried compact but it gives this:
arr.compact!
arr #=> [1, 2, 's', '', 'd'] doesn't remove empty string.
I was wondering if there's a smart way of doing this in Ruby.
You could do this:
arr.reject { |e| e.to_s.empty? } #=> [1, 2, "s", "d"]
Note nil.to_s => ''.
Since you want to remove both nil and empty strings, it's not a duplicate of How do I remove blank elements from an array?
You want to use .reject:
arr = [1, 2, 's', nil, '', 'd']
arr.reject { |item| item.nil? || item == '' }
NOTE: reject with and without bang behaves the same way as compact with and without bang: reject! and compact! modify the array itself while reject and compact return a copy of the array and leave the original intact.
If you're using Rails, you can also use blank?. It was specifically designed to work on nil, so the method call becomes:
arr.reject { |item| item.blank? }
I tend to do:
arr = [1, 2, 's', nil, '', 'd']
arr.reject(&:blank?)
returns:
=> [1, 2, "s", "d"]
arr.reject(&:blank?)
Just use this, no need to anything else.
compact_blank (Rails 6.1+)
If you are using Rails (or a standalone ActiveSupport), starting from version 6.1, there is a compact_blank method which removes blank values from arrays.
It uses Object#blank? under the hood for determining if an item is blank.
[1, 2, 's', nil, '', 'd'].compact_blank
# => [1, 2, 's', 'd']
[1, "", nil, 2, " ", [], {}, false, true].compact_blank
# => [1, 2, true]
Here is a link to the docs and a link to the relative PR.
A destructive variant is also available. See Array#compact_blank!.
You can also use - to remove all nil and '' elements:
arr -= [nil, '']
#=> [1, 2, "s", "d"]
Demonstration
Or compact and reject with shortcut (in case you are not using Rails where you can just use arr.reject(&:blank?) ):
arr = arr.compact.reject(&''.method(:==))
#=> [1, 2, "s", "d"]
Demonstration
You can use compact with reject
arr = [1, 2, 's', nil, '', 'd']
arr = [1, 2, 's', 'd']
arr = arr.compact.reject { |h| h == "" }
or
arr = arr.compact.delete_if { |h| h == "" }
The simplest and fast way of doing this is :
arr = [1, 2, 's', nil, '', 'd'] - [nil,'']
==> arr = [1, 2, 's', 'd']
You can use compact and delete_if method to remove nil and blank string in an array in Ruby
arr = [1, 2, 's', nil, '', 'd']
arr.compact!.delete_if{|arrVal| arrVal.class == String and arrVal.empty?}
=> [1, 2, "s", "d"]
try this out:
[1, 2, "s", nil, "", "d"].compact.select{|i| !i.to_s.empty?}
Note: I am considering the array might have string with white spaces in it.
You can do:
arr = [1, 2, 's', nil, ' ', 'd']
arr.reject{|a| a.nil? || (a.to_s.gsub(' ', '') == '') }
#=> [1, 2, "s", "d"]
or:
arr.reject{|a| a.nil? || (a.to_s.gsub(' ', '').empty?) }
#=> [1, 2, "s", "d"]
or if you want to update arr object itself then:
arr.reject!{|a| a.nil? || (a.to_s.gsub(' ', '') == '') } # notice the ! mark, it'll update the object itself.
p arr #=> [1, 2, "s", "d"]
Hope this will work for your case :
arr = [1, 2, 's', nil, '', 'd']
arr.select{|x| x.to_s!="" }
I would probably add .strip to eliminate potential whitespace headaches (assuming its not a rails app).
array = [1, 2, "s", nil, " ", "d", "\n"]
array.reject!{|a| a.nil? || (a.to_s.strip.empty?) }
#=> [1, 2, "s", "d"]

How to get specific value within an array of arrays

I have an array (outside array) that contains three arrays (inside arrays), each of which have three elements.
array = [[a, b, c], [d, e, f], [g, h, i]]
I want to select the specific inside array using an index of the outside array and then select the value within the selected inside array based off its index. Here is what I tried:
array.each_index{|i| puts "letter: #{array[i[3]]} " }
I was hoping that would give me the following output
letter: c letter: f letter: i
but instead, I get
letter: [[a, b, c], [d, e, f], [g, h, i]]
I also tried
array.each_index{|i| puts "letter: #{array[i][3]} " }
but I get the same result. Please any suggestions are very appreciated. I need a simple explanation.
each_index is an Enumerable which goes through all indices and performs an action on each one. When it's done it will return your original collection as it's not its job to change it. If you want to output stuff on the screen via puts / print then each_index is fine.
If you want to create a new collection as a result of going through all the elements of an original collection, you should use map.
e.g.
array = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
new_array = array.map {|el| el[2]}
=> ["c", "f", "i"]
array.map iterates through array's elements so in every step |el| is an element, not an index, as in: ['a', 'b', 'c'] in the first iteration, ['d', 'e', 'f'] in the second one and so on...
Just pointing this out since I don't know what's the goal of what you're trying to do.
do it like this:
array.each_index{|i| puts "letter: #{array[i][2]} " }
Since you want letter at index 2, not 3.
Also array should be defined like this:
array = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
You could use map like so:
a = [['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
a.map(&:last)
# => ["c", "f", "i"]
Or if you really want the puts and not the collected values:
a.each {|v| puts "letter: #{v.last}"}
You could also use Ruby's Matrix class if there's more Matrix-y stuff you want to do:
require 'matrix'
m = Matrix[['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i']]
last_column = m.column_vectors.last
# => Vector["c", "f", "i"]
You can now use any of the Vector methods on last_column, including to_a, which will put you back in familiar territory with the last column's values.
Arrays in ruby are indexed from 0, not from 1. So:
array.each_index{|i| puts "letter: #{array[i][2]} " }
should give you what you want.
You could try the below also:
p RUBY_VERSION
arr = [[1,2,3],[4,5,6],[11,12,13]]
arr.each{|x| p x; x.each_index{|i| p "Digit :: #{x[i]}" if i == 2} }
output:
"2.0.0"
[1, 2, 3]
"Digit :: 3"
[4, 5, 6]
"Digit :: 6"
[11, 12, 13]
"Digit :: 13

Basic Array Iteration in Ruby

What's a better way to traverse an array while iterating through another array? For example, if I have two arrays like the following:
names = [ "Rover", "Fido", "Lassie", "Calypso"]
breeds = [ "Terrier", "Lhasa Apso", "Collie", "Bulldog"]
Assuming the arrays correspond with one another - that is, Rover is a Terrier, Fido is a Lhasa Apso, etc. - I'd like to create a dog class, and a new dog object for each item:
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
I can iterate through names and breeds with the following:
index = 0
names.each do |name|
Dog.new("#{name}", "#{breeds[index]}")
index = index.next
end
However, I get the feeling that using the index variable is the wrong way to go about it. What would be a better way?
dogs = names.zip(breeds).map { |name, breed| Dog.new(name, breed) }
Array#zip interleaves the target array with elements of the arguments, so
irb> [1, 2, 3].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can use arrays of different lengths (in which case the target array determines the length of the resulting array, with the extra entries filled in with nil).
irb> [1, 2, 3, 4, 5].zip(['a', 'b', 'c'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'], [4, nil], [5, nil] ]
irb> [1, 2, 3].zip(['a', 'b', 'c', 'd', 'e'])
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
You can also zip more than two arrays together:
irb> [1,2,3].zip(['a', 'b', 'c'], [:alpha, :beta, :gamma])
#=> [ [1, 'a', :alpha], [2, 'b', :beta], [3, 'c', :gamma] ]
Array#map is a great way to transform an array, since it returns an array where each entry is the result of running the block on the corresponding entry in the target array.
irb> [1,2,3].map { |n| 10 - n }
#=> [ 9, 8, 7 ]
When using iterators over arrays of arrays, if you give a multiple parameter block, the array entries will be automatically broken into those parameters:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each { |array| p array }
[ 1, 'a' ]
[ 2, 'b' ]
[ 3, 'c' ]
#=> nil
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each do |num, char|
...> puts "number: #{num}, character: #{char}"
...> end
number 1, character: a
number 2, character: b
number 3, character: c
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
Like Matt Briggs mentioned, #each_with_index is another good tool to know about. It iterates through the elements of an array, passing a block each element in turn.
irb> ['a', 'b', 'c'].each_with_index do |char, index|
...> puts "character #{char} at index #{index}"
...> end
character a at index 0
character b at index 1
character c at index 2
#=> [ 'a', 'b', 'c' ]
When using an iterator like #each_with_index you can use parentheses to break up array elements into their constituent parts:
irb> [ [1, 'a'], [2, 'b'], [3, 'c'] ].each_with_index do |(num, char), index|
...> puts "number: #{num}, character: #{char} at index #{index}"
...> end
number 1, character: a at index 0
number 2, character: b at index 1
number 3, character: c at index 2
#=> [ [1, 'a'], [2, 'b'], [3, 'c'] ]
each_with_index leaps to mind, it is a better way to do it the way you are doing it. rampion has a better overall answer though, this situation is what zip is for.
This is adapted from Flanagan and Matz, "The Ruby Programming Language", 5.3.5 "External Iterators", Example 5-1, p. 139:
++++++++++++++++++++++++++++++++++++++++++
require 'enumerator' # needed for Ruby 1.8
names = ["Rover", "Fido", "Lassie", "Calypso"]
breeds = ["Terrier", "Lhasa Apso", "Collie", "Bulldog"]
class Dog
attr_reader :name, :breed
def initialize(name, breed)
#name = name
#breed = breed
end
end
def bundle(*enumerables)
enumerators = enumerables.map {|e| e.to_enum}
loop {yield enumerators.map {|e| e.next} }
end
bundle(names, breeds) {|x| p Dog.new(*x) }
+++++++++++++++++++++++++++++++++++++++++++
Output:
#<Dog:0x10014b648 #name="Rover", #breed="Terrier">
#<Dog:0x10014b0d0 #name="Fido", #breed="Lhasa Apso">
#<Dog:0x10014ab80 #name="Lassie", #breed="Collie">
#<Dog:0x10014a770 #name="Calypso", #breed="Bulldog">
which I think is what we wanted!
As well as each_with_index (mentioned by Matt), there's each_index. I sometimes use this because it makes the program more symmetrical, and therefore wrong code will look wrong.
names.each_index do |i|
name, breed = dogs[i], breeds[i] #Can also use dogs.fetch(i) if you want to fail fast
Dog.new(name, breed)
end

Resources