Check for consecutive numbers - arrays

I have an array m of integers. I'm looking for a method to check if the elements of m are consecutive. Is there a way to test for consecutive numbers?
I came up with this code intended to work when the array length is four:
m.count == 4 && (m.max-m.min) == 3
which incorrectly returns true for [1,1,1,4] or [0,0,0,3].

Enumerable has a really handy method called each_cons that works like this:
[1,2,3,4].each_cons(2).to_a # => [ [1, 2], [2, 3], [3, 4] ]
That is, it yields each consecutive set of n elements. In our case n is 2.
Of course, as the name implies, it returns an Enumerator, so we can chain it with other Enumerable methods like all?:
def four_consecutive?(arr)
return false unless arr.size == 4
arr.each_cons(2).all? {|a, b| b == a + 1 }
end
four_consecutive?([2,3,4,5]) # => true
four_consecutive?([2,2,2,5]) # => false
four_consecutive?([1,2,3,4,5]) # => false
This method has the advantage above others that, because all? short-circuits as soon as the block returns false, it will only test numbers until it finds a pair that don't meet the condition (b == a + 1). Of course, with only four elements this doesn't really make a difference—unless you're calling this method thousands of times in situation where performance matters.

You can try this:
a == (a.min..a.max).to_a && a.count == 4
This only works when the array is in increasing order. [3, 4, 5, 6] will pass but [4, 3, 5, 6] won't.

The Answer is based on mathematical problem for Sum of consecutive integers
Sum = n∗(n+1)/2
Code:
def check_sum_match?(arr)
m = arr.min - 1
n = arr.max
sum1 = arr.inject{|sum, x| sum = sum + x}
sum2 = (n*(n+1) - m*(m+1))/2
sum1 == sum2
end
arr = [5,6,7,8]
if arr.count == 4 && check_sum_match?(arr)
puts 'Yes condition matches'
else
puts 'Invalid Array'
end
# valid arrays are
# [4,6,5,7], [4,5,6,7], etc
Tentative Explanation:

If a is the array and n is the required size:
def size_and_consecutive?(a, n)
a == (a.first..(a.first+n-1)).to_a
end
size_and_consecutive? [3,4,5,6], 4
#=> true
size_and_consecutive? [4,3,5,6], 4
#=> false
size_and_consecutive? [3,4,5], 4
#=> false

A compact solution that I could come up with is as follows:
def consec(arr)
is_of_proper_length = (arr.size == 4)
if(is_of_proper_length)
are_consec = true
arr.each_cons(2) {|x,y| are_consec = false unless ((y - x) == 1)}
end
is_of_proper_length && are_consec
end
Output:
consec([1,2,3,4])
=> true
2.2.0 :051 > consec([0,0,0,0])
=> false
2.2.0 :052 > consec([4,6,5,7])
=> true
2.2.0 :053 > consec([4,5,6,7])
=> true
2.2.0 :054 > consec([5,6,7,8])
=> true
2.2.0 :055 > consec([2,2,2,5])
=> false
2.2.0 :056 > consec([2,3,4,5])
=> true
2.2.0 :057 > consec([1,2,3,4,5])
=> false

Related

How do I find the unique number in an array and return only that number in ruby?

