Related
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"]
SCORECARD = {
"1" => 40,
"2" => 100,
"3" => 300,
"4" => 1200
}
def get_score(arr)
score_base = []
#level = getPoints(arr) / 10
score_base = calculateScore(arr)
end
def getPoints(points)
points.inject(0) do |sum, point|
sum + point
end
end
def calculateScore(lines)
score_base = []
lines.each do |line|
SCORECARD.each do |key, value|
if line == key
score_base << value
end
score_base
end
end
end
describe "Basic tests" do
Test.assert_equals(get_score([0, 1, 2, 3, 4]), 1640)
Test.assert_equals(get_score([0, 1, 1, 3, 0, 2, 1, 2]), 620)
Test.assert_equals(get_score([2, 0, 4, 2, 2, 3, 0, 0, 3, 3]), 3300)
end
describe "Special tests" do
Test.assert_equals(get_score([0]), 0)
Test.assert_equals(get_score([]), 0)
end
Test Results:
Basic tests
Expected: 1640, instead got: [0, 1, 2, 3, 4]
Expected: 620, instead got: [0, 1, 1, 3, 0, 2, 1, 2]
Expected: 3300, instead got: [2, 0, 4, 2, 2, 3, 0, 0, 3, 3]
STDERR
/runner/frameworks/ruby/cw-2.rb:60:in `block in describe': Expected: 1640, instead got: [0,
1, 2, 3, 4] (Test::Error)
from /runner/frameworks/ruby/cw-2.rb:46:in `measure'
from /runner/frameworks/ruby/cw-2.rb:51:in `describe'
from /runner/frameworks/ruby/cw-2.rb:202:in `describe'
from main.rb:46:in `<main>'
PROBLEM: I need assistance with debugging the calculateScore method. Essentially, I would like to iterate through the lines array and the SCORECARD hash, then check if the current line is equal to a hash key, and finally push the value of the matched key into the score_base array.
Once I have pushed all values into the score_base array, I plan to multiply each element within the score_base array by the user's current level, then I will sum all the elements into totalScore.
The output seems to only return the elements within the lines array. Can someone shed some light on what is happening?
EDIT: MY SOLUTION TO PROBLEM
```
SCORECARD = {
"1" => 40,
"2" => 100,
"3" => 300,
"4" => 1200
}
def get_score(arr)
totalScore = multiplyByLevel(arr)
end
def calculateScore(line)
score_base = []
score_base.push SCORECARD.fetch(line.to_s, 0)
score_base.reduce(:+)
end
def multiplyByLevel(points)
experience = 1.0
level = 0
totalScore = 0
points.each do |point|
experience = experience.round(1) + (point.to_f / 10)
if level < 2
totalScore = totalScore + (1 * calculateScore(point))
elsif level >= 2
totalScore = totalScore + (level.round(1) * calculateScore(point))
else
print "Outside loop, level is #{level}"
end
level = experience.to_i
end
totalScore
end
```
EDIT: IN RESPONSE TO BOBRODES FEEDBACK: REFACTORING CALCULATESCORE METHOD
```
def calculateScore(lines)
score_base = []
lines.map {|line| score_base << SCORECARD[line.to_s]}
score_base.compact
end
#=> [40,100,300,1200]
```
Your basic problem is that #each always returns the receiver (the receiver is whatever is to the left of .each). Let's look at your code:
def calculateScore(lines)
score_base = []
lines.each do |line|
SCORECARD.each do |key, value|
if line == key
score_base << value
end
score_base
end # << returns SCORECARD
end # << returns lines
end
So, you need to put score_base here:
def calculateScore(lines)
score_base = []
lines.each do |line|
SCORECARD.each do |key, value|
if line == key
score_base << value
end
end # << returns SCORECARD
end # << returns lines
score_base
end
This is an explanation of the first part of steenslag's answer, but I'd also look at the rest of his advice.
Your code has another problem. The keys in SCORECARD are strings, while the values in your test inputs for lines are integers. So, once you fix the problem you asked about, you will always get an empty array for your result.
But your code also has a bigger problem, which is that you can use #map to get what you want with one simple line of code! You could certainly condense your code a good deal while still using #each, but #map is designed to transform values while iterating through them, and #each is designed to just iterate through them. You want to transform your values (from SCORECARD keys to SCORECARD values), so #map is the simplest way to go.
I don't want to take away all the fun and do it for you, so here's a simple example of #map:
[1, 2, 3, 4].map { |num| num + 1 } #=> [2, 3, 4, 5]
See if you can take that idea with your test input [0, 1, 2, 3, 4] and return [nil, 40, 100, 300, 1200]. Then, work out how to add all those together (there's a method to remove nil values that you will find helpful) and you'll have your one-liner.
The each method of an Array returns the Array itself (from the docs: "each {|item| block} → ary") So that is what you get. Look into map. Note that the getPoints method is not actually used, so no sum is calculated
What I'm trying to do is to create a new array which from an input array and iterate through it. Each item of the new array is the result of the multiplication of the previous and next item of the iteration.
For example:
Array input: [1, 2, 3, 4, 5, 6]
Array_final: [2, 3, 8, 15, 24, 30]
first item: 2*1 (because there's no previous item)
second item: 3*1
third item: 4*2
forth item: 5*3
fifth item: 6*4
sixth item: 6*5 (we use the current item because we don't have a next one)
This is my code and I don't understand why I keep getting array_final = [0, 0, 0, 0, 0, 0]
class Arrays
def self.multply(array)
array_final = []
last_index = array.length-1
array.each_with_index do |num, i|
if i == 0
array_final.push (num[i+1])
elsif i == last_index
array_final.push (num*num[i-1])
else
array_final.push(num[i+1]*num[i-1])
end
end
return array_final
end
end
You're using num as an array, when its an element.
I think you meant:
array.each_with_index do |num, i|
if i == 0
array_final.push (array[i+1])
elsif i == last_index
array_final.push (num*array[i-1])
else
array_final.push(array[i+1]*array[i-1])
end
end
You can use each_cons to get sequential items:
final = [input[0] * input[1]]
input.each_cons(3) do |precedent, _current, subsequent|
final << precedent * subsequent
end
final << input[-1] * input[-2]
Live example
I have a hash as follows:
random_data = {
0=>{"ABC"=>"201604", "CDE"=>"0002874", "TPP"=>"000004971821467", "APD"=>"00182", "PRODUCTID"=>"ACH", "OPP"=>"00000000000176564", "CTC"=>"00000000000286920"},
1=>{"ABC"=>"301604", "CDE"=>"0001074", "TPP"=>"000004971821467", "APD"=>"00182", "PRODUCTID"=>"ACH", "OPP"=>"00000000000119964", "CTC"=>"00000000000211920"}
}
and the 2 arrays as follows:
arr1 = [CHAR,NUMBER,NUMBER,NUMBER,CHAR,NUMBER,CHAR]
arr2 = [6,(7,0),(15,0),(5,0),3,(15,2),17]
The elements of arr1 and arr2 are mapped with the hash values, the conditions are as follows:
if array arr1 's element is char, do nothing with the hash's value.
if array arr1 's element is NUMBER, the corresponding element in hash random_data should get converted to integer, i.e the leading zeros should be removed.
For example, arr1[1] is 'Number', hence for every second element of hash, leading zeros should get removed.
ie. "CDE"=>"0002874" should become "CDE"=>"2874"
If arr1 is NUMBER and arr2 element's second digit is not 0 then decimal should be added in the hash.
For exemple:
arr1[5] = Number
arr2[5] = (15,2)
then 5th element in hash OPP should become like this "OPP"=>"1199.64" i.e for decimal is added for 2 places (15,2)
The outputs is expected as follows:
random_data = {
0=>{"ABC"=>"201604", "CDE"=>"2874", "TPP"=>"4971821467", "APD"=>"182", "PRODUCTID"=>"ACH", "OPP"=>"1765.64", "CTC"=>"00000000000286920"},
1=>{"ABC"=>"301604", "CDE"=>"1074", "TPP"=>"4971821467", "APD"=>"182", "PRODUCTID"=>"ACH", "OPP"=>"1199.64", "CTC"=>"00000000000211920"}
}
arr1 = %w| CHAR NUMBER NUMBER NUMBER CHAR NUMBER CHAR |
#=> ["CHAR", "NUMBER", "NUMBER", "NUMBER", "CHAR", "NUMBER", "CHAR"]
arr2 = [6, [7,0], [15,0], [5,0], 3, [15,2], 17]
enum = arr1.zip(arr2).map { |type, obj| type=="CHAR" ? type : obj.last }.to_enum
#=> #<Enumerator: ["CHAR", 0, 0, 0, "CHAR", 2, "CHAR"]:each>
random_data.each do |k,h|
h.update(h) do |*,v|
obj = enum.next
case obj
when "CHAR"
v
else
x = v.to_i
x = x.to_f/10**obj if obj > 0
x.to_s
end
end
enum.rewind
end
#=> {0=>{"ABC"=>"201604", "CDE"=>"2874", "TPP"=>"4971821467", "APD"=>"182",
# "PRODUCTID"=>"ACH", "OPP"=>"0.17", "CTC"=>"00000000000286920"},
# 1=>{"ABC"=>"301604", "CDE"=>"1074", "TPP"=>"4971821467", "APD"=>"182",
# "PRODUCTID"=>"ACH", "OPP"=>"1199.64", "CTC"=>"00000000000211920"}}
arr1 = %w|CHAR NUMBER NUMBER NUMBER CHAR NUMBER CHAR|
arr2 = [6, [7,0], [15,0], [5,0], 3, [15,2], 17]
types = arr1.zip(arr2)
random_data.map do |_, hash|
hash.map.with_index do |(k, v), idx|
type, digits = types[idx]
[
k,
type == 'NUMBER' ? v.to_i.to_s.tap do |s|
s[/(?=.{#{[*digits][1]}}\z)/] = '.' unless [*digits][1].zero?
end : v
]
end
end
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 }