I want my code to show a message every 20% of items processed.
I am not very happy with the way I am doing. I am sure have a smarter way
The way I am doing:
count = 0
size = foo.get_items_size (items)
target = 20
loop all items
items_processed = count*100 / size
if items_processed == target
puts "#{items_processed}% items"
target += 20
end
count +=1
end
You could incorporate the counter into your loop via with_index and make your code a little more robust by comparing the current percentage to your target via >= rather than ==:
items = ('A'..'M').to_a
target = 20
items.each.with_index(1) do |item, index|
puts item # process item
if index.quo(items.size) >= target.quo(100)
puts "#{target}% processed"
target += 20
end
end
This will print 20%, 40%, 60% etc. whenever exceeding that percentage:
A
B
C
20% processed
D
E
F
40% processed
G
H
60% processed
I
J
K
80% processed
L
M
100% processed
Note that the actual percentages are 23%, 46%, 62%, and 85%.
Check if the below code is helpful:
items = 1..100
target_percent = 20
step = ((target_percent/100.0) * items.size).round
items.each.with_index(1) do |item, index|
# Some other working logic
puts "#{index}% of work done!" if index.modulo(step).zero?
end
Since I don't know what's in the item list, I have just considered it as an array.
This will also work if you wish to change the target_percent.
I would use Enumerable#each.with_index in combination with of only printing the output when the current index is a multiple of 20:
process_steps = (1..5).map { items.size / _1 }
items.each.with_index(1) do |item, index|
# do stuff with item
puts "#{index}% items processes" if process_steps.include?(index)
end
each_slice might be what you are looking for:
items = (1..30)
slice_size = items.size/(100/20)
processed = 0
items.each_slice(slice_size) do |slice|
processed += slice.size
slice.each do |item|
puts item #process item
end
puts "#{processed} from #{items.size} items processed"
end
I would do it with slices and indexes:
items=(1..23).to_a
step=20.0/100
steps=(items.length*step).round
items.each_slice(steps).with_index{|slice, ind|
puts "Accurate: #{((steps*ind)/items.length.to_f*100).round}% processed, now processing: #{slice[0..2]}..#{slice[-3..-1]}"
puts "Dumb down: #{(ind/(1.0/step)*100).round}% processed"
puts
}
#puts "Done!"
Every 20% report implies process 5 slices with assumably the last slice being the remainder if the length of items is not evenly divisible by 5.
You can report progress either accurately (22%, 43%, 65%, 87% in for 5 buckets totaling 23 items) or dumb that down to 20%,40%,60%,80%. I have shown both here.
Example prints:
Accurate: 0% processed, now processing: [1, 2, 3]..[3, 4, 5]
Dumb down: 0% processed
Accurate: 22% processed, now processing: [6, 7, 8]..[8, 9, 10]
Dumb down: 20% processed
Accurate: 43% processed, now processing: [11, 12, 13]..[13, 14, 15]
Dumb down: 40% processed
Accurate: 65% processed, now processing: [16, 17, 18]..[18, 19, 20]
Dumb down: 60% processed
Accurate: 87% processed, now processing: [21, 22, 23]..[21, 22, 23]
Dumb down: 80% processed
You can also do standing progress percentages:
items.each_slice(steps).with_index{|slice, ind|
printf("\r %s", "#{ind/(1.0/step)*100}%")
sleep(0.6)
}
puts "\rDone! "
Or progress bars:
items.each_slice(steps).with_index{|slice, ind|
printf("\rProgress: [%-20s]", "|"*(ind*5))
sleep(0.6)
}
puts "\rDone! "
Related
I'm practicing my coding chops after a long break and ran into this kata on CodeWars
With an input of numbers in an array, return the sums of its parts. So for example:
def parts_sums(ls)
sums = []
until ls.size == 0
sums << ls.inject(:+)
ls.shift
end
sums << 0
end
######### INPUT #######
parts_sums([0, 1, 3, 6, 10])
######### EXPECTED OUTPUT ######
[20, 20, 19, 16, 10, 0]
0 + 1 + 3 + 6 + 10 = 20
1 + 6 + 3 + 10 = 20
3 + 6 + 10 = 19
6 + 10 = 16
10 = 10
0 = 0
My solution solves the kata, however once I reach arrays of around 30,000+ my solution takes too long to solve.
So my question is to the community, how would I even attempt to make this go faster. I know that recursion is usually slow, and that for loops and its variants are usually sufficient to get the job done. What happens when that fails? What are some things to try to make my code above faster?
I'm looking for some advice and some examples if anyone has any. Appreciate the input. Thanks.
def parts_sums(ls)
ls.each_with_object([ls.sum]) { |n,arr| arr << arr.last - n }
end
parts_sums([0, 1, 3, 6, 10])
#=> [20, 20, 19, 16, 10, 0]
The issue with the code is that you are performing an inject on every iteration of your loop, which is unnecessarily slow.
You only need to sum the elements of the array once, outside of any loop. Once you have that sum, you can iterate through the elements of the array and perform a constant time subtraction from the current sum and push it into the sums array.
def part_sums(ls)
sum = ls.inject(:+)
sums = [sum]
ls.each do |val|
sum -= val
sums << sum
end
sums
end
There is also no need to shift, if you iterate through the array with the each iterator or keep a counter and use a while loop.
This version of the function runs much faster:
def parts_sums_2(ls)
sums = []
last_sum = 0
(ls.length - 1).downto(0).each do |i|
last_sum += ls[i]
sums.prepend last_sum
end
sums + [0]
end
The key here is going backwards through the array - starting with the smallest sum (only the last element). Each subsequent step moves one index towards the beginning, and adds that value to the previous sum.
Since the problem statement requires you to shift each step, your result must have the largest sums at the beginning, even though these are the last ones to be computed. This is why my code uses prepend rather than push.
This is O(N) time complexity instead of O(N^2), which is an order of magnitude difference.
With 100_000 inputs, your original function took 7.040443 seconds, while mine here took 0.000008 seconds
Also in general you should try to avoid mutating the input to your methods (as you were doing with shift).
A problem asks to print all numbers from an array that are greater than 100. When I run this:
array = [3, 123, 433, -77, 56, 200, 99, 101, 6]
index = 0
9.times do
if array[index] > 100 == 0
p array[index]
end
index = index + 1
end
the number 9 is printed. I put in the proper conditional if the number was greater than 100 to get printer. Can anyone help?
Just use
if array[index] > 100
# print
end
9 is the evaluation of your program, it's not a 'print', it's the result of 9.times
Your program doesn't print any of the array values because of that condition
if array[index] > 100 == 0
it should be just
if array[index] > 100
don't know why you add that == 0
array[index] > 100 is evaluted before so all the time you always obtain true == 0 or false == 0, and these are never true. So no prints in your code
about that 9 that come up it's what Aleksei says
The problem's actually really easy to solve if you know the right tools to use:
array = [3,123,433,-77,56,200,99,101,6]
array.select { |n| n > 100 }.each do |n|
puts n
end
Where select can help narrow down lists of numbers. Ruby's Array class has an unusually large number of methods like this that can quickly and easily do a variety of things related to filtering and mapping.
The 9.times part in your code was decoupled from the actual length of the array, something that leads to a whole lot of bugs if you add/remove entries and these two fall out of sync.
Use array iterators whenever possible, like:
array = [3,123,433,-77,56,200,99,101,6]
array.each do |n|
if (n > 100)
puts n
end
end
You have written 9.times which is your array size, which indicates you want to run it for all array elements. So use each block on array.
array.each { |x| puts x if x > 100 }
Try to understand why there is no role of index of any array element in above.
Select returns its own array. So we can just print the return value.
puts array.select{|x| x > 99}
I'm currently working on small ruby projects from project Euler site. I was given a task to sum even fibonacci numbers that are less than 4 millions. Unfortunately there is a small bug in my code, because when I change the limit e.i. to 100, it prints 188 instead of 44. Surprisingly this program gives the right answer but i don't really know in what way my code is wrong.
a=[]; a[0]=1; a[1]=1;
i = 1
while a[-1] < 608
a[i+1]=(a[i] + a[i-1])
i +=1
end
x = 0
a.each do |num|
if num % 2 == 0
x += num
end
end
print "The sum of even Fibonacci number is: #{x}"
The problem comes from the second iteration. You are stopping the generation of Fibonacci numbers when one of the numbers cross the limit (ie when the last number is > 100).
It turns out that after the generation step, the array is [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144], this explains your wrong result 188 = 144+44.
So, your code works only when the last element generated is odd, which is the case in Euler's problem test. In order to correct that, change your second iteration from a.each do ... end to a[0...-1].each do ... end In order to iterate through the array except the last element.
BTW I would recommend you not to use an array here.
You are just wasting memory and ruby is losing time on extending it (this can be solved via Array.new(ARRAY_SIZE)).
Since you don't actually need a fibbonaci sequence you can just have something like this:
LIMIT = 4_000_000
a = 1
b = 1
next_number = a + b
sum = 0
while next_number < LIMIT
sum += next_number if next_number.even?
a = b
b = next_number
next_number = a + b # or next_number += a
end
UPD. Oh my god I don't know why this question appeared in my feed. Sorry for necroposting:)
I've got an array of hashes (sorted), something like this:
testArray = [{price: 540, volume: 12},
{price: 590, volume: 18},
{price: 630, volume: 50}]
Now I want to calculate the mean value up to certain total volume. Let's say someone wants to buy 40 pieces and he wants it the cheapest way. It would mean an average price of (540*12+590*18+630*50)/40 money units.
My first attempt is following:
testArray.each do |priceHash|
#priceArray << priceHash.fetch(:price)
#volumeArray << priceHash.fetch(:volume)
end
def calculateMiddlePrice(priceArray, volumeArray, totalAmount)
result = 0
# Here some crazy wild magic happens
(0...volumeArray.count).inject(0) do |r, i|
if (volumeArray[0..i].inject(:+)) < totalAmount
r += volumeArray[i]*priceArray[i]
elsif volumeArray[0..i-1].inject(:+) < totalAmount && volumeArray[0..i].inject(:+) >= totalAmount
theRest = volumeArray[i] - (volumeArray[0..i].inject(:+) - totalAmount)
r += theRest * priceArray[i]
elsif volumeArray[0] > totalAmount
r = totalAmount * priceArray[0]
end
result = r
end
result
end
Right now I'm not even sure why it works, but it does. However this absolutely ridiculous code in my eyes.
My second thought was to cut my testArray when the total amount is achieved. The code looks better
testAmount = 31
def returnIndexForSlice(array, amount)
sum = 0
array.each_index do |index|
p sum += array[index][:volume]
if sum >= amount
return index+1
end
end
end
testArray.slice(0,returnIndexForSlice(testArray, testAmount))
Still, this just doesn't feel that right, "rubyish" if you could say so. I checked almost every method for array class, played around with bsearch, however I can't figure out a really elegant way of solving my problem.
What's crossing my mind is something like that:
amountToCheck = 31
array.some_method.with_index {|sum, index| return index if sum >= amountToCheck}
But is there such method or any other way?
Given your prices array of hashes:
prices = [ {price: 540, volume: 12},
{price: 590, volume: 18},
{price: 630, volume: 50}]
You can calculate your result in 2 steps.
def calc_price(prices, amount)
order = prices.flat_map{|item| [item[:price]] * item[:volume] } #step 1
order.first(amount).reduce(:+)/amount #step 2
end
Step 1: Create an array with each individual item in it (if the prices aren't sorted, you have to add a sort_by clause). In other words, expand the prices into a numeric array containing twelve 540's, 18 590's, etc. This uses Ruby's array repetition method: [n] * 3 = [n, n, n].
Step 2: Average the first n elements
Result:
calc_price(prices, 40)
=> 585
Given an array like [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], I want to get a random value that takes into consideration the position.
I want the likelihood of 1 popping up to be way bigger than 10.
Is something like this possible?
For the sake of simplicity let's assume an array arr = [x, y, z] from which we will be sampling values. We'd like to see following relative frequencies of x, y and z:
frequencies = [5, 2, 1]
Preprocess these frequencies to calculate margins for our subsequent dice roll:
thresholds = frequencies.clone
1.upto(frequencies.count - 1).each { |i| thresholds[i] += thresholds[i - 1] }
Let's sum them up.
max = frequencies.reduce :+
Now choose a random number
roll = 1 + rand max
index = thresholds.find_index { |x| roll <= x }
Return arr[index] as a result. To sum up:
def sample arr, frequencies
# assert arr.count == frequencies.count
thresholds = frequencies.clone
1.upto(frequencies.count - 1).each { |i| thresholds[i] += thresholds[i - 1] }
max = frequencies.reduce :+
roll = 1 + rand(max)
index = thresholds.find_index { |x| roll <= x }
arr[index]
end
Let's see how it works.
data = 80_000.times.map { sample [:x, :y, :z], [5, 2, 1] }
A histogram for data shows that sample works as we've intended.
def coin_toss( arr )
arr.detect{ rand(2) == 0 } || arr.last
end
a = (1..10).to_a
10.times{ print coin_toss( a ), ' ' } #=> 1 1 1 9 1 5 4 1 1 3
This takes the first element of the array, flips a coin, returns the element and stops if the coinflip is 'tails'; the same with the next element otherwise. If it is 'heads' all the way, return the last element.
A simple way to implement this with an logarithmic probabilistic of being selected is to simulate coin flips. Generate a random integer 0 and 1, the index to that array to choose is the number of consecutive 1s you get. With this method, the chance of selecting 2 is 1/2 as likely as 1, 3 is 1/4th as likely, etc. You can vary the probability slightly say by generating random numbers between 0 and 5 and count the number of consecutive rounds above 1, which makes each number in the array 4/5th as likely to appear as the one before.
A better and more general way to solve this problem is to use the alias method. See the answer to this question for more information:
Data structure for loaded dice?