how to compute year of service - ruby-on-rails

i have this problem
1yr of service 15 days
3yrs of service 17 days
5yrs of service 20 days
10yrs or more of service 25 days
so come up of this code i get the year of employee hired
here's the code
def year_of_service(date_of_hired)
years = [1, 3, 5, 10]
now = Time.now.utc.to_date
get_year = now.year - date_of_hired.year - ((now.month > date_of_hired.month ||
(now.month == date_of_hired.month && now.day >= date_of_hired.day)) ? 0 : 1)
end
my solution here is i will use if statements but for me it will be a long code is there another solution that make it short or other possble solution
the difficulties is here how can i know if the 'get_year' is between of my array???
example:
the 'get_year' is 4 how can know that is 3 years of service not 5 but 4 is not in the array

You may employ take_while or select.
y = [1, 3, 5, 10]
y.take_while { |i| i <= 4 }.max #=> 3
y.select { |i| i <= 4 }.last #=> 3
But these involve collecting more elements than needed. Another approach is to find the index of the last element matching your criteria, then use regular array-notation to grab that element:
y[y.rindex { |i| i <= 4 }] #=> 3
You could also use find, but that would involve reversing the array first. Currently there is no rfind in Ruby.

Related

How to optimize the program with two for loops

I have a following programm
def calc_res(a)
n = a.length
result = 0
for i in 0 .. (n - 1)
for j in i .. (n - 1)
if (a[i] != a[j] && j - i > result) then
result = j - i
end
end
end
return result
end
which return following output
irb(main):013:0> calc_res([4, 6, 2, 2, 6, 6, 4])
=> 5
but it is taking time if array size is too large e.g. [0,1,2,3,.....70000]
can any one suggest me how can I optimize it.
Thanks
If I have understood the problem you are trying to solve (from code)
def calc_res(a)
last_index = a.length - 1
index = 0
while a[index] == a.last do
index = index + 1
break if index == last_index
end
last_index - index
end
It checks items from start if they are equal to items from end, end it moves the index toward the last element. As I understood you search for max length between different elements.
For you problem with [4, 6, 2, 2, 6, 6, 4] it will have one iteration and return 5, for the problem with [1...70000] it will have zero iterations and will return the difference in positions for those two (size of the array - 1)
My understanding is that the problem is to find two unique elements in the array whose distance apart (difference in indices) is maximum, and to return the distance they are apart. I return nil if all elements are the same.
My solution attempts to minimize the numbers of pairs of elements that must be examined before an optimal solution is identified. For the example given in the question only two pairs of elements need be considered.
def calc_res(a)
sz = a.size-1
sz.downto(2).find { |n| (0..sz-n).any? { |i| a[i] != a[i+n] } }
end
a = [4,6,2,2,6,6,4]
calc_res a
#=> 5
If sz = a.size-1, sz is the greatest possible distance two elements can be apart. If, for example, a = [1,2,3,4], sz = 3, which is the number of positions 1 and 4 are apart.
For a, sz = a.size-1 #=> 6. I first determine if any pair of elements that are n = sz positions apart are unique. [a[0], a[6]] #=> [4,4] is the only pair of elements 6 positions apart. Since they are not unique I reduce n by one (to 5) and examine all pairs of elements n positions apart, looking for one whose elements are unique. There are two pairs 5 positions apart: [a[0], a[5]] #=> [4,6] and [a[1], a[6]] #=> [6,4]. Both of these meet the test, so we are finished, and return n #=> 5. In fact we are finished after testing the first of these two pairs. Had neither these pairs contained unique values n would have been reduced by 1 to 4 and the three pairs [a[0], a[4]] #=> [4,6], [a[1], a[5]] #=> [6,6] and [a[2], a[6]] #=> [2,6] would have been searched for one with unique values, and so on.
See Integer#downto, Enumerable#find and Enumerable#any?.
A more rubyesque versions include:
def calc_res(a)
last = a.last
idx = a.find_index {|e| e != last }&.+(1) || a.size
a.size - idx
end
def calc_res(a)
last = a.last
a.size - a.each.with_index(1).detect(->{[a.size]}) {|e,_| e != last }.last
end
def calc_res(a)
last = a.last
a.reduce(a.size) do |memo, e|
return memo unless e == last
memo -= 1
end
end
def calc_res(a)
return 0 if b = a.uniq and b.size == 1
a.size - a.index(b[-1]).+(1)
end

Random sum of elements in an array equals to y - ruby [duplicate]

