Ruby count range of values in an array - ruby

I have an array:
[1,2,3,4,5,6,7,8,9,10]
and I need to count three separate things.
1) All values less than or equal to 6.
2) All values equal to 7 or 8.
3) All values greater than 8.
What is the best way to do this without counting individual values and adding them all together?

Use Enumerable#count with a block.
[1,2,3,4,5,6,7,8,9,10].count { |val| val <= 6 }
[1,2,3,4,5,6,7,8,9,10].count { |val| val == 7 || val == 8 }
[1,2,3,4,5,6,7,8,9,10].count { |val| val > 8 }

arr = [1,2,3,4,5,6,7,8,9,10]
arr.select {|e| e <= 6}.size
#=> 6
arr.select {|e| e == 7 || e == 8}.size
#=> 2
arr.select {|e| e > 8}.size
#=> 2

How about this:
>> hash = Hash.new(0)
>> arr = [1,2,3,4,5,6,7,8,9,10]
>> arr.each { |e| hash[(e-7.5).truncate<=>0]+=1 }
>> hash
=> {-1=>6, 0=>2, 1=>2}
Or even more succinct:
>> arr = [1,2,3,4,5,6,7,8,9,10]
>> arr.each_with_object(Hash.new(0)) { |e,h| h[(e-7.5).truncate<=>0]+=1 }
=> {-1=>6, 0=>2, 1=>2}

More interesting, imo, is to answer #Luigi's three questions with a single statement (containing no semicolons). Here are two ways to do it:
a = [1,2,3,4,5,6,7,8,9,10]
a.inject([0,0,0]) {|arr,i| [arr[0]+(i<7 ? 1:0), arr[1]+((i>6 and i<9) ? 1:0), arr[2]+(i>8 ? 1:0)]}
a.slice_before(7).to_a.map {|arr| arr.slice_before(9).to_a}.flatten(1).map(&:size)
# => [6, 2, 2]
Edit: another, inspired by #staafl's answer:
arr.group_by {|e| (e+1)/2-4<=>0}.values.map(&:size)
Can you suggest others?

Related

Keys of a hash whose values sum to a particular value

I have a hash:
a = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
I want to retrieve a set of keys from it such that the sum of their values equals a certain number, for example 5. In such case, the output should be:
Q2 Q5
Please help me on how to get this.
def find_combo(h, tot)
arr = h.to_a
(1..arr.size).find do |n|
enum = arr.combination(n).find { |e| e.map(&:last).sum == tot }
return enum.map(&:first) unless enum.nil?
end
end
h = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
find_combo(h, 5) #=> ["Q2", "Q5"]
find_combo(h, 2) #=> ["Q2"]
find_combo(h, 6) #=> ["Q5", "Q8"]
find_combo(h, 4) #=> ["Q1", "Q5"]
find_combo(h, 8) #=> ["Q2", "Q5", "Q8"]
find_combo(h, 9) #=> ["Q1", "Q2", "Q5", "Q8"]
find_combo(h, 10) #=> nil
Just out of curiosity:
hash = {"Q1"=>1, "Q2"=>2, "Q5"=>3, "Q8"=>3}
arr = hash.to_a
1.upto(hash.size).
lazy.
find do |i|
res = arr.combination(i).find do |h|
h.map(&:last).sum == 5
end
break res if res
end.tap { |result| break result.to_h if result }
#⇒ {"Q2" => 2, "Q5" => 3}

How to return the elements in the array that the variable falls between

