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

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"

Related

Returning the highest and lowest numbers in a string: Ruby

Not sure what I'm doing incorrect but I seem to be getting it woefully wrong.
The question is, you are given a string of space separated numbers, and have to return the highest and lowest number.
Note:
All numbers are valid Int32, no need to validate them.
There will always be at least one number in the input string.
Output string must be two numbers separated by a single space, and highest number is first.
def high_and_low(numbers)
# numbers contains a string of space seperated numbers
#return the highest and lowest number
numbers.minmax { |a, b| a.length <=> b.length }
end
Output:
`high_and_low': undefined method `minmax' for "4 5 29 54 4 0 -214 542 -64 1 -3 6 -6":String
minmax is not implemented for a string. You need to split your string into an array first. But note that split will return an array of strings, not numbers, you will need to translate the strings to integers (to_i) in the next step.
Because minmax returns the values in the opposite order than required, you need to rotate the array with reverse and then just join those numbers with whitespace for the final result.
numbers = "4 5 29 54 4 0 -214 542 -64 1 -3 6 -6"
def high_and_low(numbers)
numbers.split.minmax_by(&:to_i).reverse.join(' ')
end
high_and_low(numbers)
#=> "542 -214"
How about:
numbers_array = numbers.split(' ')
"#{numbers_array.max} #{numbers_array.min}"
If you're starting with a string of numbers you may have to cast the .to_i after the call to split.
In that case:
numbers_array = numbers.split(' ').map { |n| n.to_i }
"#{numbers_array.max} #{numbers_array.min}"
As you're starting with a String, you must turn it into an Array to cast minmax on it.
Also, make sure to compare Integers by casting .map(&:to_i) on the Array; otherwise you'd compare the code-point instead of the numerical value.
def get_maxmin(string)
string.split(' ')
.map(&:to_i)
.minmax
.reverse
.join(' ')
end
There is no need to convert the string to an array.
def high_and_low(str)
str.gsub(/-?\d+/).
reduce([-Float::INFINITY, Float::INFINITY]) do |(mx,mn),s|
n = s.to_i
[[mx,n].max, [mn,n].min]
end
end
high_and_low "4 5 29 54 4 0 -214 542 -64 1 -3 6 -6"
#=> [542, -214]
Demo
This uses the form of String#gsub that has one argument and no block, so it returns an enumerator that I've chained to Enumerable#reduce (a.k.a. inject). gsub therefore merely generates matches of the regular expression /-?\d+/ and performs no substitutions.
My solution to this kata
def high_and_low(numbers)
numbers.split.map(&:to_i).minmax.reverse.join(' ')
end
Test.assert_equals(high_and_low("4 5 29 54 4 0 -214 542 -64 1 -3 6 -6"), "542 -214")
#Test Passed: Value == "542 -214"
Some docs about methods:
String#split Array#map Array#minmax Array#reverse Array#join
More about Symbol#to_proc
numbers.split.map(&:to_i) is same as number.split.map { |p| p.to_i }
But "minmax_by(&:to_i)" looks better, for sure I guess.

`to_i` method with base value as parameter in ruby

Can anyone explain how base parameter works when calling to_i with the following examples?
'2'.to_i(2) #=> 0
'3'.to_i(2) #=> 0
'12'.to_i(2) #=> 1
'122'.to_i(2) #=> 1
'20'.to_i(2) #=> 0
'21'.to_i(2) #=> 0
I do not understand how it's actually working. Can anyone explain please?
It is the same reason that '54thousand'.to_i is 54: to_i reads until it finds end of string or an invalid digit.
In binary (base 2), the only valid digits are 0 and 1. Thus, because 2 is invalid, '122'.to_i(2) is identical to '1'.to_i(2). Also, '2'.to_i(2) is identical to ''.to_i(2), which is rather intuitively 0.
base, in other word Radix means the number of unique digits in a numeral system.
In Decimal, we have 0 to 9, 10 digits to represent numbers.
You are using 2 as parameter, that means Binary, so there're only 0 and 1 working.
From the Doc of to_i:
Returns the result of interpreting leading characters in str as an
integer base base (between 2 and 36). Extraneous characters past the
end of a valid number are ignored. If there is not a valid number at
the start of str, 0 is returned. This method never raises an
exception when base is valid.
You can use these number representations directly in Ruby:
num_hex = 0x100
#=> 256
num_bin = 0b100
#=> 4
num_oct = 0o100
#=> 64
num_dec = 0d100
#=> 100

