summing up string representation of year and month durations in ruby - ruby

I was wondering if there is a way to sum up multiple durations in string representations like 2 years 2 months, 10 months, 3 years and output 6 years

You could do that as follows.
str = "2 years 4 months, 10 months, 3 years, 1 month"
r = /
(\d+) # match one or more digits in capture group 1
\s+ # match one or more whitespace chars
(year|month) # match 'year' or 'month' in capture group 2
s? # optionally match 's'
\b # match a word break
/x # free-spacing regex definition mode
a = str.scan r
#=> [["2", "year"], ["4", "month"], ["10", "month"], ["3", "year"], ["1", "month"]]
h = a.each_with_object(Hash.new(0)) { |(n,period),h| h[period] += n.to_i }
#=> {"year"=>5, "month"=>15}
y, m = h["month"].divmod(12)
#=> [1, 3]
h["year"] += y
#=> 6
h["month"] = m
#=> 3
h #=> {"year"=>6, "month"=>3}
Notes:
As noted in the doc for String#scan, "If the pattern contains groups, each individual result is itself an array containing one entry per group."
Hash.new(0) creates an empty hash with a default value of zero, meaning that if that hash h does not have a key k, h[k] returns zero. Thos is sometimes called a counting hash. See the doc for Hash::new.
Numeric#divmod is a useful and greatly-underused method.

Related

Count the number of vowels occurring in all the substrings of given string