There is an array with some numbers. All numbers are equal except for one. I'm trying to get this type of thing:
find_uniq([ 1, 1, 1, 2, 1, 1 ]) == 2
find_uniq([ 0, 0, 0.55, 0, 0 ]) == 0.55
I tried this:
def find_uniq(arr)
arr.uniq.each{|e| arr.count(e)}
end
It gives me the two different values in the array, but I'm not sure how to pick the one that's unique. Can I use some sort of count or not? Thanks!
This worked:
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] != arr[1]
return arr[1] == arr[2] ? arr[0] : arr[1]
end
arr.each_cons(2) { |x, y| return y if y != x }
end
Thanks pjs and Cary Swoveland.
I would do this:
[ 1, 1, 1, 2, 1, 1 ]
.tally # { 1=>5, 2=>1 }
.find { |_, v| v == 1 } # [2, 1]
.first # 2
Or as 3limin4t0r suggested:
[ 1, 1, 1, 2, 1, 1 ]
.tally # { 1=>5, 2=>1 }
.invert[1] # { 5=>1, 1=>2 } => 2
The following doesn't use tallies and will short circuit the search when a unique item is found. First, it returns nil if the array has fewer than 3 elements, since there's no way to answer the question in that case. If that check is passed, it works by comparing adjacent values. It performs an up-front check that the first two elements are equal—if not, it checks against the third element to see which one is different. Otherwise, it iterates through the array and returns the first value it finds which is unequal. It returns nil if there is not a distinguished element in the array.
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] == arr[1]
arr.each.with_index do |x, i|
i += 1
return arr[i] if arr[i] != x
end
elsif arr[1] == arr[2]
arr[0]
else
arr[1]
end
end
This also works with non-numeric arrays such as find_uniq(%w(c c c d c c c c)).
Thanks to Cary Swoveland for reminding me about each_cons. That can tighten up the solution considerably:
def find_uniq(arr)
return nil if arr.size < 3
if arr[0] != arr[1]
return arr[1] == arr[2] ? arr[0] : arr[1]
end
arr.each_cons(2) { |x, y| return y if y != x }
end
For all but tiny arrays this method effectively has the speed of Enumerable#find.
def find_uniq(arr)
multi = arr[0,3].partition { |e| e == arr.first }
.sort_by { |e| -e.size }.first.first
arr.find { |e| e != multi }
end
find_uniq [1, 1, 1, 2, 1, 1] #=> 2
find_uniq [0, 0, 0.55, 0, 0] #=> 0.55
find_uniq [:pig, :pig, :cow, :pig] #=> :cow
The wording of the question implies the array contains at least three elements. It certainly cannot be empty or have two elements. (If it could contain one element add the guard clause return arr.first if arr.size == 1.)
I examine the first three elements to determine the object that has duplicates, which I assign to the variable multi. I then am able to use find. find is quite fast, in part because it short-circuits (stops enumerating the array when it achieves a match).
If
arr = [1, 1, 1, 2, 1, 1]
then
a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
#=> [[1, 1, 1], []]
multi = a.first.first
#=> 1
If any of these:
arr = [2, 1, 1, 1, 1, 1]
arr = [1, 2, 1, 1, 1, 1]
arr = [1, 1, 2, 1, 1, 1]
apply then
a = arr[0,3].partition { |e| e == arr.first }.sort_by { |e| -e.size }
#=> [[1, 1], [2]]
multi = a.first.first
#=> 1
Let's compare the computational performace of the solutions that have been offered.
def spickermann1(arr)
arr.tally.find { |_, v| v == 1 }.first
end
def spickermann2(arr)
arr.tally.invert[1]
end
def spickermann3(arr)
arr.tally.min_by(&:last).first
end
def pjs(arr)
if arr[0] == arr[1]
arr.each.with_index do |x, i|
i += 1
return arr[i] if arr[i] != x
end
elsif arr[1] == arr[2]
arr[0]
else
arr[1]
end
end
I did not include #3limin4t0r's solution because of the author's admission that it is relatively inefficient. I did include, however, include two variants of #spikermann's answer, one ("spickermann2") having been proposed by #3limin4t0r in a comment.
require 'benchmark'
def test(n)
puts "\nArray size = #{n}"
arr = Array.new(n-1,0) << 1
Benchmark.bm do |x|
x.report("Cary") { find_uniq(arr) }
x.report("spickermann1") { spickermann1(arr) }
x.report("spickermann2") { spickermann2(arr) }
x.report("spickermann3") { spickermann3(arr) }
x.report("PJS") { pjs(arr) }
end
end
test 100
Array size = 100
user system total real
Cary 0.000032 0.000009 0.000041 ( 0.000029)
spickermann1 0.000022 0.000015 0.000037 ( 0.000019)
spickermann2 0.000017 0.000002 0.000019 ( 0.000016)
spickermann3 0.000019 0.000002 0.000021 ( 0.000018)
PJS 0.000042 0.000025 0.000067 ( 0.000034)
test 10_000
Array size = 10_000
user system total real
Cary 0.001101 0.000091 0.001192 ( 0.001119)
spickermann1 0.000699 0.000096 0.000795 ( 0.000716)
spickermann2 0.000794 0.000071 0.000865 ( 0.000896)
spickermann3 0.000776 0.000081 0.000857 ( 0.000781)
PJS 0.001140 0.000113 0.001253 ( 0.001300)
test 1_000_000
Array size = 1_000_000
user system total real
Cary 0.061148 0.000787 0.061935 ( 0.063022)
spickermann1 0.043598 0.000474 0.044072 ( 0.044590)
spickermann2 0.044909 0.000663 0.045572 ( 0.046371)
spickermann3 0.042907 0.000210 0.043117 ( 0.043162)
PJS 0.072766 0.000226 0.072992 ( 0.073168)
I attribute the apparent superiority of #spickermann's answer to the fact that Enumerable#tally has no block to evaluate (unlike, for example, Enumerable#find in my answer).
Your code can be fixed by using find instead of each:
def find_uniq(arr)
arr.uniq.find { |e| arr.count(e) == 1 }
end
However this is quite inefficient since uniq needs to iterate the full collection. After finding the unique values the arr collection is iterated 1 or 2 more times by count (assuming there are only two unique values), depending on the position of the values in the uniq result.
For simple solution I suggest looking at the answer of spickermann which only iterates the full collection once.
For your specific scenario you could technically increase performance by short-circuiting the tally. This is done by manually tallying and breaking the loop if the tally contains 2 distinct values and at least 3 items are tallied.
def find_uniq(arr)
tally = Hash.new(0)
arr.each_with_index do |item, index|
break if tally.size == 2 && index >= 3
tally[item] += 1
end
tally.invert[1]
end

