How to create a sub array of given array of binary numbers based on number of 1's in Ruby? - ruby

Example:
Here is binary numbers array:
a = [001, 010, 100, 011, 101, 110, 111, 1000, 1001, 1010]
I want output like below:
[ [ 001, 010, 100, 1000 ], [ 011, 101, 110, 1001, 1010 ], [ 111 ] ]
Can anybody help me how to achieve it in ruby?

I'm going to assume you're working with strings ("001") and not decimal/octal literals (001). If that's not the case, I strongly suggest casting to strings to make things easier on you.
We can count the number of ones in a string x with x.count('1'). Then we can take a list of strings and organize it by this value with a.group_by(...). This gives a hash, so if you just want the values (as your suggested output suggests), then you simply take the values of it.
a.group_by { |x| x.count('1') }.values

Using Enumerable#group_by, as #Silvio has done, seems the most direct way to solve this problem, but here are a couple of other approaches one could use.
a = "001, 010, 100, 011, 101, 110, 111, 1000, 1001, 1010".split(', ')
#=> ["001", "010", "100", "011", "101", "110", "111", "1000", "1001", "1010"]
Construct a hash whose keys, k, are numbers of ones and whose values are arrays containing the elements from the original array whose numbers of one1 equal k
a.each_with_object({}) { |s,h| (h[s.count('1')] ||= []) << s }.values
#=> [["001", "010", "100", "1000"], ["011", "101", "110", "1001", "1010"], ["111"]]
Note values is applied to the hash returned by the block, namely
{1=>["001", "010", "100", "1000"], 2=>["011", "101", "110", "1001", "1010"], 3=>["111"]}
Consider the expression, (h[s.count('1')] ||= []) << s. Let
cnt = s.count('1')
Then (h[cnt] ||= []) << s expands to the following when parsed.
(h[cnt] = h[cnt] || []) << s
If h does not have a key cnt, then h[cnt] on the right of the equality equals nil, so the expression reduces to
(h[cnt] = []) << s
so h[cnt] #=> [s]. On the other hand, if h does have a key cnt, h[cnt] equals an array, which is truthy, so we execute
h[cnt] << s
Note that in h[cnt] = h[cnt] || [], the method on the left of the equality is Hash#[]=, whereas we have Hash#[] is on the right of the equality.
Sort then slice
a.sort_by { |s| s.count('1') }.slice_when { |s1,s2| s1.count('1') < s2.count('1') }.to_a
#=> [["001", "010", "100", "1000"], ["011", "101", "110", "1001", "1010"], ["111"]]

Related

Sort array of string with digits and characters in ruby

I have an array with the given strings
array = [
"1mo-30-super",
"1mo-40-classic",
"1mo-30-classic",
"1mo-110-super",
"1mo-20-extra",
"6mo-21-super",
"6mo-11-super",
"12mo-21-classic",
"12mo-21-super"
]
How can I sort the array so that it goes in numerical order, then alphabetical order so the array displays like so:
array = [
"1mo-20-extra",
"1mo-30-classic",
"1mo-30-super",
"1mo-40-classic",
"1mo-110-super",
"6mo-11-super",
"6mo-21-super",
"12mo-21-classic",
"12mo-21-super"
]
You're looking for a "natural" sort where the numeric substrings will be compared as numbers as the non-numeric parts will be compared like strings. Conveniently enough, arrays in Ruby compare element-by-element and your format is fairly regular so you can get away with a #sort_by call and a bit of mangling to convert "12mo-21-classic" to [12, 'mo-', 21, '-classic']. Something like this for example:
# This is a bit complicated so we'll give the logic a name.
natural_parts = ->(s) { s.match(/(\d+)(\D+)(\d+)(\D+)/).to_a.drop(1).map.with_index { |e, i| i.even?? e.to_i : e } }
array.sort_by(&natural_parts)
array.sort_by { |s| [s.to_i, s[/(?<=-)\d+/].to_i, s.gsub(/\A.+-/,'')] }
#=> ["1mo-20-extra", "1mo-30-classic", "1mo-30-super", "1mo-40-classic", "1mo-110-super",
# "6mo-11-super", "6mo-21-super", "12mo-21-classic", "12mo-21-super"]
When sorting arrays the method Arrays#<=> is used to order pairs of arrays. See the third paragraph of the doc for an explanation of how that is done.
The arrays used for the sort ordering are as follows.
array.each do |s|
puts "%-15s -> [%2d, %3d, %s]" % [s, s.to_i, s[/(?<=-)\d+/].to_i, s.gsub(/\A.+-/,'')]
end
1mo-30-super -> [ 1, 30, super]
1mo-40-classic -> [ 1, 40, classic]
1mo-30-classic -> [ 1, 30, classic]
1mo-110-super -> [ 1, 110, super]
1mo-20-extra -> [ 1, 20, extra]
6mo-21-super -> [ 6, 21, super]
6mo-11-super -> [ 6, 11, super]
12mo-21-classic -> [12, 21, classic]
12mo-21-super -> [12, 21, super]
(?<=-) is a positive lookbehind. It requires that the match be immediately preceded by a hyphen. /\A.+-/ matches the beginning of the string followed by one or more characters followed by a hyphen. Because regular expressions are by default greedy, it concludes the match on the second hyphen.
Note that it is not necessary to use regular expressions:
array.sort_by { |s| [s.to_i, s[s.index('-')+1..-1].to_i, s[s.rindex('-')+1..-1]] }
You can chain several #sort method calls, each sorting by a different part of a string (starting with one with smallest priority):
array.sort { |a,b| a.match(/-(.*)$/)[1] <=> b.match(/-(.*)-/)[1] } # sort by last element ('classic', 'super')
.sort { |a,b| a.match(/-(\d+)-/)[1].to_i <=> b.match(/-(\d+)-/)[1].to_i } # sort by the number between dashes
.sort { |a,b| a.to_i <=> b.to_i } # sort by the initial number
=> ["1mo-20-extra",
"1mo-30-classic",
"1mo-30-super",
"1mo-40-classic",
"1mo-110-super",
"6mo-11-super",
"6mo-21-super",
"12mo-21-super",
"12mo-21-classic"]