I have a unique sorted array: [2,4,6,8,10].
I have a variable called i. If i is 5, I want to return the elements in the array that 5 falls between. In this case [4,6]. If i is 8, then [8,10].
How should I go about this?
I've tried with partition, to some extent. If i happens to be a number directly equal to one of the values in the array. This seems to work:
a=[2,4,6,8,10]
i = 6
a.partition { |v| v < i }.max[0..1] # returns [6,8]
However, if i is a number not directly equal to any of the values in the array. For example 5, it gets a little trickier.
I got it working for the last case:
a=[2,4,6,8,10]
i = 5
partition = a.partition { |v| v < i }
[].tap { |a| a << partition[0].max; a << partition[1].min } # returns [6,8]
While this works, I am looking to see if there is a better way to write this logic.
You could use Enumerable#each_cons.
def mind_the_gap(arr, n)
arr.each_cons(2).find { |l,u| l <= n && n < u }
end
arr = [2,4,6,8,10]
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> nil
mind_the_gap(arr, 10) #=> nil
If you don't want the last two examples to return nil, you could change the method as follows.
def mind_the_gap(arr, n)
rv = arr.each_cons(2).find { |l,u| l <= n && n < u }
return rv unless rv.nil?
n < arr.first ? :low : :high
end
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> :low
mind_the_gap(arr, 10) #=> :high
Another way is to use Enumerable#slice_when.
def mind_the_gap(arr, n)
a = arr.slice_when { |l,u| l <= n && n < u }.to_a
return [a.first.last, a.last.first] unless a.size == 1
n < arr.first ? :low : :high
end
mind_the_gap(arr, 5) #=> [4,6]
mind_the_gap(arr, 8) #=> [8,10]
mind_the_gap(arr, 1) #=> :low
mind_the_gap(arr, 10) #=> :high
If you're looking for elements inside a sorted array, the "better way" probably involves bsearch or bsearch_index.
The second element in the pair is the first element in the array that is greater than your variable, so bsearch_index can return it directly. You need to check it isn't nil or 0 before returning the found element and the previous one :
a = [2, 4, 6, 8, 10]
def find_surrounding_pair(array, element)
second_index = array.bsearch_index { |x| x > element }
array[second_index - 1, 2] if second_index && second_index > 0
end
puts find_surrounding_pair(a, 1).nil?
puts find_surrounding_pair(a, 2) == [2, 4]
puts find_surrounding_pair(a, 7) == [6, 8]
puts find_surrounding_pair(a, 8) == [8, 10]
puts find_surrounding_pair(a, 12).nil?
#=> true * 5
The complexity of this method should be O(log n).
what about this
val = 5
a = [2,4,6,8,10] # assuming it's sorted
a.slice(a.rindex {|e| e <= val}, 2)
It doesn't account for the case when the lookup value is equal or bigger the last element of the array. I'd probably append a nil element for this, if that would be appropriate for the problem.
This looks like a good use to check for the inclusion in a range:
a = [2,4,6,8,10]
b = 5
a.each_cons(2).select { |i, j| (i .. j) === b }
# => [[4, 6]]
It's not clear exactly what you mean by "falls between". In the code above 8 would fall between two sets of numbers:
b = 8
a.each_cons(2).select { |i, j| (i .. j) === b }
# => [[6, 8], [8, 10]]
if the test is i <= b <= j. If it's i <= b < j then use ... instead of ..:
a.each_cons(2).select { |i, j| (i ... j) === b }
# => [[8, 10]]
I'm not a big fan of using ... but it simplifies the code.
From the Range documentation:
Ranges constructed using .. run from the beginning to the end inclusively. Those created using ... exclude the end value.
You could change that to:
a.each_cons(2).select { |i, j| i <= b && b <= j }
or:
a.each_cons(2).select { |i, j| i <= b && b < j }
if those work better for your mind. Using a Range is a little slower, but not radically so.

How do you count the amout of duplicate characters in a string ruby

