Using hash to tell positive, odd, even, and negative numbers - ruby

I have an array:
ak = [10, 20, 3, 4, 5, -5, 28, 27]
I want a solution like this:
#even:4
#odd:3
#positive:7
#negative:1
How do I use hash to do that?

You could do this in a fairly general (reusable) way as follows.
Code
def analyze_array(ak, ops)
ops.each_with_object({}) { |(k,m),h| h.update(k=>ak.count(&m)) }
end
Example
ak = [10, 20, 3, 4, 5, -5, 28, 27]
ops = [[:even, :even? ],
[:odd, :odd? ],
[:positive, ->(n) { n>0 }],
[:negative, ->(n) { n<0 }]]
analyze_array(ak, ops)
#=> {:even=>4, :odd=>4, :positive=>7, :negative=>1}
Explanation
For the example above:
enum = ops.each_with_object({})
#=> #<Enumerator: [[:even, :even?], [:odd, :odd?],
# [:positive, #<Proc:0x007fe90395aaf8#(irb):9 (lambda)>],
# [:negative, #<Proc:0x007fe90395aaa8#(irb):10 (lambda)>]]
# :each_with_object({})>
Note that :even? and :odd? are symbols (not to be confused with methods).
The elements of enum are passed into the block by Enumerator#each, which calls Array#each. We can see what the elements of enum are by converting it to an array:
enum.to_a
#=> [[[:even, :even?], {}], [[:odd, :odd?], {}],
# [[:positive, #<Proc:0x007fe90395aaf8#(irb):9 (lambda)>], {}],
# [[:negative, #<Proc:0x007fe90395aaa8#(irb):10 (lambda)>], {}]]
and simulate the passing of the (4) elements of enum into the block with Enumerator#next. The first element of enum ([[:even, :even?], {}]) is passed to the block and assigned to the block variables:
(k,m),h = enum.next
#=> [[:even, :even?], {}]
k #=> :even
m #=> :even?
h #=> {}
Next, we use Hash#update (aka merge!) to merge a one-key hash into h and return the new value of h:
h.update(k=>ak.count(&m))
#=> {}.update(:even=>[10, 20, 3, 4, 5, -5, 28, 27].count(&:even?))
#=> {:even=>4}
(Ruby allows us to write (k=>ak.count(&m)) as shorthand for ({k=>ak.count(&m)})).
As usual, & invokes Symbol#to_proc to convert the symbol :even? to a proc and then converts the proc to a block for count.
The next value of enum is then passed to the block ("odd"), similar calculations are performed and the hash { :odd=>4 } is merged into h, resulting in:
h #=> { :even=>4, :odd=>4 }
The third and fourth values of enum are then passed to the block. The only difference is that m in ak.count(&m) is already a proc (a lambda, actually), so & just converts it to a block for count.

h = Hash.new
h["even"] = ak.select {|x| x.even? && x > 0}.count
h["odd"] = ak.select {|x| x.odd? && x > 0}.count
h["positive"] = ak.select {|x| x > 0}.count
h["negative"] = ak.select {|x| x < 0}.count
and add
put h

Another solution:
ak = [10, 20, 3, 4, 5, -5, 28, 27]
akp = ak.select{ |n| n > 0 }
h = {
even: akp.count(&:even?),
odd: akp.count(&:odd?),
positive: akp.count,
negative: ak.count{ |n| n < 0 }
}
puts ak, h

Assuming (based on the output that you expect) that you only want the positive even or odd numbers:
h = Hash.new
h["even"] = ak.select {|x| x.even? && x > 0}.count
h["odd"] = ak.select {|x| x.odd? && x > 0}.count
h["positive"] = ak.select {|x| x > 0}.count
h["negative"] = ak.select {|x| x < 0}.count
puts h

You can iterate over you array and test each value like this:
def evaluate(array)
response = { even: 0, odd: 0, positive: 0, negative: 0 }
array.each do |item|
response[:even] += 1 if item.even?
response[:odd] += 1 if item.odd?
...
end
response
end
Or something like that. You can optimize it after.