I am looking at this challenge:
Given a string of length N of lowercase characters containing 0 or more vowels, the task is to find the count of vowels that occurred in all the substrings of the given string.
Example
Input: str = "abc"
Output: 3
The given string "abc" contains only one vowel = 'a'.
Substrings of "abc" are
{"a", "b", "c", "ab", "bc", "abc"}
Hence, the sum of occurrences of the vowel(s) in these strings is:
3
('a' occurred 3 times).
How to solve the above problem in O(N) time complexity ?
Here are some elements to use in the algorithm:
Let's first count how many substrings can be formed from a string (ignoring vowel counts):
"a" => {"a"} => 1
"ab" => {"ab", "a", "b"} => 1+2 = 3
"abc" => {"abc", "ab", "bc", "a", "b", "c"} => 1+2+3 = 6
"abcd" => {"abcd", "abc", "bcd", "ab", "bc", "cd", "a", "b", "c", "d"} => 1+2+3+4 = 10
...
The pattern is 1+2+3+...+𝑛, where 𝑛 is the length of the string, which is 𝑛(𝑛+1)/2
Now let's take a string that just has one vowel: "klmnopqrst". Then the answer consists of counting the number of substrings which have this vowel.
We know there are 10(10+1)/2 = 55 substrings in total, but many of those counted subtrings do not have a vowel. None of the subtrings of "klmn" have a vowel. There are 4(4+1)/2 = 10 such subtrings. Also none of the subtrings of "pqrst" have a vowel. There are 5(5+1)/2 = 15 such substrings. All other substrings have the vowel. So we can make the subtraction... the output should be 55 - 10 - 15 = 30.
Therefore the general principle is: for each vowel in the input, determine how many substrings do not include that vowel -- by counting the number of substrings at the left, and those at the right of the vowel. This gives us a clue about the number of substrings that do include that vowel -- by subtracting the non-cases from the total number of substrings.
If we do this for each vowel, we will have counted the total occurrences of vowels in all the substrings.
Here is that algorithm expressed in pseudo code:
function occurrence(str):
n := length(str)
total := 0
allcount := n * (n + 1) // 2
for i := 1 to n:
if str[i] is a vowel:
total = total + allcount - (i - 1) * i / 2 - (n - 1 - i) * (n - i) / 2
return total
NB: note that -- as is common in pseudo code -- i is a position (starting at 1), not a zero-based index.
(In case trincot's answer is not enough.)
Each vowel appears in (l + 1) * (r + 1) substrings, where l is the number of characters to the left of the vowel and r the number of characters on the right of the vowel.
Example 1:
"abc"
'a': (0 + 1) * (2 + 1) = 3
Total: 3
Example 2:
"ae"
'a': (0 + 1) * (1 + 1) = 2
'e': (1 + 1) * (0 + 1) = 2
Total: 4

I keep getting an error that I don't understand

I am trying to create a function that takes a string in it's parameters. It's supposed to determine the highest and lowest numeric values in the string and return them unchanged.
Here's my code:
def high_and_low(numbers)
numbers.split
numbers.each {|x| x.to_i}
return numbers.max().to_s, numbers.min().to_s
end
Here's the error:
main.rb:5:in `high_and_low': undefined method `each' for "4 5 29 54 4 0 -214 542 -64 1 -3 6 -6":String (NoMethodError)
from main.rb:8:in `<main>'
You have not changed the value from string to array.
Replace numbers.split with numbers = numbers.split.
Also you will need to change from numbers.each { |x| x.to_i } to numbers.map!(&:to_i). Otherwise you don't save integers anywhere.
BTW you don't have to use () and return (if it's in the end) so you can write [numbers.max.to_s, numbers.min.to_s].
Something like this should work:
def high_and_low(numbers)
numbers = numbers.split.map(&:to_i)
[numbers.max, numbers.min].map(&:to_s)
end
high_and_low("4 5 29 54 4 0 -214 542 -64 1 -3 6 -6") #=> ["542", "-214"]
And bonus (one liner, not that you should write code this way):
def high_and_low(numbers)
numbers.split.map(&:to_i).sort.values_at(-1, 0).map(&:to_s)
end
high_and_low("4 5 29 54 4 0 -214 542 -64 1 -3 6 -6") #=> ["542", "-214"]
The other answer is a good approach too so I include it here:
numbers.split.minmax_by { |n| -n.to_i }
Ruby has some nice methods available to make this much more simple:
"2 1 0 -1 -2".split.map(&:to_i).minmax
# => [-2, 2]
Breaking it down:
"2 1 0 -1 -2".split # => ["2", "1", "0", "-1", "-2"]
.map(&:to_i) # => [2, 1, 0, -1, -2]
.minmax # => [-2, 2]
If you want string versions of the values back, compare two integers in a block. minmax will return the values at the corresponding positions in the source array:
"2 1 0 -1 -2".split.minmax{ |a, b| a.to_i <=> b.to_i }
# => ["-2", "2"]
or:
"2 1 0 -1 -2".split.minmax_by{ |a| a.to_i }
# => ["-2", "2"]
minmax and minmax_by do the heavy lifting. The first is faster when there isn't a costly lookup to find the values being compared such as this case where the values are in an array and only needed to_i to compare them.
The *_by version performs a "Schwartzian transform" which basically remembers the values in the block as they're compared so the costly lookup only occurs once. (Many of Enumerable's methods have *_by versions.) These versions of the methods can improve the speed when you want to compare two values that are nested, perhaps in arrays of hashes of hashes, or objects within objects within objects.
Note: When comparing string versions of numbers it's important to convert to a numeric value when comparing. ASCII and strings order differently than numbers, hence the use of to_i.

Ruby -- Why does += increase the number for my string?

In the following code the value for "seven" changes from 1 to 2:
word_counts = Hash.new(0)
sample = "If seven maids with seven mops"
sample.split.each do |word|
word_counts[word.downcase] += 1
puts word_counts
end
Output:
{}
{"if"=>1}
{"if"=>1, "seven"=>1}
{"if"=>1, "seven"=>1, "maids"=>1}
{"if"=>1, "seven"=>1, "maids"=>1, "with"=>1}
{"if"=>1, "seven"=>2, "maids"=>1, "with"=>1}
{"if"=>1, "seven"=>2, "maids"=>1, "with"=>1, "mops"=>1}
Can someone explain why it went from 1 to 2?
OK, I'll try..
word_counts[word.downcase] += 1 means word_counts[word.downcase] = word_counts[word.downcase] + 1. Now, on fifth iteration word equals 'seven', so it does word_counts['seven'] = word_counts['seven'] + 1. But word_counts['seven'] was 1, so it becomes 2.
When you split the string you get the array with two strings "seven", because the sentence has two occurrences of that word.
"If seven maids with seven mops".split #=> ["If", "seven", "maids", "with", "seven", "mops"]

Why does Ruby return for `str[-1..1]` what it does?

Suppose we have a string str. If str contains only one character, for example, str = "1", then str[-1..1] returns 1.
But if the size (length) of str is longer than one, like str = "anything else", then str[-1..1] returns "" (empty string).
Why does Ruby interpret string slicing like this?
This behaviour is just how ranges of characters work.
The range start is -1, which is the last character in the string. The range end is 1, which is the second position from the start.
So for a one character string, this is equivalent to 0..1, which is that single character.
For a two character string, this is 1..1, which is the second character.
For a three character string, this is 2..1, which is an empty string. And so on for longer strings.
To get a non-trivial substring, the start position has to represent a position earlier than the end position.
For a single-length string, index -1 is the same as index 0, which is smaller than 1. Thus, [-1..1] gives a non-trivial substring.
For a string longer than a single character, index -1 is larger than index 0. Thus, [-1..1] cannot give a non-trivial substring, and by default, it returns an empty string.
Writing down the indices usually helps me:
# 0 1 2 3 4 5 6 7 8 9 10 11 12
str = 'a' 'n' 'y' 't' 'h' 'i' 'n' 'g' ' ' 'e' 'l' 's' 'e' #=> "anything else"
# -13 -12 -11 -10 -9 -8 -7 -6 -5 -4 -3 -2 -1
You can refer to each character by either its positive or negative index. For example, you can use either 3 or -10 to refer to "t":
str[3] #=> "t"
str[-10] #=> "t"
and either 7 or -6 to refer to "g":
str[7] #=> "g"
str[-6] #=> "g"
Likewise, you can use each of these indices to retrieve "thing" via a range:
str[3..7] #=> "thing"
str[3..-6] #=> "thing"
str[-10..7] #=> "thing"
str[-10..-6] #=> "thing"
str[-1..1] however would return an empty string, because -1 refers to the last character and 1 refers to the second. It would be equivalent to str[12..1].
But if the string consists of a single character, that range becomes valid:
# 0
str = '1'
# -1
str[-1..1] #=> "1"
In fact, 1 refers to an index after the first character, so 0 would be enough:
str[-1..0] #=> "1"

how to insert char at multiple position from end of the string

input like this:
100000000
1000
100
100000
10000
i need to start inserting char (,) from end of the string.first time after three char from end and then repeat the insert (,) after every 2 char
output
10,00,00,000
1,000
100
1,00,000
10,000
any hint guys how can i do this, i need to start inserting char(',') from end of the string.
thanks!
Here are two ways, the first using a regular expression, the last just inserting commas in a loop.
a = %w| 100000000 1000 100 100000 10000 |
#=> ["100000000", "1000", "100", "100000", "10000"]
#1 Use a regex
r = /
(?<=\d) # match digit in positive lookbehind
\d{2} # match two digits
(?= # begin positive lookahead
(?:\d{2})* # match two digits, repeated zero or more times
\d # match last digit
\z # match end of string
) # end positive lookahead
/x # extended mode
a.each { |s| puts "#{s} -> #{ s.gsub(r) { |ss| ',' + ss } }" }
100000000 -> 10,00,00,000
1000 -> 1,000
100 -> 100
100000 -> 1,00,000
10000 -> 10,000
This regex is similar to the one given earlier by #Avinish, but I chose to use a positive lookbehind and no capture group, and presented it in extended mode to help readers understand how it worked. I would use a regular expression here.
#2 Insert commas
If you do not wish to use a regular expression, you could determine the position of the last comma to insert (p below), the number of commas to insert (n below) and then insert them, back to front:
def insert_commas(string)
sz = string.size
str = string.dup
p = sz - 3
n = (sz - 2)/2
n.times { str.insert(p, ','); p -= 2 }
str
end
a.each { |s| puts "#{s} -> #{insert_commas(s)}" }
100000000 -> 10,00,00,000
1000 -> 1,000
100 -> 100
100000 -> 1,00,000
10000 -> 10,000
I duped string on the assumption you did not want to mutate string.
Alternatively,
def insert_commas(string)
sz = string.size
return string if sz < 4
p = sz.even? ? 1 : 2
string[0,p] + string[p..-2].gsub(/\d{2}/) { |s| ",#{s}" } + string[-1]
end
Use the below positive lookahead based regex.
gsub(/(\d)(?=(?:\d{2})*\d{3}$)/, "$1,")
DEMO

Resources