This question already has answers here:
Finding all possible combinations of numbers to reach a given sum
(32 answers)
Closed 6 years ago.
Need to create an array whose sum should be equal to expected value.
inp = [1,2,3,4,5,6,7,8,9,10]
sum = 200
output:
out = [10,10,9,1,3,3,3,7,.....] whose sum should be 200
or
out = [10,7,3,....] Repeated values can be used
or
out = [2,3,4,9,2,....]
I tried as,
arr = [5,10,15,20,30]
ee = []
max = 200
while (ee.sum < max) do
ee << arr.sample(1).first
end
ee.pop(2)
val = max - ee.sum
pair = arr.uniq.combination(2).detect { |a, b| a + b == val }
ee << pair
ee.flatten
Is there any effective way to do it.
inp = [1,2,3,4,5,6,7,8,9,10]
sum = 20
inp.length.downto(1).flat_map do |i|
inp.combination(i).to_a # take all subarrays of length `i`
end.select do |a|
a.inject(:+) == sum # select only those summing to `sum`
end
One might take a random element of resulting array.
result = inp.length.downto(1).flat_map do |i|
inp.combination(i).to_a # take all subarrays of length `i`
end.select do |a|
a.inject(:+) == sum # select only those summing to `sum`
end
puts result.length
#⇒ 31
puts result.sample
#⇒ [2, 4, 5, 9]
puts result.sample
#⇒ [1, 2, 3, 6, 8]
...
Please note, that this approach is not efficient for long-length inputs. As well, if any original array’s member might be taken many times, combination above should be changed to permutation, but this solution is too ineffective to be used with permutation.
I found an answer of this question in the following link:
Finding all possible combinations of numbers to reach a given sum
def subset_sum(numbers, target, partial=[])
s = partial.inject 0, :+
#check if the partial sum is equals to target
puts "sum(#{partial})=#{target}" if s == target
return if s >= target #if we reach the number why bother to continue
(0..(numbers.length - 1)).each do |i|
n = numbers[i]
remaining = numbers.drop(i+1)
subset_sum(remaining, target, partial + [n])
end
end
subset_sum([1,2,3,4,5,6,7,8,9,10],20)

Select method with args separated by double-pipes only checking first element... Alternative?

I have a bit of broken legacy code I have to fix. Its purpose was to take a large array and return elements that have a particular sector number.
Here's a simplified version of the code in the app. Goal: return any instance of 1 or 3 in array:
array = [1,1,2,2,3,3].select{|num| num == (1 || 3) }
But the return value is simply #=> [1, 1] when the desired return was #=> [1, 1, 3, 3]
Basically, what I'm looking for is the Ruby equivalent to the following SQL query:
SELECT num FROM array
WHERE num IN (1, 3);
Ruby 1.8.7, Rails 2.3.15
Do as below to meet your need :
array = [1,1,2,2,3,3]
array.select{|num| [1,3].include? num }
# => [1, 1, 3, 3]
See why you got only [1,1].
1 || 3 # => 1
1 || 3 will always returns 1, thus num == 1 is evaluated as true when select is passing only 1. As a result you got [1,1].

How to generate a human readable time range using ruby on rails