def calculate(arr)
even = arr.select {|e| e.even?}.size
odd = arr.select {|e| e.odd?}.size
pos = arr.select {|e| e >= 0}.size
neg = arr.select {|e| e < 0}.size
hash = {even: even, odd: odd, positive: pos, negative:neg}
end

Related

How to get each value of inject loop

I want to get each value of inject.
For example [1,2,3].inject(3){|sum, num| sum + num} returns 9, and I want to get all values of the loop.
I tryed [1,2,3].inject(3).map{|sum, num| sum + num}, but it didn't work.
The code I wrote is this, but I feel it's redundant.
a = [1,2,3]
result = []
a.inject(3) do |sum, num|
v = sum + num
result << v
v
end
p result
# => [4, 6, 9]
Is there a way to use inject and map at same time?
Using a dedicated Eumerator perfectly fits here, but I would show more generic approach for this:
[1,2,3].inject(map: [], sum: 3) do |acc, num|
acc[:map] << (acc[:sum] += num)
acc
end
#⇒ => {:map => [4, 6, 9], :sum => 9}
That way (using hash as accumulator) one might collect whatever she wants. Sidenote: better use Enumerable#each_with_object here instead of inject, because the former does not produce a new instance of an object on each subsequent iteration:
[1,2,3].each_with_object(map: [], sum: 3) do |num, acc|
acc[:map] << (acc[:sum] += num)
end
The best I could think
def partial_sums(arr, start = 0)
sum = 0
arr.each_with_object([]) do |elem, result|
sum = elem + (result.empty? ? start : sum)
result << sum
end
end
partial_sums([1,2,3], 3)
You could use an enumerator:
enum = Enumerator.new do |y|
[1, 2, 3].inject (3) do |sum, n|
y << sum + n
sum + n
end
end
enum.take([1,2,3].size) #=> [4, 6, 9]
Obviously you can wrap this up nicely in a method, but I'll leave that for you to do. Also don't think there's much wrong with your attempt, works nicely.
def doit(arr, initial_value)
arr.each_with_object([initial_value]) { |e,a| a << e+a[-1] }.drop 1
end
arr = [1,2,3]
initial_value = 4
doit(arr, initial_value)
#=> [5, 7, 10]
This lends itself to being generalized.
def gen_doit(arr, initial_value, op)
arr.each_with_object([initial_value]) { |e,a| a << a[-1].send(op,e) }.drop 1
end
gen_doit(arr, initial_value, :+) #=> [5,7,10]
gen_doit(arr, initial_value, '-') #=> [3, 1, -2]
gen_doit(arr, initial_value, :*) #=> [4, 8, 24]
gen_doit(arr, initial_value, '/') #=> [4, 2, 0]
gen_doit(arr, initial_value, :**) #=> [4, 16, 4096]
gen_doit(arr, initial_value, '%') #=> [0, 0, 0]

Checking to see if 2 numbers in array sum to 0 in Ruby