Retain 0's when incrementing number

If I increment integer 003 by 1, I get 4.
num = 003
num += 1
# => 4
I want it to be 004. How can I retain the 0's in this?
You can convert an integer to a String and give it a fixed amount of padding using String#rjust, where the first argument is the total width of the resulting String, and the second argument is the character to use for padding:
>> int = 3
>> str = int.to_s.rjust(3, '0')
#=> "003"
And then you can increment that string using String#next:
>> str.next
#=> "004"
First of all, the leading zero in 003 makes that an octal integer literal in Ruby, not a decimal. Of course, it doesn't matter with 3 but it does matter for 009 (which is a SyntaxError), 030, etc. Presumably you really mean to say:
num = 3
as the leading zeroes aren't really part of the number, they're just formatting that you want on output. To format the number, you'd use String#% or sprintf:
> '%03d' % 3
=> "003"
> sprintf('%03d', 3)
=> "003"

summing up string representation of year and month durations in 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.

Ruby's String#hex confusion

I've found it weird that String#hex in Ruby doesn't return the right hex value for a given char. I might be misunderstanding the method, but take the following example:
'a'.hex
=> 10
Whereas the right hex value for 'a' would be 61:
'a'.unpack('H*')
=> 61
Am I missing something? What's hex for? Any hints appreciated!
Thanks
String#hex doesn't give you the ASCII index of a character, it's for transforming a base-16 number (hexadecimal) from a string to an integer:
% ri String\#hex
String#hex
(from ruby site)
------------------------------------------------------------------------------
str.hex -> integer
------------------------------------------------------------------------------
Treats leading characters from str as a string of hexadecimal digits
(with an optional sign and an optional 0x) and returns the
corresponding number. Zero is returned on error.
"0x0a".hex #=> 10
"-1234".hex #=> -4660
"0".hex #=> 0
"wombat".hex #=> 0
So it uses the normal mapping:
'0'.hex #=> 0
'1'.hex #=> 1
...
'9'.hex #=> 9
'a'.hex #=> 10 == 0xA
'b'.hex #=> 11
...
'f'.hex #=> 15 == 0xF == 0x0F
'10'.hex #=> 16 == 0x10
'11'.hex #=> 17 == 0x11
...
'ff'.hex #=> 255 == 0xFF
It's very similar to String#to_i when using base 16:
'0xff'.to_i(16) #=> 255
'FF'.to_i(16) #=> 255
'-FF'.to_i(16) #=> -255
From the docs:
% ri String\#to_i
String#to_i
(from ruby site)
------------------------------------------------------------------------------
str.to_i(base=10) -> integer
------------------------------------------------------------------------------
Returns the result of interpreting leading characters in str as an
integer base base (between 2 and 36). Extraneous characters past the
end of a valid number are ignored. If there is not a valid number at the start
of str, 0 is returned. This method never raises an exception
when base is valid.
"12345".to_i #=> 12345
"99 red balloons".to_i #=> 99
"0a".to_i #=> 0
"0a".to_i(16) #=> 10
"hello".to_i #=> 0
"1100101".to_i(2) #=> 101
"1100101".to_i(8) #=> 294977
"1100101".to_i(10) #=> 1100101
"1100101".to_i(16) #=> 17826049
One more advantage over hex method. '10-0' to 256.
Consider you want to compare'100' > '20'. Should return true but return false. Use '100'.hex >'20'.hex. Returns true. Which is more accurate.

Resources