Sorting alpha-numeric array? - ruby

I would like to sort an alphanumeric array in Ruby and return a sorted array back.
For example:
["world", "jim", 4, 1, "apple"]
to return:
["apple", "jim", 1, 4, "world"]
so that where there is an object of the same class in the same position before the sort, just now it's numerical/alphabetical.

xs = ["world", "jim", 4, 1, "apple", 5, 6]
sorted_by_cls = xs.group_by(&:class).each { |k,vs| vs.sort!.reverse! }
sorted_xs = xs.map(&:class).map { |c| sorted_by_cls[c].pop }

Don't know how this compares to other solutions, but here's another one:
xs = ["world", "jim", 4, 1, "apple", 5, 6]
classes = xs.map(&:class)
sorts = Hash[*classes.uniq.map {|c| [c, xs.select {|x| x.class == c}.sort]}.flatten(1)]
classes.map {|c| sorts[c].shift} # => ["apple", "jim", 1, 4, "world", 5, 6]

A very interesting problem. Here's how I approached it assuming an alphanumeric array. Partition the array into alpha and numeric subarrays, sort them, and reconstitute based on the position and class of the object in the original array.
arr = ["world", "jim", 4, 1, "apple"]
alpha_num = arr.partition { |l| l.is_a? String }.map(&:sort)
arr.map { |l| l.is_a?(String) ? alpha_num.first.shift : alpha_num.last.shift }
My generalized solution is not much different than that of FMc:
arr = ["world", "jim", 4, 1, "apple", 5, 6]
sorted_hash = arr.group_by(&:class).each_value(&:sort!)
arr.map { |l| sorted_hash[l.class].shift }

arr = ["world", "jim", 4, 1, "apple"]
arr.each_with_index.group_by {|e| e.first.class}.values.map {|g|\
g.map(&:first).sort.zip(g.map(&:last).sort)}\
.each_with_object(Array.new(arr.size)) {|e,a| e.each {|f,i| a[i] = f}}
# => ["apple", "jim", 1, 4, "world"]
Let's go through this:
a1 = arr.each.with_index.group_by {|e| e.first.class} # => {String=>[["world", 0], ["jim", 1], ["apple", 4]], Fixnum=>[[4, 2], [1, 3]]}
a21, a22 = a1.values #=>[[["world",0],["jim",1],["apple",4]],[[4,2],[1,3]]]
a31 = a21.map(&:first) # => ["world", "jim", "apple"]
a41 = a31.sort # => ["apple", "jim", "world"]
a51 = a21.map(&:last).sort # => [0, 1, 4]
a61 = a41.zip(a51) # => [["apple", 0], ["jim", 1], ["world", 4]]
a62 = a22.map(&:first).sort.zip(a22.map(&:last).sort) # => [[1,2], [4,3]]
[a61, a62].each_with_object(Array.new(5)) {|e,a| e.each {|f,i| a[i] = f}}

a = ["world", "jim", 4, 1, "apple", "cabbage"]
is = a.each_index.group_by{|i| a[i].class}.values
.flat_map{|is| [is, is.sort_by{|i| a[i]}].transpose}
.sort.map(&:last)
a.values_at(*is)

Related

How to sort only specific elements in an array?

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]

Group hash of arrays by array element