I've been going at this problem for a few hours, and I can't see why I can't get it to run properly. The end game to this method is having 2 numbers in an array equaling zero when added together. Here is my code:
def two_sums(nums)
i = 0
j = -1
while i < nums.count
num_1 = nums[i]
while j < nums.count
num_2 = nums[j]
if num_1 + num_2 == 0
return "There are 2 numbers that sum to zero & they are #{num_1} and #{num_2}."
else
return "Nothing adds to zero."
end
end
i += 1
j -= 1
end
end
The problem I'm having is unless the first and last number in the array are the positive and negative of the same number, this will always return false.
For example, if I had an array that was [1, 4, 6, -1, 10], it should come back true. I'm sure my 2 while statement is the cause of this, but I can't think of a way to fix it. If someone could point me in the right direction, that would be helpful.
You can find the first pair that adds up to 0 like this:
nums.combination(2).find { |x, y| x + y == 0 }
#=> returns the first matching pair or nil
Or if you want to select all pairs that add up to 0:
nums.combination(2).select { |x, y| x + y == 0 }
#=> returns all matching pairs or an empty array
Therefore you can implement your method like this:
def two_sums(nums)
pair = nums.combination(2).find { |x, y| x + y == 0 }
if pair
"There are 2 numbers that sum to zero & they are #{pair.first} and #{pair.last}."
else
"Nothing adds to zero."
end
end
Or if you want to find all pairs:
def two_sums(nums)
pairs = nums.combination(2).select { |x, y| x + y == 0 }
if pairs.empty?
"Nothing adds to zero."
else
"The following pairs sum to zero: #{pairs}..."
end
end
Here's another way:
Code
def sum_to_zero(arr)
arr.group_by { |e| e.abs }
.values
.select { |a| (a.size > 1 && a.first == 0) || a.uniq.size > 1 }
end
Examples
sum_to_zero [1, 4, 6, -1, 10] #=> [[1, -1]]
sum_to_zero [1, 4, 1, -2, 10] #=> []
sum_to_zero [1, 0, 4, 1, 0, -1] #=> [[1, 1, -1], [0, 0]]
This method is relatively fast. Let's try it with an array of 200,000 elements, each a random number between -500,000 and 500,000.
require 'time'
t = Time.now
arr = Array.new(200_000) { rand(1_000_001) - 500_000 }
arr.size #=> 200000
sum_to_zero(arr).size #=> 16439
Time.now - t
#=> 0.23 (seconds)
sum_to_zero(arr).first(6)
#=> [[-98747, 98747],
# [157848, -157848],
# [-459650, 459650],
# [176655, 176655, -176655],
# [282101, -282101],
# [100886, 100886, -100886]]
If you wish to group the non-negative and negative values that sum to zero:
sum_to_zero(arr).map { |a| a.partition { |e| e >= 0 } }.first(6)
#=> [[[98747], [-98747]],
# [[157848], [-157848]],
# [[459650], [-459650]],
# [[176655, 176655], [-176655]],
# [[282101], [-282101]],
# [[100886, 100886], [-100886]]]
If you only want a single value for each group (a non-negative value, say):
sum_to_zero(arr).map { |a| a.first.abs }.first(6)
#=> [98747, 157848, 459650, 176655, 282101, 100886]
I think the most Ruby way would be:
nums.combination(2).any? { |x,y| (x+y).zero? }
Here's a way that should work well for large arrays. The methods above which go through every possible combination of two numbers are perfectly fine for small cases but will be very slow and memory hungry for arrays with lots of elements.
def two_sums nums
h = Hash.new
nums.each do |n|
return true if h[-n]
h[n] = true
end
false
end
Well, given it's tagged as #ruby, here's the most "ruby way" I could think of tackling this problem:
def two_sums(arr)
numbers = arr.combination(2).select { |a| a.reduce(:+) == 0 }.flatten
if numbers.empty?
"Nothing adds to zero."
else
"There are 2 numbers that sum to zero & they are #{numbers.first} and #{numbers.last}."
end
end
array.combination(2).select{|x|x[0] + x[1] == 0}

Best way to partition a sorted array into arrays of contiguous numbers?

