Question

The A010784 sequence at OEIS is the sequence containing only numbers with distinct digits. This is a finite amount.

What I've been trying to do is find several numbers in this sequence with certain attributes.
E.g: 6 is a distinct number of magnitude 10. This can be found as follows:

  • 6 x 1 = 6
  • 6 x 2 = 12
  • 6 x 3 = 18
  • 6 x 4 = 24
  • 6 x 5 = 30
  • 6 x 6 = 36
  • 6 x 7 = 42
  • 6 x 8 = 48
  • 6 x 9 = 54
  • 6 x 10 = 60
  • 6 x 11 = 66 (Two sixes; these aren't both distinct digits.)

Now I'm trying the highest numbers available for several magnitudes of order.
Let's say all orders from 1 up to 20.

What I'm currently doing is loop from the highest possible distinct number, which is 9,876,543,210 and the highest unique number of magnitude 1, down to a very low number.
This method feels extremely inefficient. At least, to me it does.

I'm pretty sure I'm missing some stuff about factoring that should be able to make things a lot faster.

def unique_num(num):
    # Check whether number is unique.
    return Boolean

def unique_num_magnitude(num):
    # Check of which magnitude the unique number is
    return Integer

# Dictionary with the current highest values
# for each magnitude.
values = {1: 987654321...}

# Set limits
upper_limit = 9876543210
lower_limit = 1

for numbers in range(upper_limit, lower_limit, -1):
    unique = unique_num(num) # Boolean
    if (unique):
        magnitude = unique_num_magnitude(num)
        if (values[magnitude] < num):
            values[magnitude] = num
Was it helpful?

Solution

Sure there is better way. You should build the numbers bottom up, i.e. if a number a1...ak (these are k digits) is not of magnitude N so that a digit appears twice within the first k digits of the result for any multiplicand 2..N then neither is any number b1...bm a1...ak. This provides a categorically faster recursive enumeration procedure because it cuts an exponential amount of search space away. Details left as an exercise to OP.

Longer explanation:

Assume there is a procedure

 magnitude(number_str)

that calculates the magnitude for the number number_str represented in 10-base, so that the procedure returns 0 if number_str contains at least one double occurrence of a digit, 1 if number_str has every digit distinct but multiplying the number by two results in a digit occurring multiple times, etc.

This procedure can be implemented in terms of another one

 unique_digits(number_str)

which returns true if all digits in number_str are unique, otherwise false.

Now then magnitude can be implemented as

 magnitude(number_str):
   n = str_to_int(number_str)
   k = n
   i = 0
   while (unique_digits(int_to_str(k))):
     k = k + n
     i = i + 1
   return i

Now this magnitude procedure can be changed to a nocarry_magnitude that changes the check subtly:

 nocarry_magnitude(number_str, limit):
   l = length(number_str)
   assert (l > 0)
   n = str_to_int(number_str)
   k = n
   i = 0
   while (unique_digits(right_substring(int_to_str(k), l))):
     k = k + n
     i = i + 1
     if (i == limit):
       break
   return i

This procedure checks for the multiple times occurring digits only in as many rightmost digits (lowest-order digits) of the product in the loop as there were digits in the original input. A limit parameter is needed to make sure the procedure terminates as it's possible that the procedure runs in an infinite loop otherwise. Clearly for any string s it holds that

 magnitude(s) <= nocarry_magnitude(s, M) for large M

For instance, take number 19:

 19 * 1 = 19
 19 * 2 = 38
 19 * 3 = 57
 19 * 4 = 76
 19 * 5 = 95
 19 * 6 = 114 // magnitude("19") = 5
 19 * 7 = 133 // nocarry_magnitude("19", 100) = 6

What I wrote above in the short description is that if

 nocarry_magnitude(s, x) == k     for x > k

then for any string that's obtained by postfixing s (denote this by x + s below), it holds that

 x : string of digits
 magnitude(x + s) <= nocarry_magnitude(x + s, m) <= nocarry_magnitude(s, m)
 when m >= magnitude(x + s)

The first inequality comes from the claim above and the second one is obvious from the definition of nocarry_magnitude. Note that magnitude(x + s) <= magnitude(s) is a non-theorem in general as exemplified by

 magnitude( "56")  = 1  // 56 * 2 = 112
 magnitude("256")  = 12 // the first duplicate occurs at 3328

which is caused by the carry. nocarry_magnitude ignores the carry which is why the suffix of a string has always as large nocarry-magnitude as any extension of it (towards left, i.e. higher-order digits).

Now then you can write a significantly faster recursive procedure as this for searching for numbers with magnitude at least M:

 search(str):
   if (str != ""):
     int n = nocarry_magnitude(str, M)
     if (n < M):
       return      # the optimization
     int k = magnitude(str)
     if (k >= M):
       store_answer(str)
   for d in ['1', '2', '3', '4', '5', '6', '7', '8', '9',
             '10', '20', '30', '40', '50', '60', '70', '80', '90']:
     search(d + str)

 search("")

Here's a full Python implementation (searching for magnitude 36):

def unique_digits(s):
    r = (len(list(s)) == len(set(s)))
    return r

def magnitude(s):
    n = int(s)
    k = n
    assert n > 0
    i = 0
    while (unique_digits(str(k))):
        k = k + n
        i = i + 1
    return i

def nocarry_magnitude(s, limit):
    l = len(s)
    assert l > 0
    n = int(s)
    assert n > 0
    k = n
    i = 0
    while (unique_digits(str(k)[-l:])):
        k = k + n
        i = i + 1
        if (i >= limit):
            break
    return i

M = 36

def search(s):
    if (not(s == "")):
        n = nocarry_magnitude(s, M)
        if (n < M):
            return
        k = magnitude(s)
        if (k >= M):
            print "Answer: %s |" % s,
            i = int(s)
            k = i
            n = 1
            while (n <= M):
                print k,
                k = k + i
                n = n + 1
            print
    for d in ['1', '2', '3', '4', '5', '6', '7', '8', '9',
              '10', '20', '30', '40', '50', '60', '70', '80', '90']:
        search(d + s)

search("")

And here's a run that takes less than one second; this finds the answer '27' which seems to be the unique number that provides the record magnitude 36:

Answer: 27 | 27 54 81 108 135 162 189 216 243 270 297 324 351 378 405
             432 459 486 513 540 567 594 621 648 675 702 729 756 783
             810 837 864 891 918 945 972

OTHER TIPS

Isn't this just a permutation problem? For a given magnitude M do you do 10cM.

For instance, of magnitude 2, how many ways are there to choose 2 digits from 0..9? (Actually, it should be one from 1..9 and one from 0..9 where the second digit doesn't match the first.)

You certainly don't need to run through them all and check them. Just set up a set and choose one, then choose another. Better yet, just create them from scratch. First do everything that starts with 1 (10, 12, 13, 14, etc) then everything that starts with 2, etc.

You have two problems:

1) Iterating over the A010784 sequence.

Use itertools.permutations to generate successive possible numbers.

2) Calculating the magnitude of a number.

You can determine if a number has only unique digits with this snippet of code:

def unique_num(x):
    return len(str(x)) == len(set(str(x))

You can cut some branches if you're just looking for the highest numbers. From 6 x 11 = 66 you also know that the magnitude of 11 is at most 5. Once you know a higher number with a magnitude >= 5 you can skip over 11.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top