I have this data structure resulted from a query grouping
{
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
I want to manipulate it so I end up with a structure grouped like this
{
0 => {
'AR' => 2,
'AQ' => 6,
nil => 1
},
1 => {
'AQ' => 1,
nil => 4
},
2 => {
'BG' => 1,
nil => 1
}
}
input = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
result = {}
input.each do |k, v|
if result[k[0]]
result[k[0]].merge!({ k[1] => v })
else
result[k[0]] = { k[1] => v }
end
end
puts result
#{0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
I think this is not the most succinct way, I hope some advice!
hash = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
new_hash = {}
hash.each{|k, v| new_hash[k[0]] ||= {}; new_hash[k[0]].merge!({k[1] => v})}
puts new_hash # {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
Here is one more very similar to previous answers but with using of #each_with_object:
hash = {
[0, "AR"]=>2,
[0, nil]=>1,
[0, "AQ"]=>6,
[1, nil]=>4,
[1, "AQ"]=>3,
[2, "BG"]=>1,
[2, nil]=>1,
}
result_hash = Hash.new { |h,k| h[k] = {} }
hash.each_with_object(result_hash) do |((parrent_key, key), value), res|
res[parrent_key].merge!(key => value)
end
=> {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
I came up with an answer that doesn't require additional variable assignments in its enclosing scope (it has "referential transparency": https://en.wikipedia.org/wiki/Referential_transparency)
input
.group_by { |(arr, num)| arr.first }
.each_with_object(Hash.new) do |(key, vals), hsh|
vals.each do |((key, innerkey), innerval)|
hsh[key] ||= {}
hsh[key][innerkey] = innerval
end
hsh
end
# {0=>{"AR"=>2, nil=>1, "AQ"=>6}, 1=>{nil=>4, "AQ"=>3}, 2=>{"BG"=>1, nil=>1}}
Two high-level steps:
I noticed the output object is grouped by the first array element (here, 0/1/2). I use #group_by to create a hash with that structure.
# output of `#group_by` on first array element:
key: 0, vals: [ [[0, "AR"], 2], [[0, nil], 1], [[0, "AQ"], 6] ]
key: 1, vals: [ [[1, nil], 4], [[1, "AQ"], 3] ]
key: 2, vals: [ [[2, "BG"], 1], [[2, nil], 1] ]
I use #each_with_object to construct the nested hashes. For each vals array above, I extracted the second and third values by destructuring the arrays in the block parameter (((key, innerkey), innerval)) and then the hash assignment was straightforward.

How to use collect and include for multidimensional array

I have:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
I want to replace elements in array1 that are included in #student_ids with 'X'. I want to see:
[['X','X','X',4,5],[7,8,9,10],[11,12,13,14]]
I have code that is intended to do this:
array1.collect! do |i|
if i.include?(#student_ids) #
i[i.index(#student_ids)] = 'X'; i # I want to replace all with X
else
i
end
end
If #student_ids is 1, then it works, but if #student_ids has more than one element such as 1,2,3, it raises errors. Any help?
It's faster to use a hash or a set than to repeatedly test [1,2,3].include?(n).
arr = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
ids = [1,2,3]
Use a hash
h = ids.product(["X"]).to_h
#=> {1=>"X", 2=>"X", 3=>"X"}
arr.map { |a| a.map { |n| h.fetch(n, n) } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
See Hash#fetch.
Use a set
require 'set'
ids = ids.to_set
#=> #<Set: {1, 2, 3}>
arr.map { |a| a.map { |n| ids.include?(n) ? "X" : n } }
#=> [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
Replace both maps with map! if the array is to be modified in place (mutated).
Try following, (taking #student_ids = [1, 2, 3])
array1.inject([]) { |m,a| m << a.map { |x| #student_ids.include?(x) ? 'X' : x } }
# => [["X", "X", "X", 4, 5], [7, 8, 9, 10], [11, 12, 13, 14]]
You can use each_with_index and replace the item you want:
array1 = [[1,2,3,4,5],[7,8,9,10],[11,12,13,14]]
#student_ids = [1,2,3]
array1.each_with_index do |sub_array, index|
sub_array.each_with_index do |item, index2|
array1[index][index2] = 'X' if #student_ids.include?(item)
end
end
You can do the following:
def remove_student_ids(arr)
arr.each_with_index do |value, index|
arr[index] = 'X' if #student_ids.include?(value) }
end
end
array1.map{ |sub_arr| remove_student_ids(sub_arr)}

How to quickly print Ruby hashes in a table format?

Is there a way to quickly print a ruby hash in a table format into a file?
Such as:
keyA keyB keyC ...
123 234 345
125 347
4456
...
where the values of the hash are arrays of different sizes. Or is using a double loop the only way?
Thanks
Try this gem I wrote (prints hashes, ruby objects, ActiveRecord objects in tables): http://github.com/arches/table_print
Here's a version of steenslag's that works when the arrays aren't the same size:
size = h.values.max_by { |a| a.length }.length
m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
nil seems like a reasonable placeholder for missing values but you can, of course, use whatever makes sense.
For example:
>> h = {:a => [1, 2, 3], :b => [4, 5, 6, 7, 8], :c => [9]}
>> size = h.values.max_by { |a| a.length }.length
>> m = h.values.map { |a| a += [nil] * (size - a.length) }.transpose.insert(0, h.keys)
=> [[:a, :b, :c], [1, 4, 9], [2, 5, nil], [3, 6, nil], [nil, 7, nil], [nil, 8, nil]]
>> m.each { |r| puts r.map { |x| x.nil?? '' : x }.inspect }
[:a, :b, :c]
[ 1, 4, 9]
[ 2, 5, ""]
[ 3, 6, ""]
["", 7, ""]
["", 8, ""]
h = {:a => [1, 2, 3], :b => [4, 5, 6], :c => [7, 8, 9]}
p h.values.transpose.insert(0, h.keys)
# [[:a, :b, :c], [1, 4, 7], [2, 5, 8], [3, 6, 9]]
No, there's no built-in function. Here's a code that would format it as you want it:
data = { :keyA => [123, 125, 4456], :keyB => [234000], :keyC => [345, 347] }
length = data.values.max_by{ |v| v.length }.length
widths = {}
data.keys.each do |key|
widths[key] = 5 # minimum column width
# longest string len of values
val_len = data[key].max_by{ |v| v.to_s.length }.to_s.length
widths[key] = (val_len > widths[key]) ? val_len : widths[key]
# length of key
widths[key] = (key.to_s.length > widths[key]) ? key.to_s.length : widths[key]
end
result = ""
data.keys.each {|key| result += key.to_s.ljust(widths[key]) + " " }
result += "\n"
for i in 0.upto(length)
data.keys.each { |key| result += data[key][i].to_s.ljust(widths[key]) + " " }
result += "\n"
end
# TODO write result to file...
Any comments and edits to refine the answer are very welcome.

Convert an array of integers into an array of strings in Ruby?

I have an array:
int_array = [11,12]
I need to convert it into
str_array = ['11','12']
I'm new to this technology
str_array = int_array.map(&:to_s)
str_array = int_array.collect{|i| i.to_s}
array.map(&:to_s) => array of integers into an array of strings
array.map(&:to_i) => array of strings into an array of integers
map and collect functions will work the same here.
int_array = [1, 2, 3]
str_array = int_array.map { |i| i.to_s }
=> str_array = ['1', '2', '3']
You can acheive this with one line:
array = [1, 2, 3]
array.map! { |i| i.to_s }
and you can use a really cool shortcut for proc: (https://stackoverflow.com/a/1961118/2257912)
array = [1, 2, 3]
array.map!(&:to_s)
Start up irb
irb(main):001:0> int_array = [11,12]
=> [11, 12]
irb(main):002:0> str_array = int_array.collect{|i| i.to_s}
=> ["11", "12"]
Your problem is probably somewhere else. Perhaps a scope confusion?
the shortest option:
int_array.map!(&:to_s)
Returns Int
x = [1,2,3,4,5,6,7,8,9,10] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Returns String
y = 1,2,3,4,5 # => ["1", "2", "3", "4", "5"]

Resources