I have a string
s = "chineedne"
I am trying create a function that can count the amount of duplicate characters in my string or any string
tried
s.each_char.map { |c| c.find.count { |c| s.count(c) > 1 }}
#=> NoMethodError: undefined method `find' for "c":String
Possible solution:
string = "chineedne"
string.chars.uniq.count { |char| string.count(char) > 1 }
#=> 2
or without uniq method to count total amount of duplicated characters:
string = "chineedne"
string.chars.count { |char| string.count(char) > 1 }
#=> 5
In order to get away from N**2 complexity, you also can use group_by method for creating hash with character -> array that include all of this character from string and than just use this hash to get any data that you want:
duplicates = string.chars.group_by { |char| char }.select { |key, value| value.size > 1 }
# or, for Ruby version >= 2.2.1 - string.chars.group_by(&:itself).select { |key, value| value.size > 1 }
than:
> duplicates.keys.size # .keys => ['n', 'e']
#=> 2
and
> duplicates.values.flatten.size # .values.flatten => ["n", "n", "e", "e", "e"]
#=> 5
You can simply count your chars:
chars_frequency = str.each_char
.with_object(Hash.new(0)) {|c, m| m[c]+=1}
=> {"c"=>1, "h"=>1, "i"=>1, "n"=>2, "e"=>3, "d"=>1}
Then just count:
chars_frequency.count { |k, v| v > 1 }
=> 2
Or (if you want to count total amount):
chars_frequency.inject(0) {|r, (k, v)| v > 1 ? r + v : r }
=> 5
I dont know too much about ruby but I think that something like this should work
yourstring = "chineedne"
count = 0
"abcdefghijklmnopqrstuvwxyz".split("").each do |c|
if (yourstring.scan(c).count > 1
count = count+1
end
end
count variable represents the ammount of duplicate characters
https://stackoverflow.com/a/12428037/6371926
string = "chineedne"
# iterate over each chars creatign an array
# downcase, incase we are dealing with case sensitive
arr = string.downcase.chars
# pulling out the uniq char and counting how many times
# they appear in the array more than once
arr.uniq.count {|n| arr.count(n) > 1}

How can I remove the first 3 duplicate values and return an array with the remaining values?

Given these arrays, how do I remove three occurrences of a value while keeping the fourth or fifth in the array?
[1,5,1,1,1] # => [1,5]
[3,3,3,2,3] # => [3,2]
[3,4,5,3,3] # => [4,5]
[1,1,1,1,1] # => [1,1]
[1,2,2,4,5] # => [1,2,2,4,5]
Here's what I've tried:
array = [1,5,1,1,1]
top3 = array.select { |x| array.count(x) >= 3 }[0..2]
last2 = array - top3
This strategy (and similar) only seem to work when there are three duplicates but not four or five. Are there elegant solutions to this problem?
UPDATE: Thank you for your amazing answers. As a beginning rubyist I learned a lot just from analyzing each response. My question came from a Ruby Koan challenge for a dice program. Here's my complete solution implemented with Abdo's suggestion. I'm sure there are more efficient ways to implement the program :)
def score(dice)
a,b,c,d,e = dice
array = [a,b,c,d,e]
total = 0
triples = array.select {|x| array.count(x) >= 3}[0..2]
singles = array.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
# Calculate values for triples
# 1 * 3 = 1000pts
# 2 * 3 = 200pts
# 3 * 3 = 300pts
# 4 * 3 = 400pts
# 5 * 3 = 500pts
# 6 * 3 = 600pts
case triples[0]
when 1 then total += triples[0]*1000
when (2..6) then total += triples[0]*100
end
# Calculate values for singles:
# 1s = 100pts each
# 5s = 50pts each
singles.include? (1) ? singles.select {|x| x == 1 }.each {|x| total += x*100 } : total
singles.include? (5) ? singles.select {|x| x == 5 }.each {|x| total += x*10 } : total
return total
end
puts score([5,1,1, 5, 6]) # 300 points
puts score([]) # 0 points
puts score([1,1,1,5,1]) # 1150 points
puts score([2,3,4,6,2]) # 0 points
puts score([3,4,5,3,3]) # 350 points
puts score([1,5,1,2,4]) # 250 points
array = [1,5,1,1,1]
occurrence = {}
array.select do|a|
if(array.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
PS: This solution preserves the order of the elements in the original array
Here's a faster solution when the size of the array is large:
(I avoid using count because it would loop through the array in an inner loop)
arr.inject({}) {
|h, i| h[i] ||= 0; h[i] += 1; h
}.collect_concat {|k,v| [k] * (v > 2 ? v - 3 : v) }
Here's the fruity comparison to the other working solutions:
arr = 1000.times.collect { rand(100) }.shuffle
require 'fruity'
compare do
vimsha {
occurrence = {};
arr.select do|a|
if(arr.count(a) > 3)
occurrence[a] ||= []
occurrence[a] << a
occurrence[a].count > 3
else
true
end
end
}
caryswoveland {
arr.uniq.reduce([]) {|a,e| a + [e]*((cnt=arr.count(e)) > 2 ? cnt-3 : cnt)}
}
aruprakshit {
num_to_del = arr.find { |e| arr.count(e) >= 3 }
if !num_to_del.nil?
3.times do
ind = arr.index { |e| e == num_to_del }
arr.delete_at(ind)
end
end
arr
}
# edited as suggested by #CarySwoveland
abdo {
arr.each_with_object(Hash.new {|h,k| h[k]=[]}) {|i,h| h[i] += 1
}.collect_concat { |k,v| [k] * (v > 2 ? v - 3 : v) }
}
broisatse {
arr.group_by{|i| i}.values.map{ |a|
a.length > 2 ? a[0, a.length - 3] : a
}.inject([], :+)
}
end
Here's the comparison result:
Running each test 64 times. Test will take about 48 seconds.
broisatse is faster than abdo by 30.000000000000004% ± 10.0%
abdo is faster than aruprakshit by 4x ± 1.0 (results differ: ...)
aruprakshit is similar to caryswoveland (results differ: ...)
caryswoveland is similar to vimsha (results differ: ...)
Note: I took #aruprakshit's code outside the method so we don't waste time in the method call itself.
When the array's size is increased further:
arr = 1000.times.collect { rand(1000) }.shuffle
we get:
abdo is faster than broisatse by 3x ± 1.0
broisatse is faster than aruprakshit by 6x ± 10.0
aruprakshit is faster than caryswoveland by 2x ± 1.0
caryswoveland is similar to vimsha
Another way, assuming order need not be preserved (which is consistent with a comment by the asker):
array = [1,2,4,1,2,1,2,1,1,4]
array.uniq.reduce([]) {|a,e| a + [e]*((cnt=array.count(e)) > 2 ? cnt-3 : cnt)}
#=> [1, 1, 4, 4]
Try something like:
a.group_by{|i| i}.values.map{|a| a[0, a.length % 3]}.inject([], :+)
This will remove all triplets from the array. If you want to remove only the first triplet, then do:
a.group_by{|i| i}.values.map{|a| a.length > 2 ? a[0, a.length - 3] : a }.inject([], :+)
Note: This might mess up the order of the array:
[1,2,1,2,3] #=> [1,1,2,2,3]
Let me know if you need to keep the order and, if so, which elements need to be removed if there are more than three, e.g. what should say: [1,1,2,1,1,] - [1,2] or [2,1]?
x.group_by{|i| i }.values.select{|a| a.size >= 3 }.each{|a| c=[3,a.size].min; x.delete_if{|e| a[0]==e && (c-=1)>=0 } }
It will remove the first [3,a.size].min occurrences of a[0] from the input x where a is, for example, [1,1,1,1] for x = [1,2,1,1,1]
I'd do as below :
def del_first_three(a)
num_to_del = a.find { |e| a.count(e) >= 3 }
return a if num_to_del.nil?
3.times do
ind = a.index { |e| e == num_to_del }
a.delete_at(ind)
end
a
end
del_first_three([3,4,5,3,3]) # => [4, 5]
del_first_three([1,5,1,1,1]) # => [5, 1]
del_first_three([1,2,2,4,5]) # => [1, 2, 2, 4, 5]

Ruby: How to either set a variable to 0 or, if it is already set, increment by 1

I know of the ||= operator, but don't think it'll help me here...trying to create an array that counts the number of "types" among an array of objects.
array.each do |c|
newarray[c.type] = newarray[c.type] ? newarray[c.type]+1 ? 0
end
Is there a more graceful way to do this?
types = Hash.new(-1) # It feels like this should be 0, but to be
# equivalent to your example it needs to be -1
array.each do |c|
types[c.type] += 1
end
Use the Array#fetch method for which you can provide a default value if the index doesn't exist:
array.each do |c|
newarray[c.type] = newarray.fetch(c.type, -1) + 1
end
array.each do |c|
newarray[c.type] = 1 + (newarray[c.type] || -1)
end
Alternatively
array.each do |c|
newarray[c.type] ||= -1
newarray[c.type] += 1
end
||= does help:
types = {}
array.each do |c|
types[c.class] ||= 0
types[c.class] += 1
end
Your variable newarray is named oddly, since in Ruby and most other languages, arrays are indexed by integers, not random Objects like Class. It's more likely this is a Hash.
Also, you should be using c.class, instead of c.type, which is deprecated.
Finally, since you're creating a Hash, you can use inject like so:
newarray = array.inject( {} ) do |h,c|
h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0
h
end
Or, for a one-liner:
newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
As you can see, this gives the desired results:
irb(main):001:0> array = [1, {}, 42, [], Object.new(), [1, 2, 3]]
=> [1, {}, 42, [], #<Object:0x287030>, [1, 2, 3]]
irb(main):002:0> newarray = array.inject( {} ) { |h,c| h[c.class] = h.key?(c.class) ? h[c.class]+1 : 0 ; h }
=> {Object=>0, Hash=>0, Array=>1, Fixnum=>1}
In Ruby 1.8.7 or later you can use group_by and then turn each list of elements into count - 1, and make a hash from the array returned by map.
Hash[array.group_by(&:class).map { |k,v| [k, v.size-1] }]

Resources