In the following code:
def solve(a0, a1, a2, b0, b1, b2)
#score index: 0 = james, 1 = sam
score = Array.new(2, 0)
calcScore = lambda do |x,y|
if ( x > y )
score[0] += 1
end
if ( x < y )
score[1] += 1
end
end
0.upto 2 do |index|
calcScore.call(eval("a#{index}"),eval("b#{index}"))
end
score
end
Is there a more eloquent DRY way to achieve the dynamic variable reference without using:
eval("a#{index}")
While local_variable_get and eval could seem to do their job here, the right approach would be:
def solve(a0, a1, a2, b0, b1, b2)
a, b = [a0, a1, a2], [b0, b1, b2]
# deal with score
0.upto 2 do |index|
calcScore.call(a[index], b[index])
end
score
end
Or, better and more DRY:
def solve(*as_and_bs)
raise unless as_and_bs.size == 6
a, b = as_and_bs(0..2), as_and_bs(3..5)
# deal with score
0.upto 2 do |index|
calcScore.call(a[index], b[index])
end
score
end
Use binding.local_variable_get:
0.upto 2 do |index|
calcScore.call(binding.local_variable_get("a#{index}"),
binding.local_variable_get("b#{index}"))
end
eval is evil. Don't use it. Here's an equivalent code, which should work for any number of scores. It uses the fact that a <=> b returns -1, 0 or 1.
Your input format isn't very convenient. This code uses each_slice and transpose to transform [1,2,3,4,5,6] into [[1, 4], [2, 5], [3, 6]]. You can then iterate over the games to calculate the total score:
def calc_score(a, b)
[[0, 0], [1, 0], [0, 1]][a <=> b]
end
def solve(*scores)
size = scores.size
raise 'Need an even number of scores' unless size.even?
raise 'Need at least two scores' unless size > 0
scores.each_slice(size / 2).to_a.transpose.inject([0, 0]) do |(a_total, b_total), (a, b)|
a_score, b_score = calc_score(a, b)
[a_total + a_score, b_total + b_score]
end
end
or even shorter :
def solve(*scores)
size = scores.size
raise 'Need an even number of scores' unless size.even?
raise 'Need at least two scores' unless size > 0
scores.each_slice(size / 2).to_a.transpose.map do |a, b|
calc_score(a, b)
end.transpose.map{ |s| s.inject(:+) } # .map(&:sum) in Ruby 2.4
end
As an example:
solve(1, 2, 3, 4, 5, 6)
# [0, 3]
solve(2, 0, 0, 3)
# [1, 1]
If you combine a1, a2, and a3 into an array and do the same thing with b, then you can use regular [] indexing:
def solve(a, b)
#score index: 0 = james, 1 = sam
score = Array.new(2, 0)
calcScore = lambda do |x,y|
if ( x > y )
score[0] += 1
end
if ( x < y )
score[1] += 1
end
end
0.upto 2 do |index|
calsScore.call(a[index], b[index])
end
score
end
You could also add a custom error checking for the array lengths:
raise(ArgumentError) unless [a,b].all? { |arr| arr.length == 3 }
Related
The question says:
Given an array of integers.
Return an array, where the first element is the count of positives numbers and the second element is sum of negative numbers. 0 is neither positive nor negative.
If the input is an empty array or is null, return an empty array.
def count_positives_sum_negatives(last)
pos = []
neg = []
x = lst.each
if x % 2 == 0
pos.push x
else neg.push x
end
y = pos.count
z = neg.sum
puts "[#{y},#{z}]"
end
I tested with
count_positives_sum_negatives([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -11, -12, -13, -14, -15])
#should return [10, -65]
I am unsure why my one only gives an error message:
An error occurred while loading spec_helper.
Failure/Error: if (x % 2) == 0
NoMethodError:
undefined method `%' for #<Enumerator: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -11, -12, -13, -14, -15]:each>
# ./lib/solution.rb:7:in `count_positives_sum_negatives'
# ./lib/solution.rb:18:in `<top (required)>'
# ./spec/spec_helper.rb:1:in `require'
# ./spec/spec_helper.rb:1:in `<top (required)>'
No examples found.
No examples found.
You also certainly meant lst.each do |x| rather than x = lst.each. You get the error you do because lst.each without a block returns an Enumerator object, and that does not respond to %.
def count_positives_sum_negatives(lst)
pos = []
neg = []
lst.each do |x|
if x % 2 == 0
pos.push x
else
neg.push x
end
end
y = pos.count
z = neg.sum
puts "[#{y},#{z}]"
end
Note: x % 2 == 0 can be written as x.even?
But you're looking at evens and odds, and your description says count positives and sum negatives. You can use #positive? and #negative? for this, along with #partition.
def count_positives_sum_negatives(lst)
pos, neg = lst.partition(&:positive?)
y = pos.count
z = neg.sum
puts "[#{y},#{z}]"
end
You could also use #each_with_object to give you the info you're looking for.
def count_positives_sum_negatives(lst)
lst.each_with_object([0, 0]) do |x, arr|
if x.positive?
arr[0] += 1
else
arr[1] += x
end
end
end
#Chris has given the reason for your problem.
I think I would be inclined to write it as follows.
def count_positives_sum_negatives(lst)
non_pos = lst.reject(&:positive?)
[lst.size - non_pos.size, non_pos.sum]
end
count_positives_sum_negatives [-1, 2, 0, 0, 1, -4, -7, 0, 6]
#=> [3, -12]
This has the weakness that a temporary array is created (non_pos), but lst.size - non_pos.size and non_pos.sum are both relatively fast calculations, the latter because it is implemented in C. One would have to benchmark, however.
lst.reject(&:positive?) is a short form of lst.reject { |n| n.positive? ).
See Array#reject and Array#sum.
Okay, this final one I've done outputs the correct answer, but I think the portal itself is broken as it cannot tell that [10, -65] is the same as [10, -65]
enter image description here
def count_positives_sum_negatives(lst)
pos = []
neg = []
lst.each do |x|
if x.positive?
pos.push x
else
neg.push x
end
end
y = pos.count
z = neg.sum
final = []
final << y
final << z
print final
end
I have an application whose purpose is to take in coordinates from the command line and output directions from 0,0 to each coordinate eg 0,0 to 1,1 would be EN as in East North. When application reaches that coordinate it would output D as in drop delivery. So if I enter:
“ ./testapplication.rb "5x5 (2, 2) (3, 5)"
The output is:
EENNDENNND
So far I have the following:
#!/home/eamonn/.rbenv/shims/ruby
instructions = ''
addresses = ARGV.to_s.scan(/\(([^\)]+)\)/)
starting_point = ['0, 0']
addresses.each do |point|
starting_point = starting_point[0].split(", ")
destination = point[0].split(", ")
x = destination[0].to_i - starting_point[0].to_i
y = destination[1].to_i - starting_point[1].to_i
if x < 0
instructions << 'W' * x.abs
elsif x > 0
instructions << 'E' * x
end
if y < 0
instructions << 'S' * y.abs
elsif y > 0
instructions << 'N' * y
end
instructions << "D"
starting_point = point
end
puts instructions
While the application works I feel there are a few problems with it such as the efficiency of the code so any pointers are appreciated.
Also I am used to writing ruby on rails applications but as I am writing this as a standalone ruby application I am a bit confused as to how I would run tests for this. I have been looking into using rspec and creating a spec folder and writing tests in there. The testing approach I am considering is:
describe 'testing that application' do
it 'should return the right directions' do
expect(navigate(["5x5 (2, 2) (3, 5)"])).to equal('EENNDENNND')
end
end
Any advice as to whether I should include testing for incorrect input here or just perform some error handling when ARGV is passed into addresses.
You can refactor your code like this.
def generate_instructions(input)
addresses = input.to_s.scan(/\(([^\)]+)\)/)
instructions = ''
# use like array
starting_point = [0, 0]
addresses.each do |point|
sx, sy = starting_point # will set 1. param like first value
arr = point[0].split(", ") # split by , and set inside array
dx, dy = arr[0].to_i, arr[1].to_i # set array inside variables and re-type to -integer
x = dx - sx
y = dy - sy
# add into instructions
instructions << (x < 0 ? 'W' * x.abs : 'E' * x)
instructions << (y < 0 ? 'S' * y.abs : 'N' * y)
instructions << 'D'
# reset points to destination (use it like array)
starting_point = [dx, dy]
end
instructions
end
puts generate_instructions(ARGV) if ARGV
For testing use RSpec
require './testapplication.rb'
describe 'Test output for generate_instructions' do
it 'return EENNDENNND' do
expect(generate_instructions(["5x5 (2, 2) (3, 5)"])).to be == 'EENNDENNND'
end
end
I hope this helps.
There are improvements that can be made, but just to address the subject of testing, here's one approach you could take. Refactor your code to put the process in a single method, like so...
#!/home/eamonn/.rbenv/shims/ruby
def navigate(input)
instructions = ''
addresses = input.to_s.scan(/\(([^\)]+)\)/)
starting_point = ['0, 0']
addresses.each do |point|
starting_point = starting_point[0].split(", ")
destination = point[0].split(", ")
x = destination[0].to_i - starting_point[0].to_i
y = destination[1].to_i - starting_point[1].to_i
if x < 0
instructions << 'W' * x.abs
elsif x > 0
instructions << 'E' * x
end
if y < 0
instructions << 'S' * y.abs
elsif y > 0
instructions << 'N' * y
end
instructions << "D"
starting_point = point
end
instructions
end
if $0 == __FILE__
puts navigate(ARGV)
end
The conditional at the bottom means you will only really take the ARGV values if you're running the script standalone. If it's included in a test script, then we don't.
to test it, you need...
spec/testapplication_spec.rb
require './testapplication.rb'
describe 'testing the navigate method' do
it 'should return the right value' do
expect(navigate(["5x5 (2, 2) (3, 5)"]).to eq('EENNDENNND')
end
end
So in the test you mimic the ARGV input that the navigate method would receive, to see if it returns the correct result.
Here is a more Ruby-like way of making that calculation.
str = ' ./testapplication.rb "5x5 (2, 2) (3, 5) (1, 2)"'
r = /
\( # match a left paren
(\d+) # match one or more digits in capture group 1
,[ ]+ # match a comma followed by one or more spaces
(\d+) # match one or more digits in capture group 2
\) # match a right paren
/x # free-spacing regex definition mode
(conventionally written /\((\d+), +(\d+)\\)/)1.
str.scan(r).
map { |pair| pair.map(&:to_i) }.
unshift([0,0]).
each_cons(2).
map do |(fx,fy), (tx,ty)|
ew = tx-fx
ns = ty-fy
"%s%sD" % [ew >= 0 ? 'E'*ew : 'W'*(-ew), ns > 0 ? 'N'*ns : 'S'*(-ns)]
end.join
#=> "EENNDENNNDWWSSSD"
The steps are as follows2.
a = str.scan(/\((\d+), +(\d+)\)/)
#=> [["2", "2"], ["3", "5"], ["1", "2"]
b = a.map { |pair| pair.map(&:to_i) }
#=> [[2, 2], [3, 5], [1, 2]]
c = b.unshift([0,0])
#=> [[0, 0], [2, 2], [3, 5], [1, 2]]
d = c.each_cons(2)
#=> #<Enumerator: [[0, 0], [2, 2], [3, 5], [1, 2]]:each_cons(2)>
We can see the elements that will be generated by the enumerator d by converting it to an array.
d.to_a
#=> [[[0, 0], [2, 2]], [[2, 2], [3, 5]], [[3, 5], [1, 2]]]
Continuing,
e = d.map do |(fx,fy), (tx,ty)|
ew = tx-fx
ns = ty-fy
"%s%sD" % [ew >= 0 ? 'E'*ew : 'W'*(-ew), ns > 0 ? 'N'*ns : 'S'*(-ns)]
end
#=> ["EENND", "ENNND", "WWSSSD"]
and lastly,
e.join
#=> "EENNDENNNDWWSSSD"
Consider the first element generated by the enumerator d and passed to the block. The block variables are assigned values using disambiguation (aka decomposition) and the block calculation is performed3.
(fx,fy), (tx,ty) = d.next
#=> [[0, 0], [2, 2]]
fx
#=> 0
fy
#=> 0
tx
#=> 2
ty
#=> 2
ew = tx-fx
#=> 2
ns = ty-fy
#=> 2
"%s%sD" % [ew >= 0 ? 'E'*ew : 'W'*(-ew), ns > 0 ? 'N'*ns : 'S'*(-ns)]
#-> "%s%sD" % [true ? 'E'*ew : 'W'*(-ew), true ? 'N'*ns : 'S'*(-ns)]
#-> "%s%sD" % ['E'*ew, 'N'*ns]
#=> "EENND"
The remaining calculations to produce e are similar.
1. When free-spacing regex definition mode is used spaces must be enclosed in character classes (as I've done) or protected in some other way, as all unprotected spaces are removed. Note that a space--followed by a plus sign--is present in the conventionally-written regex. Free-spacing mode has the advantage that it is self-documenting.
2. See String#scan, particularly the treatment of regex groups, Array#unshift and Enumerable#each_cons.
3. See Enumerator#next.
I need to check whether the sum of any 2 elements of an array equals to the given number. This is what I came up with, but it doesn't seem to do the comparison
def sum_comparison(int_array, x)
n = int_array.length
(0..n).each do |i|
(1..n).each do |j|
if ((int_array[i].to_i + int_array[j].to_i) == x)
return true
else
return false
end
end
end
end
Your solution seems overly complicated and strongly influenced by the programming style of low-level procedural languages like C. One apparent problem is that you write
n = int_array.length
(0..n).each do |i|
# use int_array[i].to_i inside the loop
end
Now inside the each loop, you will get the numbers i = 0, 1, 2, ..., n, for example for int_array = [3,4,5] you get i = 0, 1, 2, 3. Notice that there are four elements, because you started counting at zero (this is called an off by one error). This will eventually lead to an array access at n, which is one beyond the end of the array. This will again result in a nil coming back, which is probably why you use to_i to convert that back to an integer, because otherwise you would get a TypeError: nil can't be coerced into Fixnum whend doing the addition. What you probably wanted instead was simply:
int_array.each do |i|
# use i inside the loop
end
For the example array [3,4,5] this would actually result in i = 3, 4, 5. To get the combinations of an array in a more Ruby way, you can for example use Array#combination. Likewise, you can use Array#any? to detect if any of the combinations satisfy the specified condition:
def sum_comparison(array, x)
array.combination(2).any? do |a, b|
a + b == x
end
end
When your function compare first element, it's immediately returns false. You need to return only true when iterating and return false at the end if nothing were found, to avoid this issue:
def sum_comparison(int_array, x)
n = int_array.size
(0...n).each do |i|
(1...n).each do |j|
if (int_array[i].to_i + int_array[j].to_i) == x
return true
end
end
end
false
end
To simplify this you can use permutation or combination and any? methods as #p11y suggests. To get founded elements you could use find or detect.
def sum_comparison(a, x)
a.combination(2).any? { |i, j| i + j == x }
end
a.combination(2).detect { |i, j| i + j == x }
# sum_comparison([1,2,3, 4], 6) => [2, 4]
Using an enumerator:
#!/usr/bin/env ruby
def sum_comparison(int_array, x)
enum = int_array.to_enum
loop do
n = enum.next
enum.peek_values.each do |m|
return true if (n + m) == x
end
end
false
end
puts sum_comparison([1, 2, 3, 4], 5)
Output:
true
Problem
Your method is equivalent to:
def sum_comparison(int_array, x)
return int_array[0].to_i + int_array[1].to_i == x
end
Therefore,
int_array = [1,2,4,16,32,7,5,7,8,22,28]
sum_comparison(int_array, 3) #=> true, just lucky!
sum_comparison(int_array, 6) #=> false, wrong!
Alternative
Here is a relatively efficient implemention, certainly far more efficient than using Enumerable#combination.
Code
def sum_comparison(int_array, x)
sorted = int_array.sort
smallest = sorted.first
sorted_stub = sorted.take_while { |e| e+smallest <= x }
p "sorted_stub = #{sorted_stub}"
return false if sorted_stub.size < 2
loop do
return false if sorted_stub.size < 2
v = sorted_stub.shift
found = sorted_stub.find { |e| v+e >= x }
return true if found && v+found == x
end
false
end
Examples
sum_comparison([7,16,4,12,-2,5,8], 3)
# "sorted_stub = [-2, 4, 5]"
#=> true
sum_comparison([7,16,4,12,-2,5,8], 7)
# "sorted_stub = [-2, 4, 5, 7, 8]"
#=> false
sum_comparison([7,16,4,22,18,12,2,41,5,8,17,31], 9)
# "sorted_stub = [2, 4, 5, 7]"
#=> true
Notes
The line p "sorted_stub = #{sorted_stub}" is included merely to display the array sorted_stub in the examples.
If e+smallest > x for any elements f and g in sorted for which g >= e and f < g, f+g >= e+smallest > x. Ergo, sorted_stub.last is the largest value in sorted that need be considered.
For a given value v, the line found = sorted_stub.find { |e| v+e >= x } stops the search for a second value e for which v+e = x as soon as it finds e such that v+e >= x. The next line then determines if a match has been found.
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}
I have an array of objects and I would like to group them based on the difference between the attributes of 2 adjacent elements. The array is already sorted by that attribute. For instance:
Original array:
array = [a, b, c, d, e]
and
a.attribute = 1
b.attribute = 3
c.attribute = 6
d.attribute = 9
e.attribute = 10
If I want to group the elements such that the difference between the attributes of 2 adjacent elements are less or equal than 2, the result should look like so:
END RESULT
result_array = [[a, b], [c], [d, e]]
WHAT I HAVE
def group_elements_by_difference(array, difference)
result_array = []
subgroup = []
last_element_attribute = array.first.attribute
array.each do |element|
if element.attribute <= (last_element_attribute + difference)
subgroup << element
else
#add the subgroup to the result_array
result_array << subgroup
subgroup = []
subgroup << element
end
#update last_element_attribute
last_element_attribute = element.attribute
end
result_array << subgroup
end
QUESTION
Is there a built in function in Ruby 1.9.3, such as group_by that could replace my group_elements_by_difference?
The following uses numerals directly, but the algorithm should be the same as when you do it with attributes. It assumes that all numerals are greater than 0. If not, then replace it with something that works.
array = [1, 3, 6, 9, 10]
[0, *array].each_cons(2).slice_before{|k, l| l - k > 2}.map{|a| a.map(&:last)}
# => [[1, 3], [6], [9, 10]]
With attributes, do l.attribute, etc., and replace 0 with a dummy element whose attribute is 0.
Following Jan Dvorak's suggestion, this solution uses slice_before and a hash to keep the state:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
initial = { prev: data.first }
data.slice_before(initial) do |item, state|
prev, state[:prev] = state[:prev], item
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end
require 'rspec/autorun'
describe GroupByAdjacentDifference do
let(:a) { double("a", attribute: 1) }
let(:b) { double("b", attribute: 3) }
let(:c) { double("c", attribute: 6) }
let(:d) { double("d", attribute: 9) }
let(:e) { double("e", attribute: 10) }
let(:data) { [a, b, c, d, e] }
let(:service) { described_class.new(data) }
context "#group_by" do
it "groups data by calculating adjacent difference" do
expect(service.group_by(2)).to eq([[a, b], [c], [d, e]])
end
end
end
which gives
$ ruby group_by_adjacent_difference.rb
.
Finished in 0.0048 seconds
1 example, 0 failures
In alternative, local variables could also be used to keep state, although I find it a bit harder to read:
class GroupByAdjacentDifference < Struct.new(:data)
def group_by(difference)
tmp = data.first
data.slice_before do |item|
tmp, prev = item, tmp
value_for(item) - value_for(prev) > difference
end.to_a
end
def value_for(elem)
elem.attribute
end
end
array = [1, 3, 6, 9, 10]
prev = array[0]
p array.slice_before{|el| prev,el = el,prev; prev-el > 2}.to_a
# => [[1, 3], [6], [9, 10]]