16

I'd like to go through all n-digit numbers such that second digit of the number is always lower or equal to the first, third is lower or equal to the second etc. I can get this by writing a horrible code such as:

for i in range(10):
    for j in range(i+1):
        for k in range(j+1):

etc., but with 10-digit numbers my code starts looking horrible, and also that's a lot of writing, and indentation get horrible if I want to commend few of those. Is there a nice, concise way of getting this?

Edit: just so that people know why I'm bothering with this, https://projecteuler.net/problem=74 has me check numbers from 1 to one milion. Unfortunately, It's not as straightforward as I thought -- numbers with leading zeros are treated differently than the ones with zeros inside, so some additional magic had to be performed. Anyway, thanks to all for insightful suggestions.

098799
  • 462
  • 1
  • 4
  • 17
  • 1
    @iFlo some ancient beast that works by mysterious magic. – 098799 Jan 20 '17 at 13:18
  • Is the order of the numbers important? – Stefan Pochmann Jan 20 '17 at 13:20
  • @StefanPochmann Not for this use, but in general would also be good to know how to do it with a fixed order. – 098799 Jan 20 '17 at 13:21
  • 1
    Actually, here the way you should proceed : Considerate your number, compare the two first digit. Then, call the function again but you have truncate the first digit. Then you are checking the 2nd and the 3rd digit, etc. You can stop either if there is no longer two digitis to compare or if the current comparison failed. – iFlo Jan 20 '17 at 13:23
  • 1
    @iFlo I don't think you understand. I don't want to check whether or not a given number obeys my rule, I want to run a function on ALL n-digit numbers obeying this rule, so it's quite crucial not to iterate on all n-digit numbers and just check if the rule is being obeyed because it would take ages. – 098799 Jan 20 '17 at 13:25
  • @StefanPochmann: I missed the lower _or equal_ :-(. – RemcoGerlich Jan 20 '17 at 13:31
  • @098799 Is there a time limit? I checked them all, with the only optimization being hard-coded factorials, and it finished in 30 seconds. Here's [my solution](https://repl.it/FPkK). Don't view until you're done if you don't want my solution influencing yours. – mbomb007 Jan 20 '17 at 17:27
  • 1
    Not entirely related to your question: it's possible to combine multiple for loops into a single loop using [this trick](http://codegolf.stackexchange.com/a/41217/34718). – mbomb007 Jan 20 '17 at 17:34

5 Answers5

23

Could use itertools:

>>> for comb in itertools.combinations_with_replacement(range(9, -1, -1), 3):
        print comb

(9, 9, 9)
(9, 9, 8)
(9, 9, 7)
(9, 9, 6)
...
(4, 0, 0)
(3, 3, 3)
(3, 3, 2)
(3, 3, 1)
(3, 3, 0)
(3, 2, 2)
(3, 2, 1)
(3, 2, 0)
(3, 1, 1)
(3, 1, 0)
(3, 0, 0)
(2, 2, 2)
(2, 2, 1)
(2, 2, 0)
(2, 1, 1)
(2, 1, 0)
(2, 0, 0)
(1, 1, 1)
(1, 1, 0)
(1, 0, 0)
(0, 0, 0)

Or recursively, appending more and more digits until enough, which can more directly produce int objects instead of digit tuples (not sure whether that's what you actually need):

def build(enough, prefix=0):
    if prefix >= enough:
        print(prefix)
        return
    for digit in range(prefix % 10 + 1) if prefix else range(1, 10):
        build(enough, prefix * 10 + digit)

Demo (note it leaves out "000", not sure whether you'd want that anyway):

>>> n = 3
>>> build(10**(n-1))
100
110
111
200
210
211
220
221
222
300
310
311
320
321
322
330
331
332
333
400
410
411
420
Stefan Pochmann
  • 27,593
  • 8
  • 44
  • 107
4

this an approach using itertools:

from itertools import combinations_with_replacement

N = 3

for kji in combinations_with_replacement((str(i) for i in range(10)), N):
    print(''.join(reversed(kji)))

note that the order is not the same as in your original approach.

i recently had a simliar question...

hiro protagonist
  • 44,693
  • 14
  • 86
  • 111
2

A simple recursive approach

def ordered_digits_generator(numDigits,min=1,max=9):
    for first in range(min,max+1):
        if numDigits == 1:
             yield first
        else:
             addend = first*10**(numDigits-1)
             for rest in ordered_digits(numDigits-1,min=0,max=first):
                 yield addend+rest

Then called via:

for number in ordered_digits_generator(10):
    print number

works as expected.

The mathematician's approach

The itertools package already has logic which essentially already implements this recursion. Presumably better than the first approach, with significant testing. So we can use it as follows:

import itertools
def ordered_digits_combo(numDigits):
    exponent = [10**i for i in range(0,numDigits)]

    for subset in itertools.combinations(range(0,numDigits+9),numDigits):
        if subset[numDigits-1]>numDigits-1:
            v = 0
            for i in range(0,numDigits):
                v += exponent[i]*(subset[i]-i)
            yield v

Given an ordered subset a[0]<a[1]<...<a[n-1] of {0,1,...,n+8}, we pick the number with the ith digit from the right equal to a[i]-i. We have to exclude the case a[n-1]==n-1 because that consists of a number with all zeros.

Olivier
  • 303
  • 3
  • 14
Thomas Andrews
  • 1,577
  • 1
  • 13
  • 31
0

I implemented @iFlo's suggestion as commented originally. It's not hyper efficient but it certainly doesn't take ages.

def digit_test(n):
    while n > 9:
        if (n % 100 / 10) < (n % 10): return False
        n /= 10
    return True

# under a second to construct a list of all numbers below 1000000 meeting the criteria
candidates = [x for x in xrange(1,1000000) if digit_test(x)]

# should be 8001 elements, consistent with other algorithms
print len(candidates)
brian_o
  • 462
  • 1
  • 6
  • 16
0

I would probably implement this recursively:

def generate(max, digits):
    for d in range(max + 1):
        if digits == 1:
            yield d
        else:
            first = d * 10**(digits-1)
            for n in generate(d, digits - 1):
                yield first + n

The output:

In : list(generate(3, 3))
Out:
[0,
 100,
 110,
 111,
 200,
 210,
 211,
 220,
 221,
 222,
 300,
 310,
 311,
 320,
 321,
 322,
 330,
 331,
 332,
 333]
Brandon
  • 2,367
  • 26
  • 32