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

114 Views Asked by At

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?

2

There are 2 best solutions below

3
On BEST ANSWER

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
0
On

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"]]