If there's two maximum elements of an array?

In this code if user type 2, two times and 1, two times. Then there's two maximum elements and both Kinder and Twix should be printed. But how ? I probably can do this with if method but this will make my code even longer. Any cool version? Can I do this with just one if?
a = [0, 0, 0,]
b = ["Kinder", "Twix", "Mars"]
while true
input = gets.chomp.to_i
if input == 1
a[0] += 1
elsif input == 2
a[1] += 1
elsif input == 3
a[2] += 1
elsif input == 0
break
end
end
index = a.index(a.max)
chocolate = b[index] if index
print a.max,chocolate
The question really has nothing to do with how the array a is constructed.
def select_all_max(a, b)
mx = a.max
b.values_at(*a.each_index.select { |i| a[i] == mx })
end
b = ["Kinder", "Twix", "Mars"]
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
See Array#values_at.
This could alternatively be done in a single pass.
def select_all_max(a, b)
b.values_at(
*(1..a.size-1).each_with_object([0]) do |i,arr|
case a[i] <=> arr.last
when 0
arr << i
when 1
arr = [i]
end
end
)
end
p select_all_max [0, 2, 1], b
["Twix"]
p select_all_max [2, 2, 1], b
["Kinder", "Twix"]
p select_all_max [1, 1, 1], b
["Kinder", "Twix", "Mars"]
One way would be as follows:
First, just separate the input-gathering from the counting, so we'll just gather input in this step:
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
Now we can tally up the inputs. If you have Ruby 2.7 you can simply do counts_by_input = inputs.tally to get { "Twix" => 2, "Kinder" => 2 }. Otherwise, my preferred approach is to use group_by with transform_values:
counts_by_input = inputs.group_by(&:itself).transform_values(&:count)
# => { "Twix" => 2, "Kinder" => 2 }
Now, since we're going to be extracting values based on their count, we want to have the counts as keys. Normally we might invert the hash, but that won't work in this case because it will only give us one value per key, and we need multiple:
inputs_by_count = counts_by_input.invert
# => { 2 => "Kinder" }
# This doesn't work, it removed one of the values
Instead, we can use another group_by and transform_values (the reason I like these methods is because they're very versatile ...):
inputs_by_count = counts_by_input.
group_by { |input, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
# => { 2 => ["Twix", "Kinder"] }
The transform_values code here is probably a bit confusing, but one important thing to understand is that often times, calling Enumerable methods on hashes converts them to [[key1, val1], [key2, val2]] arrays:
counts_by_input.group_by { |input, count| count }
# => { 2 => [["Twix", 2], ["Kinder", 2]] }
Which is why we call transform_values { |keyvals| keyvals.map(&:first) } afterwards to get our desired format { 2 => ["Twix", "Kinder"] }
Anyway, at this point getting our result is very easy:
inputs_by_count[inputs_by_count.keys.max]
# => ["Twix", "Kinder"]
I know this probably all seems a little insane, but when you get familiar with Enumerable methods you will be able to do this kind of data transformation pretty fluently.
Tl;dr, give me the codez
inputs = []
loop do
input = gets.chomp.to_i
break if input.zero?
inputs << input
end
inputs_by_count = inputs.
group_by(&:itself).
transform_values(&:count).
group_by { |keyvals, count| count }.
transform_values { |keyvals| keyvals.map(&:first) }
top_count = inputs_by_count.keys.max
inputs_by_count[top_count]
# => ["Twix", "Kinder"]
How about something like this:
maximum = a.max # => 2
top_selling_bars = a.map.with_index { |e, i| b[i] if e == maximum }.compact # => ['Kinder', 'Twix']
p top_selling_bars # => ['Kinder', 'Twix']
If you have
a = [2, 2, 0,]
b = ['Kinder', 'Twix', 'Mars']
You can calculate the maximum value in a via:
max = a.max #=> 2
and find all elements corresponding to that value via:
b.select.with_index { |_, i| a[i] == max }
#=> ["Kinder", "Twix"]

How can I refactor this Ruby method to run faster?

The method below is supposed to take an array a and return the duplicated integer whose second index value is the lowest. The array will only include integers between 1 and a.length. With this example,
firstDuplicate([1,2,3,2,4,5,1])
the method returns 2.
def firstDuplicate(a)
num = 1
big_num_array = []
a.length.times do
num_array = []
if a.include?(num)
num_array.push(a.index(num))
a[a.index(num)] = "x"
if a.include?(num)
num_array.unshift(a.index(num))
num_array.push(num)
end
big_num_array.push(num_array) if num_array.length == 3
end
num += 1
end
if big_num_array.length > 0
big_num_array.sort![0][2]
else
-1
end
end
The code works, but seems longer than necessary and doesn't run fast enough. I am looking for ways to refactor this.
You could count the entries as you go and use Enumerable#find to stop iterating as soon as you find something again:
h = { }
a.find do |e|
h[e] = h[e].to_i + 1 # The `to_i` converts `nil` to zero without a bunch of noise.
h[e] == 2
end
You could also say:
h = Hash.new(0) # to auto-vivify with zeros
a.find do |e|
h[e] += 1
h[e] == 2
end
or use Hash#fetch with a default value:
h = { }
a.find do |e|
h[e] = h.fetch(e, 0) + 1
h[e] == 2
end
find will stop as soon as it finds an element that makes that block true so this should be reasonably efficient.
Here are two ways that could be done quite simply.
Use a set
require 'set'
def first_dup(arr)
st = Set.new
arr.find { |e| st.add?(e).nil? }
end
first_dup [1,2,3,2,4,5,4,1,4]
#=> 2
first_dup [1,2,3,4,5]
#=> nil
See Set#add?.
Use Array#difference
def first_dup(arr)
arr.difference(arr.uniq).first
end
first_dup [1,2,3,2,4,5,4,1,4]
#=> 2
first_dup [1,2,3,4,5]
#=> nil
I have found Array#difference to be sufficiently useful that I proposed it be added to the Ruby core (but it doesn't seem to be gaining traction). It is as follows:
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
As explained at the link, it differs from Array#- as follows:
a = [1,2,2,3,3,2,2]
b = [2,2,3]
a - b
#=> [1]
a.difference(b)
#=> [1,3,2,2]
That is, difference "removes" one 2in a for each 2 in b (similar for 3), preserving the order of what's left of a. a is not mutated, however.
The steps in the example given above for the present problem are as follows.
arr = [1,2,3,2,4,5,4,1,4]
a = arr.uniq
#=> [1,2,3,4,5]
b = arr.difference(a)
#=> [2, 4, 1, 4]
b.first
#=> 2
If you are looking for super performance, ruby is probably not a best language of choice. If you are looking for a readability, here you go:
[1,2,3,2,4,5,1].
map. # or each (less readable, probably faster)
with_index.
group_by(&:shift). # or group_by(&:first)
min_by { |v, a| a[1] && a[1].last || Float::INFINITY }.
first
#⇒ 2

ruby: evaluate a special array as an arbitrarily long ternary if...elsif...else statement (or ternary expression)

I'm storing some decision-making data in arrays that look like: [condition, result, ..., condition, result, default], so basically ternary expressions (c ? r : ... c ? r : d) and I'm evaluating them as such with this piece of code:
class Array
def ternary &block
# the block checks if a condition is true
i = 0
while true
if i % 2 == 1 or i == length - 1
return self[i]
else
i += yield(self[i]) ? 1 : 2
end
end
end
end
['false', 0, 'true', 1, 'true', 2, 3].ternary {|i| i == 'true'}
# => 1
[1, 2, 3, 4, 5].ternary {|i| i > 6}
# => 5 (defaults to last value because all conditions failed)
I'm wondering if there's a faster built-in way of doing this or how I can improve this code.
Note: there are arbitrarily many conditions and [only_possible_answer] should also work
EDIT: The answers so far (tested over 1 000 000 iterations of the same array):
Setup
flat = ['false', 0, 'false', 1, 'false', 2, 'false', 3, 'false', 4, 'true', 5, 6]
nested = [['false', 0], ['false', 1], ['false', 2], ['false', 3], ['false', 4], ['true', 5], [6]]
Option = Struct.new :condition, :result
Default = Struct.new :result
class Default
def call; self; end
# otherwise:
# undefined method ‘call’ for #<struct Default result=whatever>
end
options = [Option.new('false', 0), Option.new('false', 1), Option.new('false', 2), Option.new('false', 3), Option.new('false', 4), Option.new('true', 5)]
class Array
def ternary_flat_slow
i = 0
while true
if i % 2 == 1 or i == length - 1
return self[i]
else
i += yield(self[i]) ? 1 : 2
end
end
end
def ternary_flat_fast # by #illusionist
index = 0
index += 2 until (index >= self.length - 1) || yield(self[index])
return self[index+1] unless index == self.length - 1
self.last
end
def ternary_nested
find {|i| i.length == 1 or yield i[0]} .last
end
def ternary_options default # by #ReinHenrichs
find(default) {|i| yield i} .result
end
def case_when_then_else(&block) # by #Amadan
each_slice(2).find { |x|
x.size == 1 || (block_given? ? block[x[0]] : x[0])
}&.last
end
end
require 'benchmark'
Benchmark.bmbm(9) do |i|
i.report('flat slow') { 1000000.times { flat.ternary_flat_slow {|con| con == 'true' }}}
i.report('flat fast') { 1000000.times { flat.ternary_flat_fast {|con| con == 'true' }}}
i.report(' nested') { 1000000.times { nested.ternary_nested {|con| con == 'true' }}}
i.report(' options') { 1000000.times { options.ternary_options(Default.new(6)) {|con| con.condition == 'true' }}}
i.report(' c_w_t_e') { 1000000.times { flat.case_when_then_else {|con| con == 'true' }}}
end
Results
Rehearsal ---------------------------------------------
flat slow 4.510000 0.030000 4.540000 ( 4.549424) # original
flat fast 3.600000 0.030000 3.630000 ( 3.656058) # #illusionist
nested 6.920000 0.080000 7.000000 ( 7.252300) # me (as suggested)
options 7.030000 0.050000 7.080000 ( 7.130508) # #ReinHenrichs
c_w_t_e 19.320000 0.140000 19.460000 ( 19.593633) # #Amadan
----------------------------------- total: 41.710000sec
user system total real
flat slow 4.290000 0.030000 4.320000 ( 4.339875) # original
flat fast 3.360000 0.020000 3.380000 ( 3.401809) # #illusionist
nested 6.180000 0.040000 6.220000 ( 6.258939) # me (as suggested)
options 6.640000 0.040000 6.680000 ( 6.704991) # #ReinHenrichs
c_w_t_e 18.340000 0.120000 18.460000 ( 18.548176) # #Amadan
However "unrubyish" it is, #illusionist's answer is the fastest and speed is a primary concern
I think this is an XY Problem. You have flattened pairs of values into an array. This forces you to go to a lot of extra effort to test the array indices to recover the information originally contained in the pairs (which element is first, which element is second). All of this extra work could be avoided by simply not flattening the pairs in the first place, thereby retaining their knowledge of which element is which.
Because of this, I am going to provide a solution to the problem you are trying to solve rather than tell you how to implement the solution you have chosen.
You can represent each condition/result pair as an object and then find the first one that matches:
# A condition/result pair
Option = Struct.new :condition, :result
# To hold a default value which duck-types with `result`.
Default = Struct.new :result
options = [Option.new('false', 0), Option.new('true', 1), Option.new('true', 2)]
options.find(Default.new(3)) {|opt| opt.condition == 'true'}.result
# => 1
If you are allergic to creating new objects, you can use 2-element arrays as pairs:
options = [['false', 0], ['true', 1], ['true', 2]]
options.find([nil, 3]) {|condition, result| condition == 'true'}.last
# => 1
But Ruby is an object-oriented language. Creating new objects to solve new problems is exactly what the language is designed for.
fastest among all answers
My solution
require 'minitest/autorun'
class OldArray < Array
def ternary &block
# the block checks if a condition is true
i = 0
while true
if i % 2 == 1 or i == length - 1
return self[i]
else
i += yield(self[i]) ? 1 : 2
end
end
end
end
class NewArray < Array
def ternary
index = 0
# Loop until you find item satisfying the condition skipping one item
index += 2 until (index >= self.length - 1) || yield(self[index])
# return the next value unless its the last/default item
return self[index+1] unless index == self.length - 1
# return default item
self.last
end
end
class TestMe < Minitest::Test
def test_array
assert_equal 5, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6}
assert_equal 4, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 2}
assert_equal 5, NewArray.new([1, 2, 3, 4, 5]).ternary {|i| puts "is: #{i}";i > 3}
assert_equal 1, NewArray.new(['false', 0, 'true', 1, 'true', 2, 3]).ternary {|i|i == 'true'}
end
end
require 'benchmark/ips'
Benchmark.ips do |x|
x.report("My Technique") { NewArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6} }
x.report("Your Technique") { OldArray.new([1, 2, 3, 4, 5]).ternary {|i| i > 6} }
end
Benchmark
Warming up --------------------------------------
My Technique 98.295k i/100ms
Your Technique 73.008k i/100ms
Calculating -------------------------------------
My Technique 1.292M (± 1.9%) i/s - 6.487M in 5.023137s
Your Technique 891.204k (± 1.8%) i/s - 4.526M in 5.080896s
Note: I have created new classes from Array for testing purposes. You can however open the Array class and add the behavior. It will work as fine.
I was going to say this is a bit more Rubyish solution than what you have, but Rein is right - the whole concept is not very Rubyish to start with, and you'd probably profit from asking a different, more specific question.
module ArrayWithCase
refine Array do
def case_when_then_else(&block)
each_slice(2).find { |x|
x.size == 1 || (block_given? ? block[x[0]] : x[0])
}&.last
end
end
end
module Test
using ArrayWithCase
p ['false', 0, 'true', 1, 'true', 2, 3].case_when_then_else {|i| i == 'true'}
# => 1
p [false, 0, true, 1, true, 2, 3].case_when_then_else
# => 1
p [1,2,3,4,5].case_when_then_else { |i| i > 6 }
# => 6
p ["only_possible_answer"].case_when_then_else(&:itself)
# => "only_possible_answer"
p [false, 4].case_when_then_else(&:itself)
# => nil
end

