I have an array in this format:
[
{ day: 1
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 2
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 3
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 4
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 5
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 6
intervals: [
{
from: 900,
to: 1200
},
{
from: 1300,
to: 2200
}
]
},
{ day: 7
intervals: [
{
from: 900,
to: 1200
},
{
from: 1300,
to: 2200
}
]
}
]
I wan't to group them like this:
[
{ day: 1-5
intervals: [
{
from: 900,
to: 1200
}
]
},
{ day: 6-7
intervals: [
{
from: 900,
to: 1200
},
{
from: 1300,
to: 2200
}
]
}
]
Criteria:
Only group if intervals are the same.
Only group if matches are in chronological order, i.e 1-5 or 1-3, not 1-2-5.
How can this be achieved?
Here's a variation of #joelparkerhenderson's solution that tries to be a bit closer to your requirements re formatting of the output etc.
output = []
grouped = input.group_by do |x|
x[:intervals]
end
grouped.each_pair do |k, v|
days = v.map {|day| day[:day]}
if days.each_cons(2).all? { |d1, d2| d1.next == d2 }
output << {
:days => days.values_at(0,-1).join('-'),
:intervals => k
}
end
end
puts output
This produces the required output:
by_interval = data.inject({}) do | a, e |
i = e[:intervals]
a[i] ||= []
a[i] << e[:day].to_i
a
end
result = by_interval.map do | interval, days |
slices = days.sort.inject([]) do | a, e |
a << [] if a == [] || a.last.last != e - 1
a.last << e
a
end
slices.map do | slice |
{:day => "#{slice.first}-#{slice.last}", :intervals => interval }
end
end
result.flatten!
I'm sure there are better approaches :-)
You need to look into Map Method for an array. You need to remap the array and iterate over it to extract the data you want using your "grouping" logic above.
Extending #Michael Kohl's answer
output = []
grouped = schedules.as_json.group_by do |x|
x['intervals']
end
grouped.each_pair do |k, v|
days = v.map {|day| day['day']}
grouped_days = days.inject([[]]) do |grouped, num|
if grouped.last.count == 0 or grouped.last.last == num - 1
grouped.last << num
else
grouped << [ num ]
end
grouped
end
grouped_days.each do |d|
output << {
heading: d.values_at(0, -1).uniq.join('-'),
interval: k
}
end
end
output
You should probably split that up into separate methods, but you get the idea.
Related
Note: The closer the sum of the prices are to max_price, the better
Initial data:
**max_price** = 11
[
{
id: 1,
price: 5
},
{
id: 2,
price: 6
},
{
id: 3,
price: 6
},
{
id: 4,
price: 1
},
{
id: 5,
price: 3
},
]
For instance, for the first time, we should return
[
{
id: 1,
price: 5
},
{
id: 2,
price: 6
}
]
because the sum of prices of these 2 elements is equal to or less than max_price.
But for the next time, we should return other random elements where their price sum is equal to or less than max_price
[
{
id: 3,
price: 6
},
{
id: 4,
price: 1
},
{
id: 5,
price: 3
}
]
Every time we should return an array with random elements where their sum is equal to or less than max_price.
How can we do that in ruby?
As #Spickerman stated in his comment, this looks like the knapsack problem, and isn't language sensitive at all.
for a Ruby version, I played around a bit, to see how to get the pseudocode working, and I've come up with this as a possible solution for you:
Initialisation of your records:
#prices =
[
{ id: 1, price: 3 },
{ id: 2, price: 6 },
{ id: 3, price: 6 },
{ id: 4, price: 1 },
{ id: 5, price: 5 }
]
# Define value[n, W]
#max_price = 11
#max_items = #prices.size
Defining the Ruby subprocedures, based on that Wiki page, one procedure to create the possibilities, one procedure to read the possibilities and return an index:
# Define function m so that it represents the maximum value we can get under the condition: use first i items, total weight limit is j
def put_recurse(i, j)
if i.negative? || j.negative?
#value[[i, j]] = 0
return
end
put_recurse(i - 1, j) if #value[[i - 1, j]] == -1 # m[i-1, j] has not been calculated, we have to call function m
return unless #prices.count > i
if #prices[i][:price] > j # item cannot fit in the bag
#value[[i, j]] = #value[[i - 1, j]]
else
put_recurse(i - 1, j - #prices[i][:price]) if #value[[i - 1, j - #prices[i][:price]]] == -1 # m[i-1,j-w[i]] has not been calculated, we have to call function m
#value[[i, j]] = [#value[[i - 1, j]], #value[[i - 1, j - #prices[i][:price]]] + #prices[i][:price]].max
end
end
def get_recurse(i, j)
return if i.negative?
if #value[[i, j]] > #value[[i - 1, j]]
#ret << i
get_recurse(i - 1, j - #prices[i][:price])
else
get_recurse(i - 1, j)
end
end
procedure to run the previously defined procedures in a nice orderly fashion:
def knapsack(items, weights)
# Initialize all value[i, j] = -1
#value = {}
#value.default = -1
#ret = []
# recurse through results
put_recurse(items, weights)
get_recurse(items, weights)
#prices.values_at(*#ret).sort_by { |x| x[:id] }
end
Running the code to get your results:
knapsack(#max_items, #max_price)
So I am trying to sum a total from an array, but once that total is above 8000 it should reduce the amount added by 50%. I seem to be getting the sum of the total from before and after the if condition. Can anyone explain why and how to fix it?
arr = [{ add_on: "AWD Drivetrain", price: 2500 }, { add_on: "Sport Package", price: 3500 }, { add_on: "Winter Tire Package", price: 2000 }, { add_on: "GPS Navigation", price: 2000 },]
def calculate_price_recursive(arr)
prices = arr.sort_by { |x| -x[:price] }.map { |x| x[:price] }
return recur_sum(prices, 0)
end
def recur_sum(prices, total)
puts "#{prices}"
return total if prices.count == 0
if total < 8000
prices[0] + recur_sum(prices[1..-1], total + prices[0])
else
(prices[0] / 2) + recur_sum(prices[1..-1], total + prices[0])
end
end
Ruby does not have TCO enabled by default. To enable it explicitly one should do this:
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
That said, the result might look like
RubyVM::InstructionSequence.compile_option = {
tailcall_optimization: true,
trace_instruction: false
}
arr = [
{ add_on: "AWD Drivetrain", price: 2500 },
{ add_on: "Sport Package", price: 3500 },
{ add_on: "Winter Tire Package", price: 2000 },
{ add_on: "GPS Navigation", price: 2000 }
]
def calculate_price_recursive(arr)
prices = arr.map { |x| x[:price] }.sort.reverse
recur_sum(prices)
end
def recur_sum(prices, total = 0)
return total if prices.count == 0
recur_sum(
prices[1..-1],
total + prices[0] / (total < 8000 ? 1 : 2)
)
end
I was adding the stuff twice.
def recur_sum(prices, total)
puts "#{prices}"
return total if prices.count == 0
if total < 8000
recur_sum(prices[1..-1], total + prices[0])
else
recur_sum(prices[1..-1], total + prices[0] / 2)
end
end
I found a way to do the whole thing recusrively in one function:
def recur_sum_holistic(arr, base, check, constant)
if check == 1
constant = arr.map { |x| x[:add_on] }.join(', ')
end
return "The cost for this car is $#{((base + (((base - 24999) * 0.02) + 1200)) * 1.13).round(2)} with the following configuration: #{constant}" if arr.count == 0
recur_sum_holistic(arr[1..-1], base + (arr[0][:price] / (base < (24999 + 8000) ? 1 : 2)), 2, constant)
end
\\for Element in Arr:
\\\\if ElementSum < 8300
\\\\\\ElementSum = ElementSum + Element
\\\\else
\\\\\\ElementSum = ElementSum + (Element / 2)
Given a hash like so:
h = {
"actual_amount" => 20,
"otherkey" => "value",
"otherkey2" => [{"actual_amount" => 30, "random_amount" => 45}]
}
where there are any number of layers of nesting, is there a simple way to pluck all the key-value pairs (or just the values) of the keys that are actual_amount?
I've assumed the values of keys are literals or arrays of hashes.
This question clearly calls for a recursive solution.
def amounts(h)
h.each_with_object([]) do |(k,v),a|
case v
when Array
v.each { |g| a.concat amounts(g) }
else
a << v if k == "actual_amount"
end
end
end
Suppose
h = {
"actual_amount"=>20,
1=>2,
2=>[
{ "actual_amount"=>30,
3=>[
{ "actual_amount" => 40 },
{ 4=>5 }
]
},
{ 5=>6 }
]
}
then
amounts(h)
#=> [20, 30, 40]
Using the hash, provided by Cary, as an input:
▶ flatten = ->(inp) do
▷ [*(inp.respond_to?(:map) ? inp.map(&:flatten) : inp)]
▷ end
▶ res = flatten(h).first
▶ res.select.with_index do |_, i|
▷ i > 0 && res[i - 1] == 'actual_amount'
▷ end
#⇒ [20, 30, 40]
I have an array of pairs like this:
arr = [
{lat: 44.456, lng: 33.222},
{lat: 42.456, lng: 31.222},
{lat: 44.456, lng: 33.222},
{lat: 44.456, lng: 33.222},
{lat: 42.456, lng: 31.222}
]
There are some geographical coordinates of some places. I want to get an array with these coordinates grouped and sorted by frequency. The result should look like this:
[
{h: {lat: 44.456, lng: 33.222}, fr: 3},
{h: {lat: 42.456, lng: 31.222}, fr: 2},
]
How can I do this?
The standard ways of approaching this problem are to use Enumerable#group_by or a counting hash. As others have posted answers using the former, I'll go with the latter.
arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }.map { |k,v| { h: k, fr: v } }
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]
First, count instances of the hashes:
counts = arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }
#=> {{:lat=>44.456, :lng=>33.222}=>3,
# {:lat=>42.456, :lng=>31.222}=>2}
Then construct the array of hashes:
counts.map { |k,v| { h: k, fr: v } }
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]
g = Hash.new(0) creates an empty hash with a default value of zero. That means that if g does not have a key k, g[k] returns zero. (The hash is not altered.) g[k] += 1 is first expanded to g[k] = g[k] + 1. If g does not have a key k, g[k] on the right side returns zero, so the expression becomes:
g[k] = 1.
Alternatively, you could write:
counts = arr.each_with_object({}) { |f,g| g[f] = (g[f] ||= 0) + 1 }
If you want the elements (hashes) of the array returned to be in decreasing order of the value of :fr (here it's coincidental), tack on Enumerable#sort_by:
arr.each_with_object(Hash.new(0)) { |f,g| g[f] += 1 }.
map { |k,v| { h: k, fr: v } }.
sort_by { |h| -h[:fr] }
arr.group_by(&:itself).map{|k, v| {h: k, fr: v.length}}.sort_by{|h| h[:fr]}.reverse
# =>
# [
# {:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3},
# {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}
# ]
arr.group_by{|i| i.hash}.map{|k, v| {h: v[0], fr: v.size}
#=> [{:h=>{:lat=>44.456, :lng=>33.222}, :fr=>3}, {:h=>{:lat=>42.456, :lng=>31.222}, :fr=>2}]
I have the following code:
wrg = { "1.png", "2.png", "3.png", "4.png" };
table = { }
for i = 1, 4 do
table[ i ] = wrg[ math.random( 1, #wrg ) ]
end
for i = 1, 4 do
print( table[ i ] )
end
output:
4.png
2.png
4.png
4.png
I don't need repeat "4.png" how fix?
You need a random permutation. For instance, this code:
wrg = { "1.png", "2.png", "3.png", "4.png" };
t = {}
n=#wrg
for i=1,n do
t[i]=wrg[i]
end
math.randomseed(os.time())
for i=1,n-1 do
local j=math.random(i,n)
t[i],t[j]=t[j],t[i]
end
for i=1,n do
print(t[i])
end