Is there an easy way or a method to partition an array into arrays of contiguous numbers in Ruby?
[1,2,3,5,6,8,10] => [[1,2,3],[5,6],[8],[10]]
I can make some routine for that but wonder if there's a quick way.
Sam
I like to inject:
numbers = [1, 2, 3, 5, 6, 8, 10]
contiguous_arrays = []
contiguous_arrays << numbers[1..-1].inject([numbers.first]) do |contiguous, n|
if n == contiguous.last.succ
contiguous << n
else
contiguous_arrays << contiguous
[n]
end
end
#=> [[1, 2, 3], [5, 6], [8], [10]]
A smörgåsbord of approaches, with:
arr = [1,2,3,5,6,8,10]
#1
# If subarray is empty or the current value n is not the last value + 1,
# add the subarray [n] to the collection; else append the current value
# to the last subarray that was added to the collection.
arr.each_with_object([]) { |n,a|
(a.empty? || n != a.last.last+1) ? a << [n] : a[-1] << n }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#2
# Change the value of 'group' to the current value n if it is the first
# element in arr or it is not equal to the previous element in arr + 1,
# then 'chunk' on 'group' and extract the result from the resulting chunked
# array.
arr.map.with_index do |n,i|
group = n if i == 0 || n != arr[i-1] + 1
[n, group]
end.chunk(&:last)
.map { |_,c| c.map(&:first) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
#3
# If n is the last element of arr, append any number other than n+1 to
# a copy of arr and convert to an enumerator. Step though the enumerator
# arr.size times, adding the current value to a subarray b, and using
# 'peek' to see if the next value of 'arr' equals the current value plus 1.
# If it does, add the subarray b to the collecton a and set b => [].
enum = (arr+[arr.last]).to_enum
a, b = [], []
arr.size.times do
curr = enum.next
b << curr
(a << b; b = []) unless curr + 1 == enum.peek
end
end
a
#=> [[1, 2, 3], [5, 6], [8], [10]]
#4
# Add elements n of arr sequentially to an array a, each time first inserting
# an arbitrary separator string SEP when n does not equal the previous value
# of arr + 1, map each element of a to a string, join(' '), split on SEP and
# convert each resulting array of strings to an array of integers.
SEP = '+'
match_val = arr.first
arr.each_with_object([]) do |n,a|
(a << SEP) unless n == match_val
a << n
match_val = n + 1
end.map(&:to_s)
.join(' ')
.split(SEP)
.map { |s| s.split(' ').map(&:to_i) }
#=> [[1, 2, 3], [5, 6], [8], [10]]
All of the above methods work when arr contains negative integers.
arr = [1,2,3,5,6,8,10]
prev = arr[0]
result = arr.slice_before { |e|
prev, prev2 = e, prev
e != prev2.succ
}.entries
p result
Not very original, lifted right out of the Ruby docs actually.
Another method with enumerator:
module Enumerable
def split_if
enum = each
result = []
tmp = [enum.peek]
loop do
v1, v2 = enum.next, enum.peek
if yield(v1, v2)
result << tmp
tmp = [enum.peek]
else
tmp << v2
end
end
result
end
end
[1,2,3,5,6,8,10].split_if {|i,j| j-i > 1}
Or:
class Array
def split_if(&block)
prev_element = nil
inject([[]]) do |results, element|
if prev_element && block.call(prev_element, element)
results << [element]
else
results.last << element
end
prev_element = element
results
end
end
end
Just do it iteratively.
x = [1,2,3,5,6,8,10]
y = []; z = []
(1..x.length - 1).each do |i|
y << x[i - 1]
if x[i] != x[i-1] + 1
z << y
y = []
end
end
y << x[x.length - 1]
z << y
z
# => [[1, 2, 3], [5, 6], [8], [10]]

Get total number of ranges of a given length in an array

I have an array total of 12 elements, each element represents and int. For instance total[0] = 1. I have another array remaining that is total - occupied spaces. remaining will have fewer elements that total.
I want to write a method that can look in total for instances where there are >= size gaps between consecutive ints in the array. For example:
If `foo.total = [1,2,6,7,8,9,]`
then when I call `foo.number_of_slots_available(3)`
I get `2` (because 3,4,5 is not included and 10,11,12 is not included)
Here are the beginnings of my method:
def number_of_slots(size)
total_array = (1..12).to_a
occupied_spaces = some_map.to_a
remaining_array = total_array - occupied_spaces
return ????
end
Enumerable#chunk is the good way to go. Look below.
arr = [1,2,6,7,8,9]
rng = (1..12).to_a
rng.chunk { |i| arr.include? i }.to_a
# => [[true, [1, 2]],
# [false, [3, 4, 5]],
# [true, [6, 7, 8, 9]],
# [false, [10, 11, 12]]]
rng.chunk { |i| arr.include? i }.count{|j| j[0] == false}
# => 2
Edit
"I want to write a method that can look in total for instances where there are >= size gaps between consecutive ints in the array"
arr = [1,2,3,6,7,8,9,10,11]
rng = (1..15).to_a
rng.chunk { |i| arr.include? i }.to_a
# => [[true, [1, 2, 3]],
# [false, [4, 5]],
# [true, [6, 7, 8, 9, 10, 11]],
# [false, [12, 13, 14, 15]]]
rng.chunk { |i| arr.include? i }.count{|j| j[0] == false and j[1].size >= 3 }
# => 1
rng.chunk { |i| arr.include? i }.count{|j| j[0] == false and j[1].size >= 2 }
# => 2
# J[1] is the array,whose size is the actual gap size.
If total is sorted is a simple algorithm and should be something like this (I might have some syntax errors):
def containsGaps(total, gap)
int i = 0;
int count = 0;
while i < total.length -1 do
if total[i+1] - total[i] >= gap then count++;
end
return count;
end
And your return might be:
return containsGaps(total_array, size);
Here is a way I found of doing it. I modified the method a bit adding in the array to be passed along with the size.
#!/usr/bin/ruby
arr = [1,2,6,7,8,9]
bar = [1,2,3,6,7,10]
def number_of_slots(arr, size)
count = 0
range = (1..12).to_a
# arr.sort! (if the array is not always sorted)
range.each_cons(size) do |chunk|
if (chunk & arr).size == 0
count += 1
end
end
count
end
puts number_of_slots(arr, 3)
puts number_of_slots(bar, 2)
Output:
2
3

Removing elements from array Ruby

Let's say I am trying to remove elements from array a = [1,1,1,2,2,3]. If I perform the following:
b = a - [1,3]
Then I will get:
b = [2,2]
However, I want the result to be
b = [1,1,2,2]
i.e. I only remove one instance of each element in the subtracted vector not all cases. Is there a simple way in Ruby to do this?
You may do:
a= [1,1,1,2,2,3]
delete_list = [1,3]
delete_list.each do |del|
a.delete_at(a.index(del))
end
result : [1, 1, 2, 2]
[1,3].inject([1,1,1,2,2,3]) do |memo,element|
memo.tap do |memo|
i = memo.find_index(e)
memo.delete_at(i) if i
end
end
Not very simple but:
a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,3].count(k)}.values.flatten
=> [1, 1, 2, 2]
Also handles the case for multiples in the 'subtrahend':
a = [1,1,1,2,2,3]
b = a.group_by {|n| n}.each {|k,v| v.pop [1,1,3].count(k)}.values.flatten
=> [1, 2, 2]
EDIT: this is more an enhancement combining Norm212 and my answer to make a "functional" solution.
b = [1,1,3].each.with_object( a ) { |del| a.delete_at( a.index( del ) ) }
Put it in a lambda if needed:
subtract = lambda do |minuend, subtrahend|
subtrahend.each.with_object( minuend ) { |del| minuend.delete_at( minuend.index( del ) ) }
end
then:
subtract.call a, [1,1,3]
A simple solution I frequently use:
arr = ['remove me',3,4,2,45]
arr[1..-1]
=> [3,4,2,45]
a = [1,1,1,2,2,3]
a.slice!(0) # remove first index
a.slice!(-1) # remove last index
# a = [1,1,2,2] as desired
For speed, I would do the following, which requires only one pass through each of the two arrays. This method preserves order. I will first present code that does not mutate the original array, then show how it can be easily modified to mutate.
arr = [1,1,1,2,2,3,1]
removals = [1,3,1]
h = removals.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 3=>1}
arr.each_with_object([]) { |n,a|
h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n }
#=> [1, 2, 2, 1]
arr
#=> [1, 1, 1, 2, 2, 3, 1]
To mutate arr write:
h = removals.group_by(&:itself).transform_values(&:count)
arr.replace(arr.each_with_object([]) { |n,a|
h.key?(n) && h[n] > 0 ? (h[n] -= 1) : a << n })
#=> [1, 2, 2, 1]
arr
#=> [1, 2, 2, 1]
This uses the 21st century method Hash#transform_values (new in MRI v2.4), but one could instead write:
h = Hash[removals.group_by(&:itself).map { |k,v| [k,v.size] }]
or
h = removals.each_with_object(Hash.new(0)) { | n,h| h[n] += 1 }

Resources