Project

General

Profile

Actions

Feature #18762

open

Add an Array#undigits that compliments Integer#digits

Added by shan (Shannon Skipper) about 2 years ago. Updated 7 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:108461]

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
Actions #1

Updated by shan (Shannon Skipper) about 2 years ago

  • Description updated (diff)
Actions #2

Updated by jeremyevans0 (Jeremy Evans) about 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) about 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) about 2 years ago

sawa (Tsuyoshi Sawada) wrote in #note-3:

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(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) about 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) about 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) about 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) 7 days 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.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0