“natural” sort an array in Ruby - ruby-on-rails

I have an array which contains numbers and alphabets something like:
newArray = ["1 a", "1 b" ,"2 c", "2 a"]
I would like to sort them in a way that the output is expected as follows:
newArray = ["2 a", "2 c" ,"1 a", "1 b"]
What I want to do is sort the numbers in descending order and if the numbers are same, then sort alphabetically
Can I implement a comparison function in sort_by or is there a way to do that using ruby sort

First you should use a better representation of your input. You can parse your existing array for example like this:
arr = newArray.map { |s| x,y = s.split; [x.to_i, y] }
# => [[1, "a"], [1, "b"], [2, "c"], [2, "a"]]
Then we can sort as we wish using sort_by:
arr.sort_by { |x,y| [-x, y] }
# => [[2, "a"], [2, "c"], [1, "a"], [1, "b"]]

Similar to #NiklasB. 's answer above (copied his sort_by)
arr.map(&:split).sort_by { |x,y| [-x.to_i, y] }
=> [["2", "a"], ["2", "c"], ["1", "a"], ["1", "b"]]

In a less elegant way, you can do that
arr.sort! do |p1, p2|
num1, str1 = p1.split(' ')
num2, str2 = p2.split(' ')
if (num1 != num2)
p2 <=> p1
else
p1 <=> p2
end
end
$stdout.puts arr

Related

How to get even groups in ruby?

Let's say I want to get a certain number of even groups based on a collection of records with varying count. How is this possible?
I'm looking for a method like objects.in_x_even_groups(4)
Group your objects by their index modulo the number of groups.
objects.group_by.with_index { |_, i| i % num_groups }.values
Example:
objects = %w{a b c d e f g h i j k}
objects.group_by.with_index { |_, i| i % 3 }.values
# [["a", "d", "g", "j"], ["b", "e", "h", "k"], ["c", "f", "i"]]
This won't pad undersized groups with nil and it also will interleave your objects. So this won't work if you need consecutive objects to be in the same group.
You are probably looking for the in_groups method. From the docs:
in_groups(number, fill_with = nil)
Splits or iterates over the array in number of groups, padding any remaining slots with fill_with unless it is false.
%w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
["1", "2", "3", "4"]
["5", "6", "7", nil]
["8", "9", "10", nil]
I assume:
the elements are to be kept in order;
l-s is to be minimized, where l is the size of the largest group and s is the size of the smallest group; and
group sizes are non-increasing.
l-s will be at most 1.
def group_em(arr, ngroups)
n_per_group, left_over = arr.size.divmod(ngroups)
cum_off = 0
ngroups.times.map do |i|
n = n_per_group + (i < left_over ? 1 : 0)
a = arr[cum_off, n]
cum_off += n
a
end
end
arr = [1, 2, 3, 4, 5, 6, 7]
(1..7).each { |m| puts "ngroups=#{m}: #{group_em(arr, m)}" }
ngroups=1: [[1, 2, 3, 4, 5, 6, 7]]
ngroups=2: [[1, 2, 3, 4], [5, 6, 7]]
ngroups=3: [[1, 2, 3], [4, 5], [6, 7]]
ngroups=4: [[1, 2], [3, 4], [5, 6], [7]]
ngroups=5: [[1, 2], [3, 4], [5], [6], [7]]
ngroups=6: [[1, 2], [3], [4], [5], [6], [7]]
ngroups=7: [[1], [2], [3], [4], [5], [6], [7]]
You're looking for in_groups_of:
https://apidock.com/rails/Array/in_groups_of
array = %w(1 2 3 4 5 6 7 8 9 10)
array.in_groups_of(3) {|group| p group}
=>
["1", "2", "3"]
["4", "5", "6"]
["7", "8", "9"]
["10", nil, nil]

How can I create an array of duplicate case-insensitive strings from another array in Ruby?