I'm trying to find the best way to generate the following output
<name> job took 30 seconds
<name> job took 1 minute and 20 seconds
<name> job took 30 minutes and 1 second
<name> job took 3 hours and 2 minutes
I started this code
def time_range_details
time = (self.created_at..self.updated_at).count
sync_time = case time
when 0..60 then "#{time} secs"
else "#{time/60} minunte(s) and #{time-min*60} seconds"
end
end
Is there a more efficient way of doing this. It seems like a lot of redundant code for something super simple.
Another use for this is:
<title> was posted 20 seconds ago
<title> was posted 2 hours ago
The code for this is similar, but instead i use Time.now:
def time_since_posted
time = (self.created_at..Time.now).count
...
...
end
If you need something more "precise" than distance_of_time_in_words, you can write something along these lines:
def humanize(secs)
[[60, :seconds], [60, :minutes], [24, :hours], [Float::INFINITY, :days]].map{ |count, name|
if secs > 0
secs, n = secs.divmod(count)
"#{n.to_i} #{name}" unless n.to_i==0
end
}.compact.reverse.join(' ')
end
p humanize 1234
#=>"20 minutes 34 seconds"
p humanize 12345
#=>"3 hours 25 minutes 45 seconds"
p humanize 123456
#=>"1 days 10 hours 17 minutes 36 seconds"
p humanize(Time.now - Time.local(2010,11,5))
#=>"4 days 18 hours 24 minutes 7 seconds"
Oh, one remark on your code:
(self.created_at..self.updated_at).count
is really bad way to get the difference. Use simply:
self.updated_at - self.created_at
There are two methods in DateHelper that might give you what you want:
time_ago_in_words
time_ago_in_words( 1234.seconds.from_now ) #=> "21 minutes"
time_ago_in_words( 12345.seconds.ago ) #=> "about 3 hours"
distance_of_time_in_words
distance_of_time_in_words( Time.now, 1234.seconds.from_now ) #=> "21 minutes"
distance_of_time_in_words( Time.now, 12345.seconds.ago ) #=> "about 3 hours"
chronic_duration parses numeric time to readable and vice versa
If you want to show significant durations in the seconds to days range, an alternative would be (as it doesn't have to perform the best):
def human_duration(secs, significant_only = true)
n = secs.round
parts = [60, 60, 24, 0].map{|d| next n if d.zero?; n, r = n.divmod d; r}.
reverse.zip(%w(d h m s)).drop_while{|n, u| n.zero? }
if significant_only
parts = parts[0..1] # no rounding, sorry
parts << '0' if parts.empty?
end
parts.flatten.join
end
start = Time.now
# perform job
puts "Elapsed time: #{human_duration(Time.now - start)}"
human_duration(0.3) == '0'
human_duration(0.5) == '1s'
human_duration(60) == '1m0s'
human_duration(4200) == '1h10m'
human_duration(3600*24) == '1d0h'
human_duration(3600*24 + 3*60*60) == '1d3h'
human_duration(3600*24 + 3*60*60 + 59*60) == '1d3h' # simple code, doesn't round
human_duration(3600*24 + 3*60*60 + 59*60, false) == '1d3h59m0s'
Alternatively you may be only interested in stripping the seconds part when it doesn't matter (also demonstrating another approach):
def human_duration(duration_in_seconds)
n = duration_in_seconds.round
parts = []
[60, 60, 24].each{|d| n, r = n.divmod d; parts << r; break if n.zero?}
parts << n unless n.zero?
pairs = parts.reverse.zip(%w(d h m s)[-parts.size..-1])
pairs.pop if pairs.size > 2 # do not report seconds when irrelevant
pairs.flatten.join
end
Hope that helps.
There is problem with distance_of_time_in_words if u ll pass there 1 hour 30 min it ll return about 2 hours
Simply add in helper:
PERIODS = {
'day' => 86400,
'hour' => 3600,
'minute' => 60
}
def formatted_time(total)
return 'now' if total.zero?
PERIODS.map do |name, span|
next if span > total
amount, total = total.divmod(span)
pluralize(amount, name)
end.compact.to_sentence
end
Basically just pass your data in seconds.
Rails has a DateHelper for views. If that is not exactly what you want, you may have to write your own.
#Mladen Jablanović has an answer with good sample code. However, if you don't mind continuing to customize a sample humanize method, this might be a good starting point.
def humanized_array_secs(sec)
[[60, 'minutes '], [60, 'hours '], [24, 'days ']].inject([[sec, 'seconds']]) do |ary, (count, next_name)|
div, prev_name = ary.pop
quot, remain = div.divmod(count)
ary.push([remain, prev_name])
ary.push([quot, next_name])
ary
end.reverse
end
This gives you an array of values and unit names that you can manipulate.
If the first element is non-zero, it is the number of days. You may want to write code to handle multiple days, like showing weeks, months, and years. Otherwise, trim off the leading 0 values, and take the next two.
def humanized_secs(sec)
return 'now' if 1 > sec
humanized_array = humanized_array_secs(sec.to_i)
days = humanized_array[-1][0]
case
when 366 <= days
"#{days / 365} years"
when 31 <= days
"#{days / 31} months"
when 7 <= days
"#{days / 7} weeks"
else
while humanized_array.any? && (0 == humanized_array[-1][0])
humanized_array.pop
end
humanized_array.reverse[0..1].flatten.join
end
end
The code even finds use for a ruby while statement.

RoR array -count identical elements-on including value

how do we count idential values on after appending value in to array
such that
a=[]
a<<1 count of 1 is 1
a<<1 count of 1 is 2
thanks
You could do:
a.select{|v| v == 1}.size
It's only one solution
Someone will probably come up with a more specialized solution, but I would just reduce it
counts = [1,3,3].reduce({}) do |acc,n|
acc.tap do |a|
a[n] ||= 0
a[n] += 1
end
end
counts.each {|k,v| puts "#{k} was found #{v} times"}
(note that tap is ruby 1.9, and is backported in activesupport)
output of that will be
1 was found 1 times
3 was found 2 times
a = [1,2,3,4,5,1,2,2,3,4]
=> [1, 2, 3, 4, 5, 1, 2, 2, 3, 4]
a.uniq.each do |i|
?> puts i.to_s + ' has appeared ' + a.count(i).to_s + ' times'
end
1 has appeared 2 times
2 has appeared 3 times
3 has appeared 2 times
4 has appeared 2 times
5 has appeared 1 times
=> [1, 2, 3, 4, 5]

Resources