How to check an array that it contains equal number of characters or not using Ruby

I have an array like this ['n','n','n','s','n','s','n','s','n','s'] and I want to check if there are equal counts of characters or not. In the above one I have 6 ns and 4 ss and so they are not equal and I tried, but nothing went correct. How can I do this using Ruby?
Given array:
a = ['n','n','n','s','n','s','n','s','n','s']
Group array by it's elements and take only values of this group:
(f,s) = a.group_by{|e| e}.values
Compare sizes:
f.size == s.size
Result: false
Or you can try this:
x = ['n','n','n','s','n','s','n','s','n','s']
x.group_by {|c| c}.values.map(&:size).inject(:==)
You can go for something like this:
def eq_num? arr
return false if arr.size == 1
arr.uniq.map {|i| arr.count(i)}.uniq.size == 1
end
arr = ['n','n','n','s','n','s','n','s','n','s']
eq_num? arr #=> false
arr = ['n','n','n','s','n','s','s','s']
eq_num? arr #=> true
Works for more than two kinds of letters too:
arr = ['n','n','t','s','n','t','s','s','t']
eq_num? arr #=> true
Using Array#count is relatively inefficient as it requires a full pass through the array for each element whose instances are being counted. Instead use Enumerable#group_by, as others have done, or use a counting hash, as below (see Hash::new):
Code
def equal_counts?(arr)
arr.each_with_object(Hash.new(0)) { |s,h| h[s] += 1 }.values.uniq.size == 1
end
Examples
equal_counts? ['n','n','n','s','n','s','n','s','n','s']
#=> false
equal_counts? ['n','r','r','n','s','s','n','s','r']
#=> true
Explanation
For
arr = ['n','n','n','s','n','s','n','s','n','s']
the steps are as follows.
h = arr.each_with_object(Hash.new(0)) { |s,h| h[s] += 1 }
#=> {"n"=>6, "s"=>4}
a = h.values
#=> [6, 4]
b = a.uniq
#=> [6, 4]
b.size == 1
#=> false

Resources