Feature #18762
openAdd an Array#undigits that compliments Integer#digits
Description
I've found Integer#digits convenient and useful but several times have needed to go from the place-value notation back to an Integer after manipulation and wished there was a complimentary Array#undigits.
class Array
def undigits(base = 10)
each_with_index.sum do |digit, exponent|
digit * base**exponent
end
end
end
42.digits.undigits
#=> 42
42.digits(16).undigits(16)
#=> 42
Below is my stab at a Ruby implementation with behavior mirroring Integer#digits.
class Array
def undigits(base = 10)
base_int = base.to_int
raise TypeError, "wrong argument type #{base_int.class} (expected Integer)" unless base_int.is_a?(Integer)
raise ArgumentError, 'negative radix' if base_int.negative?
raise ArgumentError, "invalid radix #{base_int}" if base_int < 2
each_with_index.sum do |digit, exponent|
raise MathDomainError, 'out of domain' if digit.negative?
digit * base_int**exponent
end
end
end
Updated by jeremyevans0 (Jeremy Evans) over 2 years ago
- Tracker changed from Bug to Feature
- Backport deleted (
2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN)
Updated by sawa (Tsuyoshi Sawada) over 2 years ago
Using a chain of two methods each_with_index
and sum
as well as **
for such a simple task is an overkill, and perhaps that is why you feel you want a built-in method. You can simply do:
[2, 4].inject{_1 + _2 * 10} # => 42
Updated by shan (Shannon Skipper) over 2 years ago
sawa (Tsuyoshi Sawada) wrote in #note-3:
Using a chain of two methods
each_with_index
andsum
as well as**
for such a simple task is an overkill, and perhaps that is why you feel you want a built-in method. You can simply do:[2, 4].inject(0){_1 + _2 * 10} # => 42
@sawa (Tsuyoshi Sawada) The code you showed would sum the numbers multiplied by 10, resulting in 60
rather than 42
.
[2, 4].inject(0){_1 + _2 * 10} #=> 60
Updated by sawa (Tsuyoshi Sawada) over 2 years ago
@shan (Shannon Skipper) (Shannon Skipper)
Sorry. I had the code wrong. A similar code works for an array in backwards.
[].inject(0){_1 * 10 + _2} # => 0
[2, 1].inject(0){_1 * 10 + _2} # => 21
[3, 2, 1].inject(0){_1 * 10 + _2} # => 321
[6, 3, 2, 1].inject(0){_1 * 10 + _2} # => 6321
Updated by shan (Shannon Skipper) over 2 years ago
sawa (Tsuyoshi Sawada) wrote in #note-5:
@shan (Shannon Skipper) (Shannon Skipper)
Sorry. I had the code wrong. A similar code works for an array in backwards.
[].inject(0){_1 * 10 + _2} # => 0 [2, 1].inject(0){_1 * 10 + _2} # => 21 [3, 2, 1].inject(0){_1 * 10 + _2} # => 321 [6, 3, 2, 1].inject(0){_1 * 10 + _2} # => 6321
I think you'd just need to reverse it, since this result seems backwards. I think it's easy to get the implementation wrong and a handy method to have.
6321.digits.inject(0) { _1 * 10 + _2 } #=> 1234
6321.digits.reverse.inject(0) { _1 * 10 + _2 } #=> 4321
Using your #reduce strategy, you'd end up with something like the following.
class Array
def undigits(base = 10)
reverse.reduce(0) do |acc, digit|
acc * base + digit
end
end
end
It's a fairly similar shape to what I had.
class Array
def undigits(base = 10)
each_with_index.sum do |digit, exponent|
digit * base**exponent
end
end
end
I assumed we'd want to implement it in C if it's accepted, and was just trying to illustrate behavior, but unsure.
Updated by shan (Shannon Skipper) over 2 years ago
@sawa (Tsuyoshi Sawada) If this feature is accepted and for whatever reason a Ruby version is used instead of C, yours is more performant Ruby code than mine. It might look like the following. We'd want to implement this in C like Integer#digits, right?
class Array
def undigits(base = 10)
base_int = base.to_int
raise TypeError, "wrong argument type #{base_int.class} (expected Integer)" unless base_int.is_a?(Integer)
raise ArgumentError, 'negative radix' if base_int.negative?
raise ArgumentError, "invalid radix #{base_int}" if base_int < 2
reverse.reduce(0) do |acc, digit|
raise MathDomainError, 'out of domain' if digit.negative?
acc * base + digit
end
end
end
Updated by inopinatus (Joshua GOODALL) 5 months ago ยท Edited
I'll speak up for this feature. Whilst writing a base58 encoder/decoder today, I was surprised by the lack of an inverse to Integer#digits
.
In my benchmarking the reduce/inject variant was considerably faster than the sum variant, especially when we get into bignum territory.
However, for consistency with other conversions, Integer(array, base)
should work too, as Integer(string, base)
currently does. The latter is already special-cased, so that could be extended to handle an array even if the method above doesn't end up as Array#to_i
.