Algorithm to find all combinations of a string, maintain order, not fixed length

I would like to find all combinations of a string, maintaining order, but of any length. For example:
string_combinations("wxyz")
# => ['w', 'wx', 'wxy', 'wxyz', 'wxz', 'wy', 'wyz', 'wz', 'x', 'xy', 'xyz', 'xz', 'y', 'yz', 'z']
I would prefer if you could use loops only and avoid using ruby methods like #combination as I am trying to find the cleanest way to implement this if I come across it in another language.
Is there a way to do this in less than O(n^3)? My initial thought is something like:
def string_combinations(str)
result = []
(0...str.length).each do |i|
result << str[i]
((i+1)...str.length).each do |j|
result << str[i] + str[j]
((j+1)...str.length).each do |k|
result << str[i] + str[j..k]
# Still not covering everything.
end
end
end
result
end
Here are two ways it could be done without making use of Array#combination. I've also included code for the case when combination is permitted (#3)1.
1. Map each of the numbers between 1 and 2**n-1 (n being the length of the string) to a unique combination of characters from the string
def string_combinations(str)
arr = str.chars
(1..2**str.length-1).map do |n|
pos = n.bit_length.times.map.with_object([]) { |i,a| a << i if n[i] == 1 }
arr.values_at(*pos).join
end.sort
end
string_combinations("wxyz")
# => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
# "x", "xy", "xyz", "xz", "y", "yz", "z"]
Discrete probability theory provides us with the equation
sum(i = 1 to n) ( |i| C(n,i) ) == 2^n - 1
where C(n,i) is "the number of combinations of n things taken i at a time".
If the given string is "wxyz", n = "wxyz".length #=> 4, so there are 2**4 - 1 #=> 15 combinations of one or more characters from this string. Now consider any of the numbers between 1 and 16, say 11, which is 0b1011 in binary. Converting this to an array of binary digits, we obtain:
bin_arr = [1,0,1,1]
We now pluck out each character of wxyz for which the corresponding index position in bin_arr equals 1, namely
["w", "y", "z"]
and then join those elements to form a string:
["w", "y", "z"].join #=> "wyz"
Since each number 1 to 15 corresponds to a distinct combination of one or more characters from this string, , we can obtain all such combinations by repeating the above calculation for each the numbers between 1 and 15.
No matter which method you use, the resulting array will contain 2**n - 1 elements, so you are looking at O(2**str.length).
2. Use recursion
def string_combinations(str)
(combos(str) - [""]).sort
end
def combos(str)
return [str, ""] if str.length==1
forward = combos str[1..-1]
[*forward, *[str[0]].product(forward).map(&:join)]
end
string_combinations("wxyz")
# => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
# "x", "xy", "xyz", "xz", "y", "yz", "z"]
Notice that
combos("wxyz")
#=> ["z", "", "yz", "y", "xz", "x", "xyz", "xy",
# "wz", "w", "wyz", "wy", "wxz", "wx", "wxyz", "wxy"]
includes an empty string, which must be removed, and the array needs sorting. Hence the need to separate out the recursive method combos.
3. Use Array#combination
Here we invoke arr.combination(n) for all values of n between 1 and arr.size and return a (flattened) array comprised of all n return values.
def string_combinations(str)
a = str.chars
(1..str.size).flat_map { |n| a.combination(n).map(&:join) }.sort
end
string_combinations "wxyz"
# => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
# "x", "xy", "xyz", "xz", "y", "yz", "z"]
1 Since I wrote it before realizing that's not what the OP wanted. ¯\_(ツ)_/¯
A pretty simple solution using a stack (I can't provide ruby-code though):
string inp
list result
//initialize stack
stack s
s.push(0)
while(!s.isEmpty())
int tmp = s.peek()
//the current value is higher than the max-index -> shorten prefix
if tmp >= inp.length()
s.pop()
//increment the last character of the prefix
if !s.isEmpty()
s.push(s.pop() + 1)
continue
//build the result-string from the indices in the stack
//note that the indices in the stack are reverse (highest first)!!!
result.add(buildString(inp , s)
//since we aren't at the end of the string, we can append another character to the stack
s.push(tmp + 1)
The basic idea would be to maintain a stack of positions from which characters will be taken. This stack has the property that each element in the stack is a larger number than the next element in the stack. Thus the ordering of the stack is maintained. If we reach a number that is equal to the string-length, we eliminate that number and increment the next number, thus moving on to the next prefix.
E.g.:
stack string
0 (init) a b c (init)
0 a
0 1 a b
0 1 2 a b c
0 2 a c
1 b
1 2 b c
2 c
The peek of the stack would represent the character of the input-string that is modified, the rest of the stack represents the prefix.
It seems like the algorithm could be expressed in English as:
"w", followed by "w" + all combinations of "xyz", followed by
"x", followed by "x" + all combinations of "yz", followed by
etc.
In other words, there is a notion of a "prefix" and then recursion on the "remaining chars". With that in mind, here is a Ruby solution:
def combine_with_prefix(prefix, chars)
result = []
chars.each_with_index do |ch, i|
result << "#{prefix}#{ch}"
result.concat(combine_with_prefix(result.last, chars[(i + 1)..-1]))
end
result
end
def string_combinations(str)
combine_with_prefix(nil, str.chars)
end
string_combinations("wxyz")
# => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz", "x", "xy", "xyz", "xz", "y", "yz", "z"]
Here's another way to think about this: You start with an input string s having length n. From that you calculate the set of strings that can be produced by removing one character from s. That set has n members. For each of those members you perform the same operation: Calculate the set of strings that can be produced by removing one character. Each of those sets has n-1 members. For each of those n(n-1) members, perform the operation again, and so on until n is 1. The result is the union of all of the calculated sets.
For example, suppose your starting string is abcd (n = 4) The set of strings that can be produced by removing one character is (bcd, acd, abd, abc). That's 4 operations. Repeating the operation for each of those strings yields 4 sets of 3 members each (4x3 = 12 operations), each of which has length 2. Repeating again yields 12 sets of 2 members each (4x3x2 = 24 operations), each having length 1. That's the magic number, so we round up all of those strings, throw out the duplicates, and we've got our answers. In the end we did 4+4x3+4x3x2 = 40 operations.
That holds true for every length of string. If we have 5 characters we do 5+5x4+5x4x3+5x4x3x2 = 205 operations. For 6 characters it's 1,236 operations. I leave it to you to figure out what that equates to in big-O notation.
This boils down to a really simple recursive algorithm:
def comb(str)
[ str,
*if str.size > 1
str.each_char.with_index.flat_map do |_,i|
next_str = str.dup
next_str.slice!(i)
comb(next_str)
end
end
]
end
p comb("wxyz").uniq.sort
# => [ "w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
# "x", "xy", "xyz", "xz", "y", "yz", "z" ]
We end up throwing out a lot with uniq, though, which tells us we can save a lot of cycles by memoizing:
def comb(str, memo={})
return memo[str] if memo.key?(str)
[ str,
*if str.size > 1
str.each_char.with_index.flat_map do |_,i|
next_str = str.dup
next_str.slice!(i)
memo[str] = comb(next_str, memo)
end
end
]
end
p comb("wxyz").uniq.sort
In case you're curious, with memoization the inner loop is reached 23 times for a 4-character input versus 41 without memoization; 46 vs. 206 times for 5 characters; 87 vs. 1,237 times for 6; and 162 vs. 8,653 for 7. Fairly significant, I think.

Sort array of numbers in Scientific Notation

I would like to sort an array of numbers (in scientific notation) from the smallest to the highest.
This is what I have tried (in vain):
require 'bigdecimal'
s = ['1.8e-101','1.3e-116', '0', '1.5e-5']
s.sort { |n| BigDecimal.new(n) }.reverse
# Results Obtained
# => [ "1.3e-116", "1.8e-101", "0", "1.5e-5" ]
# Expected Results
# => [ "0", "1.3e-116", "1.8e-101", "1.5e-5"]
The block of Enumerable#sort is expected to return -1, 0 or 1. What you want is Enumerable#sort_by:
s.sort_by { |n| BigDecimal.new(n) }
# => ["0", "1.3e-116", "1.8e-101", "1.5e-5"]
Another option is to use BigDecimal#<=> within sort:
s.sort { |x, y| BigDecimal(x) <=> BigDecimal(y) }
#=> ["0", "1.3e-116", "1.8e-101", "1.5e-5"]

Check if all items within sub-array are identical Ruby

Trying to check if all items within sub-arrays are the same. For example, I have a 5x5 board and I want to know if one of the arrays contains all x's:
board = [[47, 44, 71, 8, 88],
['x', 'x', 'x', 'x', 'x'],
# [83, 85, 97, 'x', 57],
[83, 85, 97, 89, 57],
[25, 31, 96, 68, 51],
[75, 70, 54, 80, 83]]
I currently have:
def check_x
board.each do |x|
return true if x.include?('x')
end
return false
end
But this will merely check if one of the integers is x and not all. Any suggestions would be greatly appreciated.
A bit more idiomatic:
board.one? { |row| row.all? { |item| item == 'x' } }
As simple as board.map { |row| row.uniq.count == 1 } will do
#=> [false, true, false, false, false]
uniq returns unique elements in an array. map here is iterating over your array and passing one row at a time to the block. It will return true for cases where all elements in an array are same (['x', 'x', 'x', 'x', 'x'].uniq #=> ['x'] whose length is 1)
If you just want to check if any row in board has all duplicate elements, ruby has just a function. Guess what? any?. Just change above one-liner with any? as:
board.any? { |row| row.uniq.count == 1 } #=> true
If you want to find out which row(s) has/have all the duplicates, and what duplicate it has:
board.each.with_index.select { |row, index| row.uniq.count == 1 }
#=> [[["x", "x", "x", "x", "x"], 1]], where 1 is index.
Pure Ruby awesomeness.
if all elements are same in an array, that means maximum and minimum is equal.
for your board you can find index of desired sub-array with this one line
board.each {|b| puts board.index(b) if b.max == b.min}
or just replace x.include?("x") with x.min == x.max in your function for true/false result
Assuming all elements of board (rows of the board) are the same size, which seems a reasonable assumption, you could do it thus:
x_row = ['x']*board.first.size
#=> ["x", "x", "x", "x", "x"]
board.any? { |row| row == x_row }
#=> true
Assuming it's always a fixed length array, your method can just be:
def full_row
board.each do |row|
return true if (row.uniq.count == 1) && (row[0] == 'x')
end
return false
end
This could be boiled down to fewer lines, but I hate line wrapping in vim :p

Sorting: Sort array based on multiple conditions in Ruby

I have a mulitdimensional array like so:
[
[name, age, date, gender]
[name, age, date, gender]
[..]
]
I'm wondering the best way to sort this array based on multiple conditions...For instance, how would I sort based on age first then by name?
I was messing around with the sort method like so:
array.sort { |a,b| [ a[1], a[0] ] <=> [ b[1], b[0] ] }
Besides that I don't really understand this syntax, I'm not getting the results I would expect. Should I be using the sort method? Should I be individually comparing results by mapping the array?
You should always use sort_by for a keyed sort. Not only is it much more readable, it is also much more efficient. In addition, I would also prefer to use destructuring bind, again, for readability:
array.sort_by {|name, age| [age, name] }
This should do the trick:
array.sort { |a,b| [ a[1], a[0] ] <=> [ b[1], b[0] ] }
So what does this do? It uses a lot of Ruby idioms.
First is blocks, which are sort of like callbacks or anonymous functions/classes in other languages. The sort method of Array uses them to compare two elements based on the return value of the block. You can read all about them here.
Next is the <=> operator. It returns -1 if the first argument is less than the second, 0 if they are equal, and 1 if the first is greater than the second. When you use it with arrays, it will compare the arrays element-wise until one of them returns -1 or 1. If the arrays are equal, you will get 0.
As I understand it you want to order by age first, and then if more than one record has the same age, arrange that subset by name.
This works for me
people = [
["bob", 15, "male"],
["alice", 25, "female"],
["bob", 56, "male"],
["dave", 45, "male"],
["alice", 56, "female"],
["adam", 15, "male"]
]
people.sort{|a,b| (a[1] <=> b[1]) == 0 ? (a[0] <=> b[0]) : (a[1] <=> b[1]) }
# The sorted array is
[["adam", 15, "male"],
["bob", 15, "male"],
["alice", 25, "female"],
["dave", 45, "male"],
["alice", 56, "female"],
["bob", 56, "male"]]
What this is doing is comparing by age first, and if the age is the same (<=> returs 0) it comparing the name.

Resources