Project

General

Profile

Actions

Feature #18762

open

Add an Array#undigits that compliments Integer#digits

Added by shan (Shannon Skipper) about 2 months ago. Updated about 2 months ago.

Status:
Open
Priority:
Normal
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 months ago

  • Description updated (diff)
Actions #2

Updated by jeremyevans0 (Jeremy Evans) about 2 months ago

  • Backport deleted (2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN)
  • Tracker changed from Bug to Feature

Updated by sawa (Tsuyoshi Sawada) about 2 months 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 months 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 months 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 months 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 months 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
Actions

Also available in: Atom PDF