Best combination based in rules [closed] - ruby

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
Which combination of five entries has the largest combined scores subject to the condition?

If the dataset is not huge, here you go (possibly quite inefficient):
data =
%|Derek Aufderhar, 2134, 1
Hadley Kuhn, 2044, 0
Myrtie Lueilwitz, 2207, 2
Mitchell Schiller, 2036, 2
Javier Walter MD, 2485, 4
Waino Leuschke, 2486, 2
Ariel Jacobson, 2015, 3
Melvin Bailey, 2485, 0
Dovie Emmerich, 2383, 4
Adrian Stroman Jr., 2180, 1
Helen Douglas, 2352, 4
Yessenia O’Reilly, 2247, 2|
# unnecessary: transform to hash for clarity
values =
data.
split($/).
map { |e| e.split(',') }.
map { |name, elo, score| {name: name, elo: elo.to_i, score: score.to_i } }
# find the top
values.
combination(5).
reject { |data| data.map { |e| e[:elo] }.inject(:+) > 11_000 }.
max { |data| data.map { |e| e[:score] }.inject(:+) }
#⇒ [{:name=>"Yessenia O'Reilly", :elo=>2247, :score=>2},
# {:name=>"Helen Douglas", :elo=>2352, :score=>4},
# {:name=>"Adrian Stroman Jr.", :elo=>2180, :score=>1},
# {:name=>"Ariel Jacobson", :elo=>2015, :score=>3},
# {:name=>"Mitchell Schiller", :elo=>2036, :score=>2}]

Code
def best_five(players, max_elo)
players.combination(5).with_object({ names:[], tot_scores: -1 }) do |arr, best|
names, elos, scores = arr.map(&:values).transpose
best.replace({ names: names, tot_scores: scores.sum }) unless
elos.sum > max_elo || scores.sum <= best[:tot_scores]
end
end
Here players is an array of hashes, each with keys :name, :elo and score, where the value of :name is a string and values of the other two keys are integers.
Example
players =<<_
Derek Aufderhar, 2134, 1
Hadley Kuhn, 2044, 0
Myrtie Lueilwitz, 2207, 2
Mitchell Schiller, 2036, 2
Javier Walter MD, 2485, 4
Waino Leuschke, 2486, 2
Ariel Jacobson, 2015, 3
Melvin Bailey, 2485, 0
Dovie Emmerich, 2383, 4
Adrian Stroman Jr., 2180, 1
Helen Douglas, 2352, 4
Yessenia O’Reilly, 2247, 2
_
It is convenient to convert this string to a hash, both to address the current problem and to perform other operations with the data.
players_by_name = players.each_line.with_object({}) do |line, h|
name, elo, score = line.split(',')
h[name] = { name: name, elo: elo.to_i, score: score.to_i }
end
#=> {"Derek Aufderhar" =>{:name=>"Derek Aufderhar", :elo=>2134, :score=>1},
# "Hadley Kuhn" =>{:name=>"Hadley Kuhn", :elo=>2044, :score=>0},
# ...
# "Yessenia O’Reilly"=>{:name=>"Yessenia O’Reilly", :elo=>2247, :score=>2}}
We may now compute the best five for max_elo = 11000:
best = best_five(players_by_name.values, 11000)
#=> {:names=>["Myrtie Lueilwitz", "Mitchell Schiller", "Ariel Jacobson",
# "Dovie Emmerich", "Helen Douglas"],
# :tot_scores=>15}
To retrieve information for these five players we compute the following:
a = players_by_name.values_at(*best[:names])
#=> [{:name=>"Myrtie Lueilwitz" , :elo=>2207, :score=>2},
# {:name=>"Mitchell Schiller", :elo=>2036, :score=>2},
# {:name=>"Ariel Jacobson" , :elo=>2015, :score=>3},
# {:name=>"Dovie Emmerich" , :elo=>2383, :score=>4},
# {:name=>"Helen Douglas" , :elo=>2352, :score=>4}]
We already know the scores sum to 15. As
a.map { |h| h[:elo] }.sum
#=> 10993
we see that the combined ELO limit is not exceeded.
Array#sum made its debut in Ruby v2.4.

Related