I have an array of strings. I'm wanting to change the name of these duplicate strings to append a numerical value to make them unique like so...
Original Array
a, a, A, b, c, D, d
Corrected Array
a, a1, A2, b, c, D, d1
I've gotten close to this with the following code; however, if the strings are a different case structure then they aren't currently considered duplicates with this code snippet. I would like them to be considered duplicates, but yet not change their case in the results array.
duplicate_counter = 1
duplicates = Array.new
duplicates = file_columns.select{ |e| file_columns.count(e) > 1 } # get duplicate column names
duplicates.each{ |x| file_columns.delete(x) }
duplicates.sort!
duplicates.each_with_index do |d, i|
if i > 0
if d == duplicates[i-1]
d = d.strip + duplicate_count.to_s
duplicate_count += 1
else
duplicate_count = 1
end
end
# Add back the column names, but with the appended numerical counts to make them unique
file_columns.push(d)
end
You are over thinking it considerably. I'm sure there are better ways to do this as well, but it gets the job done.
a = ['a', 'a', 'A', 'b', 'c', 'D', 'd']
letters = Hash.new(-1)
a.map do |letter|
l = letter.downcase
letters[l] += 1
if (letters[l] > 0)
"#{letter}#{letters[l]}"
else
"#{letter}"
end
end
Here's a way to do it if letters independent of case are not necessarily grouped. For example, it will convert this array:
arr = %w{ a D a A b c D a d }
#=> ["a", "D", "a", "A", "b", "c", "D", "a", "d"]
to:
["a", "D", "a1", "A2", "b", "c", "D1", "a3", "d2"]
Code
def convert(arr)
arr.each_with_index
.group_by { |c,_| c.downcase }
.values
.flat_map { |c|
c.map
.with_index { |(f,l),i| [i > 0 ? f<<i.to_s : f, l] } }
.sort_by(&:last)
.map(&:first)
end
Example
For arr above:
convert(arr)
#=> ["a", "D", "a1", "A2", "b", "c", "D1", "a3", "d2"]
Explanation
Dear reader, if you are new to Ruby, this may look impossibly complex. If you break it down into steps, however, it's not that bad. After you gain experience and become familiar with commonly-used methods, it will come quite naturally. Here I've used the following methods, chained together so that the return value of each becomes the receiver of the next:
Enumerable#each_with_index
Enumerable#group_by
Hash#values
Enumerable#flat_map
Enumerable#sort_by
Enumerable#first
Here's what's happening.
enum = arr.each_with_index
#=> #<Enumerator: ["a", "D", "a", "A", "b", "c",
# "D", "a", "d"]:each_with_index>
h = enum.group_by { |c,_| c.downcase }
#=> {"a"=>[["a", 0], ["a", 2], ["A", 3], ["a", 7]],
# "d"=>[["D", 1], ["D", 6], ["d", 8]],
# "b"=>[["b", 4]],
# "c"=>[["c", 5]]}
a = h.values
#=> [[["a", 0], ["a", 2], ["A", 3], ["a", 7]],
# [["D", 1], ["D", 6], ["d", 8]],
# [["b", 4]],
# [["c", 5]]]
b = a.flat_map { |c| c.map.with_index { |(f,l),i| [i > 0 ? f<<i.to_s : f, l] } }
#=> [["a", 0], ["a1", 2], ["A2", 3], ["a3", 7], ["D", 1],
# ["D1", 6], ["d2", 8], ["b", 4], ["c", 5]]
c = b.sort_by(&:last)
#=> [["a", 0], ["D", 1], ["a1", 2], ["A2", 3], ["b", 4],
# ["c", 5], ["D1", 6], ["a3", 7], ["d2", 8]]
c.map(&:first)
#=> ["a", "D", "a1", "A2", "b", "c", "D1", "a3", "d2"]

How to looping through multiple arrays in Ruby?

I am new to Ruby. I am creating a rails platform where I have encountered the following issue:
I have my first array, which for the sake of simplicity I call 'capital' as:
capital = [A,B,C,D]. I have a second array small = [a,b,c,d]. The elements of small have many to one relationship with the elements of the capital, as:
capital
has_many:small
These two are linked through two tables in my database, so that valid combination of these arrays can be printed by
capital.each do |x|
small.each do |y|
puts x,y
end
end
which prints out valid combinations of x,y as defined in the database. This works perfectly fine.
Now, here is the issue: I have a third array array_3, which contains some combinations of the elements of capital and small, as:
array_3 = [(A,c), (C,d), (D,b)]
I want to print all valid combinations of elements of capital and small as defined in the database, such that the combination is not present in array_3. How do I go about it?
capital = %w(A B C D)
small = %w(a b c d)
array_3 = [%w(A c), %w(C d), %w(D b)]
capital.product(small) - array_3
#=> [["A", "a"], ["A", "b"], ["A", "d"], ["B", "a"], ["B", "b"], ["B", "c"], ["B", "d"], ["C", "a"], ["C", "b"], ["C", "c"], ["D", "a"], ["D", "c"], ["D", "d"]]
You can do as
capital.each do |x|
small.each do |y|
puts x,y unless array_3.include?([x,y])
end
end
Here is for all combination of capital and small which exists in array_3
capital.each do |x|
small.each do |y|
puts "[#{x}, #{y}]" unless array_3.exclude?([x,y])
puts "[#{y}, #{x}]" unless array_3.exclude?([y,x])
end
end
Check the Code in console:
> small
=> ["a", "b", "c", "d"]
> capital
=> ["A", "B", "C", "D"]
> array_3
=> [["A", "c"], ["C", "d"], ["D", "b"], ["A", "A"], ["C", "C"], ["a", "C"], ["b", "A"]]
> capital.each do |x|
> small.each do |y|
> puts "[#{x}, #{y}]" unless array_3.exclude?([x,y])
> puts "[#{y}, #{x}]" unless array_3.exclude?([y,x])
> end
> end
Output:
=>[b, A] # [small, capital]
[A, c] # [capital, small]
[a, C] # [small, capital]
[C, d] # [capital, small]
[D, b] # [capital, small]
Note: You can use if array_3.include? instead of unless array_3.exclude? which will return the same result.
For your more info about exclude? and include? as you are new in Ruby. ;)

How to correctly use the inject method in Ruby

I'm still a ruby newbie and I'm trying to learn how exactly to use the inject method.
By using the inject method, I'm trying to return an array of letters and their appearance in a given string like so:
def sorting(str)
str.split("").uniq.map { |letter|
[letter, str.split("").inject(0) { |sum, char| sum + 1 if letter == char }]
}
end
p sorting("aaabdbbcccaaabcacba")
The desire result should be:
[["a", 8], ["b", 5], ["d", 1], ["c", 5]]
Unfortunately, I'm getting an error: undefined method '+' for nil:NilClass.
What am I doing wrong and how can I solve it using inject and its sum advantages?
using Enumerable#group_by seems more appropriate:
>> "aaabdbbcccaaabcacba".chars.group_by { |x| x }.map { |key, a| [key, a.size] }
=> [["a", 8], ["b", 5], ["d", 1], ["c", 5]]
Problem of the given code:
The block passed to inject does not care about non-matching character.
Replace sum + 1 if letter == char with letter == char ? sum + 1 : sum:
def sorting(str)
str.split("").uniq.map { |letter|
[letter, str.split("").inject(0) { |sum, char| letter == char ? sum + 1 : sum }]
}
end
Here is another way to go :
s = "aaabdbbcccaaabcacba"
s.chars.uniq.map{|c|[c,s.count(c)]}
# => [["a", 8], ["b", 5], ["d", 1], ["c", 5]]

JSON Array to Hash

I need to parse and display solr facets which are returned in either JSON or Ruby formar:
Collections: [ "a", 1, "b", 2, "c", 3, "d", 4, ... ]
into
{"a"=>1, "b"=>2, "c"=>3, "d"=>4}
What is the cleanest way?
EDIT: Well now that we know what you actually want, a hash ...
collections = ["a", 1, "b", 2, "c", 3, "d", 4]
Hash[*collections]
# => {"a"=>1, "b"=>2, "c"=>3, "d"=>4}
Original answer: I may not understand your goal but...
collections = ["a", 1, "b", 2, "c", 3, "d", 4]
collections.each_slice(2).map{ |(x, y)| "#{x} - #{y}" }
# => ["a - 1", "b - 2", "c - 3", "d - 4"]
What i see you want to do is maybe a hash ? {a => "1", b => "2"} ??
If so, read below:
collections = [ "a", 1, "b", 2, "c", 3, "d", 4]
result = Hash[*collections.flatten]
result prints {"a"=>1, "b"=>2, "c"=>3, "d"=>4}

Resources