How to calculate percentage of sum hash value in ruby [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I have static json file which contains:
{
"homes": {
"person_a": "windows+tables",
"person_b": "lights+tables",
"person_c": "doors+curtains"
}
}
The application should, for each request, calculate quotes for the 3 insurers. The business requirement is as follows:
The quote is a 10% of the rate if 2 covers are matched or 20% if just 1 cover is matched and that is the biggest requested, 25% if it is the second biggest or 30% if is the third.
the request from user is as follow
{:curtains=>20, :tables=>30, :windows=>50}
The system should not return a quote if the value is zero(0)
The application will then calculate the following quotes:
person_a: 8 (10% of 80 (two matches on windows and tables))
person_b: 7.5 (25% of 30 (one match on contents, the 2nd biggest cover))
insurer_c: 6 (30% of 20 (one match on curtains, the 3rd biggest cover)
This is my solution:
require_relative './rules'
module Coverage
class CalculateQuotes
def initialize(quotes)
#quotes = quotes
end
def get_rates
result = []
#insurer = Coverage::Rules.parse_file ## which will give {
#"insurer_rates": {
#"person_a": "windows+tables",
# "person_b": "lights+tables",
#"person_c": "doors+curtains"
# }}
#insurer[:insurer_rates].each do |k, v|
#match_covers = match_cover(v.split("+"))
result << [k, calculate_rate ]
end
end
def match_cover(covers)
covers = covers.map { |x| x.to_sym }
#quotes.select { |k,v| covers.include?(k) }
end
def calculate_rate
premium = 0.0
persentage = get_percentage_by_match_covers
#match_covers.values.each do |v|
premium += v * persentage
end
premium == 0 ? nil : premium
end
def get_percentage_by_match_covers
if #match_covers.size == 2
0.1
elsif #match_covers.size == 1
only_1_match_covers
else
0
end
end
def only_1_match_covers
index = position_of_customer_request
case index
when 0
0.2
when 1
0.25
when 2
0.3
else
raise StandardError
end
end
def position_of_customer_request
(#quotes.to_a.reverse).index(#match_covers.to_a.flatten)
end
end
end
request = {:windows=>50, :contents=>30, :engine=>20}
Coverage::CalculateQuotes.new(request).get_rates
Please help me on How can I make it better calculation and code with SOLID ruby principles??
Data
double_quote_rate = 0.1
single_quote_rate = [0.3, 0.25, 0.2]
request = {:curtains=>20, :tables=>30, :windows=>50}
Code
The key is to create a hash that maps sets of products into their computed values.
The first step is to create the key-value pairs where the key is a set containing a single product:
require 'set'
h = single_quote_rate.zip(request.sort_by(&:last)).
each_with_object({}) { |(rate, (product, score)),h|
h[[product].to_set] = rate*score }
#=> {#<Set: {:curtains}>=>6.0, #<Set: {:tables}>=>7.5,
# #<Set: {:windows}>=>10.0}
Note that the values in single_quote_rate are ordered largest to smallest. The intermediate calculation is as follows:
single_quote_rate.zip(request.sort_by(&:last))
#=> [[0.3, [:curtains, 20]], [0.25, [:tables, 30]],
# [0.2, [:windows, 50]]]
Now add all combinations of two products:
request.to_a.combination(2).each { |(product1, score1),(product2,score2)|
h[[product1,product2].to_set] = double_quote_rate*(score1+score2) }
h #=> {#<Set: {:curtains}>=>6.0,
# #<Set: {:tables}>=>7.5,
# #<Set: {:windows}>=>10.0,
# #<Set: {:curtains, :tables}>=>5.0,
# #<Set: {:curtains, :windows}>=>7.0,
# #<Set: {:tables, :windows}>=>8.0}
Here the first calculation is as follows:
enum = request.to_a.combination(2)
#=> #<Enumerator: [[:curtains, 20], [:tables, 30],
# [:windows, 50]]:combination(2)>
We can convert this enumerator to an array to see the three elements (arrays) that will be passed to the block.
enum.to_a
#=> [[[:curtains, 20], [:tables, 30]],
# [[:curtains, 20], [:windows, 50]],
# [[:tables, 30], [:windows, 50]]]
The block variables are assigned as follows:
(product1, score1),(product2,score2) = enum.next
#=> [[:curtains, 20], [:tables, 30]]
product1
#=> :curtains
score1
#=> 20
product2
#=> :tables
score2
#=> 30
Breaking up arrays into their component elements is called array decompostion.
For convenience assign the keys of request to a variable:
keys = request.keys
#=> [:curtains, :tables, :windows]
Example
hash = { "homes": { "person_a": "windows+tables",
"person_b": "lights+tables",
"person_c": "doors+curtains" } }
hash[:"homes"].transform_values do |s|
h[s.split('+').map(&:to_sym).select { |s| keys.include?(s) }.to_set]
end
#=> {:person_a=>8.0, :person_b=>7.5, :person_c=>6.0}
An example calculation of the key to use to obtain the desired value of h is as follows:
s = "lights+tables"
a = s.split('+')
#=> ["lights", "tables"]
b = a.map(&:to_sym)
#=> [:lights, :tables]
c = b.select { |s| keys.include?(s) }
#=> [:tables]
d = c.to_set
#=> #<Set: {:tables}>
h[d]
#=> 7.5

How do I count unique multiple words in a Ruby string?

Trying to write a Ruby code that will count unique words and return their total occurrences.
So suppose I want to find number of occurrences for Sally, Marina and Tina in the following sentence "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner."
I tried the following but this defeats the dry principal. Is there a better way?
string = "Monday Tina will meet Sally and Harris. Then Tina will visit her mom Marina. Marina and Tina will meet David for dinner. Sally will then take Tina out for a late night party."
puts "Marina appears #{string.split.count("brown").to_i} times."
puts "Tina appears #{string.split.count("grey").to_i} times."
puts "Sally appears #{string.split.count("blue").to_i} times."
Expected result: program looks through the text for unique words and returns them.
Actual: I had to hard code each unique word on its own PUTS line and do string.split.count(for that unique word)
Note:
I tried the following but this gives me EVERY word. I need to refine it to give me just the ones I ask for. This is where I am struggling.
def cw(string)
w = string.split(' ')
freq = Hash.new(0)
w.each { |w| freq[w.downcase] += 1 }
return freq
end
puts cw(string)
def count_em(str, who)
str.gsub(/\b(?:#{who.join('|')})\b/i).
each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
end
str = "Monday Tina will meet Sally and Harris. Then Tina will visit her " +
"mom Marina. Marina and Tina will meet David for dinner. Sally will " +
"then take Tina out for a late night party."
who = %w| Sally Marina Tina |
count_em(str, who)
#> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
The first steps are as follows.
r = /\b(?:#{who.join('|')})\b/i
#=> /\b(?:Sally|Marina|Tina)\b/i
enum = str.gsub(r)
#=> #<Enumerator: "Monday Tina will meet Sally and Harris. Then
# ...
# for a late night party.":gsub(/\b(?:Sally|Marina|Tina)\b/i)>
We can convert this to an array to see the values that will be passed to each_with_object.
enum.to_a
#=> ["Tina", "Sally", "Tina", "Marina", "Marina", "Tina", "Sally", "Tina"]
We then simply count the number of instances of the unique values generated by enum.
enum.each_with_object(Hash.new(0)) { |person,h| h[person] += 1 }
#=> {"Tina"=>4, "Sally"=>2, "Marina"=>2}
See String#gsub, in particular the case when there is one argument and no block. This is admittedly an unusual use of gsub, as it is making no substitutions, but here I prefer it to String#scan because gsub returns an enumerator whereas scan produces a temporary array.
See also Hash::new, the case where new takes an argument and no block. The argument is called the default value. If h is the hash so-defined, the default value is returned by h[k] if h does not have a key k. The hash is not altered.
Here the default value is zero. When the expression h[person] += 1 it is parsed it is converted to:
h[person] = h[person] + 1
If person equals "Tina", and it is the first time "Tina" is generated by the enumerator and passed to the block, h will not have a key "Tina", so the expression becomes:
h["Tina"] = 0 + 1
as 0 is the default value. The next time "Tina" is passed to the block the hash has a key "Tina" (with value 1), so the following calculation is performed.
h["Tina"] = h["Tina"] + 1 #=> 1 + 1 #=> 2
To get only the required people name:
people = ['Marina', 'Tina', 'Sally', 'Dory']
tmp = string.scan(/\w+/).keep_if{ |w| people.include? w }
counts people.map{ |name| [name, tmp.count{|n| n == name }] }.to_h
counts #=> {"Marina"=>2, "Tina"=>4, "Sally"=>2, "Dory"=>0}
This maps the peopole array against tmp to a nested array containing [name, count], then converted to a hash.
The good is that it returns 0 if people doesn't appear, see 'Dory'.
To get the total count, two ways:
tmp.size #=> 8
counts.values.sum #=> 8

Find the specific number from Array.collect [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I want to find out what's the first, the second, and the third result, so that I can do if firstnumber==secondnumber && secondnumber==thirdnumber. How could I find those numbers on the results?
numbers = 3.times.map { Random.new.rand(0..10000) }
prizes = numbers.map do |x|
case x
when 1..3000
[ '7', 10000 ]
when 3001..6000
[ "Cherries", 500 ]
when 6001..10000
[ "Diamond", 400 ]
end
end
puts "Your results are: #{prizes.collect { |p| p[0] }.join(", ")}!
I tried to use p[0][0], but it gives the first letter instead.
Say if:
results = prizes.collect { |p| p[0] } #=> ["Diamond", "Cherries", "7"]
Then do the following to get at each result:
results[0] #=> "Diamond"
results[1] #=> "Cherries"
results[2] #=> "7"
You could also use results.first to get the first element. If you happen to be working in Rails you can even do the following:
results.second #=> "Cherries"
results.third #=> "7"
Here's a fine way to do this:
numbers = 3.times.map { Random.new.rand(0..10000) }
prizes = numbers.map do |x|
case x
when 1..3000
{ name: '7', val: 10000 }
when 3001..6000
{name: "Cherries", val: 10000 }
when 6001..10000
{name: "Diamond", val: 400 }
end
end
# You could replace 'map' with 'collect' here and have the same results
prizes_string = prizes.map { |p| "#{p[:name]}: #{p[:val]}" }.join(" and ")
puts "Your results are: #{prizes_string}!"

Array of strings Group by first common letters [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
Is there anyway of grouping first common letters in an array of strings?
For example:
array = [ 'hello', 'hello you', 'people', 'finally', 'finland' ]
so when i do
array.group_by{ |string| some_logic_with_string }
The result should be,
{
'hello' => ['hello', 'hello you'],
'people' => ['people'],
'fin' => ['finally', 'finland']
}
NOTE: Some test cases are ambiguous and expectations conflict with other tests, you need to fix them.
I guess plain group_by may not work, a further processing is needed.
I have come up with below code that seems to work for all the given test cases in consistent manner.
I have left notes in the code to explain the logic. Only way to fully understand it will be to inspect value of h and see the flow for a simple test case.
def group_by_common_chars(array)
# We will iteratively group by as many time as there are characters
# in a largest possible key, which is max length of all strings
max_len = array.max_by {|i| i.size}.size
# First group by first character.
h = array.group_by{|i| i[0]}
# Now iterate remaining (max_len - 1) times
(1...max_len).each do |c|
# Let's perform a group by next set of starting characters.
t = h.map do |k,v|
h1 = v.group_by {|i| i[0..c]}
end.reduce(&:merge)
# We need to merge the previously generated hash
# with the hash generated in this iteration. Here things get tricky.
# If previously, we had
# {"a" => ["a"], "ab" => ["ab", "abc"]},
# and now, we have
# {"a"=>["a"], "ab"=>["ab"], "abc"=>["abc"]},
# We need to merge the two hashes such that we have
# {"a"=>["a"], "ab"=>["ab", "abc"], "abc"=>["abc"]}.
# Note that `Hash#merge`'s block is called only for common keys, so, "abc"
# will get merged, we can't do much about it now. We will process
# it later in the loop
h = h.merge(t) do |k, o, n|
if (o.size != n.size)
diff = [o,n].max - [o,n].min
if diff.size == 1 && t.value?(diff)
[o,n].max
else
[o,n].min
end
else
o
end
end
end
# Sort by key length, smallest in the beginning.
h = h.sort {|i,j| i.first.size <=> j.first.size }.to_h
# Get rid of those key-value pairs, where value is single element array
# and that single element is already part of another key-value pair, and
# that value array has more than one element. This step will allow us
# to get rid of key-value like "abc"=>["abc"] in the example discussed
# above.
h = h.tap do |h|
keys = h.keys
keys.each do |k|
v = h[k]
if (v.size == 1 &&
h.key?(v.first) &&
h.values.flatten.count(v.first) > 1) then
h.delete(k)
end
end
end
# Get rid of those keys whose value array consist of only elements that
# already part of some other key. Since, hash is ordered by key's string
# size, this process allows us to get rid of those keys which are smaller
# in length but consists of only elements that are present somewhere else
# with a key of larger length. For example, it lets us to get rid of
# "a"=>["aba", "abb", "aaa", "aab"] from a hash like
# {"a"=>["aba", "abb", "aaa", "aab"], "ab"=>["aba", "abb"], "aa"=>["aaa", "aab"]}
h.tap do |h|
keys = h.keys
keys.each do |k|
values = h[k]
other_values = h.values_at(*(h.keys-[k])).flatten
already_present = values.all? do |v|
other_values.include?(v)
end
h.delete(k) if already_present
end
end
end
Sample Run:
p group_by_common_chars ['hello', 'hello you', 'people', 'finally', 'finland']
#=> {"fin"=>["finally", "finland"], "hello"=>["hello", "hello you"], "people"=>["people"]}
p group_by_common_chars ['a', 'ab', 'abc']
#=> {"a"=>["a"], "ab"=>["ab", "abc"]}
p group_by_common_chars ['aba', 'abb', 'aaa', 'aab']
#=> {"ab"=>["aba", "abb"], "aa"=>["aaa", "aab"]}
p group_by_common_chars ["Why", "haven't", "you", "answered", "the", "above", "questions?", "Please", "do", "so."]
#=> {"a"=>["answered", "above"], "do"=>["do"], "Why"=>["Why"], "you"=>["you"], "so."=>["so."], "the"=>["the"], "Please"=>["Please"], "haven't"=>["haven't"], "questions?"=>["questions?"]}
Not sure, if you can sort by all common letters. But if you want to do sort only by first letter then here it is:
array = [ 'hello', 'hello you', 'people', 'finally', 'finland' ]
result = {}
array.each { |st| result[st[0]] = result.fetch(st[0], []) + [st] }
pp result
{"h"=>["hello", "hello you"], "p"=>["people"], "f"=>["finally", "finland"]}
Now result contains your desired hash.
Hmm, you're trying to do something that's pretty custom. I can think of two classical approaches that sort of do what you want: 1) Stemming and 2) Levenshtein Distance.
With stemming you're finding the root word to a longer word. Here's a gem for it.
Levenshtein is a famous algorithm which calculates the difference between two strings. There is a gem for it that runs pretty fast due to a native C extension.

How to make dynamic multi-dimensional array in ruby?

I have a beginner ruby question about multi dimensional arrays.
I want to sort entries by year and month. So I want to create a multi-dimensional array that would contain years -> months -> entries of month
So the array would be like:
2009 ->
08
-> Entry 1
-> Entry 2
09
-> Entry 3
2007 ->
10
-> Entry 5
Now I have:
#years = []
#entries.each do |entry|
timeobj = Time.parse(entry.created_at.to_s)
year = timeobj.strftime("%Y").to_i
month = timeobj.strftime("%m").to_i
tmparr = []
tmparr << {month=>entry}
#years.push(year)
#years << tmparr
end
but when I try to iterate through the years array, I get: "undefined method `each' for 2009:Fixnum"
Tried also:
#years = []
#entries.each do |entry|
timeobj = Time.parse(entry.created_at.to_s)
year = timeobj.strftime("%Y").to_i
month = timeobj.strftime("%m").to_i
#years[year][month] << entry
end
You are getting the error because a FixNum (that is, a number) is pushed on the array, in the line that reads #years.push(year).
Your approach of using Arrays to start with is a bit flawed; an array is perfect to hold an ordered list of items. In your case, you have a mapping from keys to values, which is perfect for a Hash.
In the first level, the keys are years, the values are hashes. The second level's hashes contain keys of months, and values of arrays of entries.
In this case, a typical output of your code would look something like (based on your example):
{ 2009 => { 8 => [Entry1, Entry2], 9 => [Entry3] }, 2007 => { 10 => [Entry5] }}
Notice that, however, the order of years and months is not guaranteed to be in any particular order. The solution is normally to order the keys whenever you want to access them. Now, a code that would generate such an output (based on your layout of code, although can be made much rubier):
#years = {}
#entries.each do |entry|
timeobj = Time.parse(entry.created_at.to_s)
year = timeobj.strftime("%Y").to_i
month = timeobj.strftime("%m").to_i
#years[year] ||= {} # Create a sub-hash unless it already exists
#years[year][month] ||= []
#years[year][month] << entry
end
You can get the nested array structure in one line by using a combination of group_bys and map:
#entries.group_by {|entry| entry.created_at.year }.map { |year, entries| [year, entries.group_by {|entry| entry.created_at.month }] }
I'm using hash tables instead of arrays, because I think it probably makes more sense here. However, it's fairly trivial to change back to using arrays if that's what you prefer.
entries = [
[2009, 8, 1],
[2009, 8, 2],
[2009, 9, 3],
[2007, 10, 5]
]
years = Hash.new
entries.each { |e|
year = e[0]
month = e[1]
entry = e[2]
# Add to years array
years[year] ||= Hash.new
years[year][month] ||= Array.new
years[year][month] << entry
}
puts years.inspect
The output is: {2007=>{10=>[5]}, 2009=>{8=>[1, 2], 9=>[3]}}
# create a hash of hashes of array
#years = Hash.new do |h,k|
h[k] = Hash.new do |sh, sk|
sh[sk] = []
end
end
#entries.each do |entry|
timeobj = Time.parse(entry.created_at.to_s)
year = timeobj.year
month = timeobj.month
#years[year][month] << entry
end

Resources