简体   繁体   English

生成所有k的数字序列,该数字序列的第k位从左到右从右到右加到10

[英]Generate sequence of numbers whose k-th digit from the left and from the right sums to 10 for all k

A Python coding exercise asks to make a function f such that f(k) is the k-th number such that its k-th digit from the left and from the right sums to 10 for all k. Python编码练习要求使函数f使得f(k)是第k个数字,以使所有k的左起和右起的第k个数字求和。 For example 5, 19, 28, 37 are the first few numbers in the sequence. 例如5, 19, 28, 37是序列中的前几个数字。

I use this function that explicitly checks if the number 'n' satisfies the property: 我使用此函数来显式检查数字“ n”是否满足该属性:

def check(n):

    #even digit length
    if len(str(n)) % 2 == 0:

        #looping over positions and checking if sum is 10
        for i in range(1,int(len(str(n))/2) + 1):

            if int(str(n)[i-1]) + int(str(n)[-i]) != 10:

                return False

    #odd digit length
    else:

        #checking middle digit first
        if int(str(n)[int(len(str(n))/2)])*2 != 10:

            return False

        else:
            #looping over posotions and checking if sum is 10
            for i in range(1,int(len(str(n))/2) + 1):

                if int(str(n)[i-1]) + int(str(n)[-i]) != 10:

                    return False

    return True

and then I loop over all numbers to generate the sequence: 然后遍历所有数字以生成序列:

for i in range(1, 10**9):

    if check(i):
        print(i)

However the exercise wants a function f(i) that returns the i-th such number in under 10 seconds. 但是,练习需要一个函数f(i)在不到10秒的时间内返回第i个此类数字。 Clearly, mine takes a lot longer because it generates the entire sequence prior to number 'i' to calculate it. 显然,我的时间要长得多,因为它会在数字“ i”之前生成整个序列以进行计算。 Is it possible to make a function that doesn't have to calculate all the prior numbers? 是否可以使一个函数不必计算所有先前的数字?

Testing every natural number is a bad method. 测试每个自然数是一个不好的方法。 Only a small fraction of the natural numbers have this property, and the fraction decreases quickly as we get into larger numbers. 只有一小部分自然数具有此属性,并且随着我们进入更大的数,分数会迅速下降。 On my machine, the simple Python program below took over 3 seconds to find the 1,000th number (2,195,198), and over 26 seconds to find the 2,000th number (15,519,559). 在我的机器上,下面的简单Python程序花了3秒多的时间来找到第1000个数字(2,195,198),并花费了26秒的时间来找到了第2000个数字(15,519,559)。

# Slow algorithm, only shown for illustration purposes

# '1': '9', '2': '8', etc.
compl = {str(i): str(10-i) for i in range(1, 10)}

def is_good(n):
    # Does n have the property
    s = str(n)
    for i in range((len(s)+1)//2):
        if s[i] != compl.get(s[-i-1]):
            return False
    return True

# How many numbers to find before stopping
ct = 2 * 10**3

n = 5
while True:
    if is_good(n):
        ct -= 1
        if not ct:
            print(n)
            break
    n += 1

Clearly, a much more efficient algorithm is needed. 显然,需要一种更有效的算法。

We can loop over the length of the digit string, and within that, generate numbers with the property in numeric order. 我们可以遍历数字字符串的长度,并在其中生成带有数字顺序的属性的数字。 Sketch of algorithm in pseudocode: 伪代码算法示意图:

for length in [1 to open-ended]:
    if length is even, middle is '', else '5'
    half-len = floor(length / 2)
    for left in (all 1) to (all 9), half-len, without any 0 digits:
        right = 10's complement of left, reversed
        whole-number = left + middle + right

Now, note that the count of numbers for each length is easily computed: 现在,请注意,可以很容易地计算出每个长度的数字计数:

Length    First    Last     Count
1         5        5        1
2         19       91       9
3         159      951      9
4         1199     9911     81
5         11599    99511    81

In general, if left-half has n digits, the count is 9**n . 通常,如果左半部分有n位数字,则计数为9**n

Thus, we can simply iterate through the digit counts, counting how many solutions exist without having to compute them, until we reach the cohort that contains the desired answer. 因此,我们可以简单地遍历数字计数,对存在的解决方案进行计数而不必计算它们,直到我们找到包含所需答案的同类为止。 It should then be relatively simple to compute which number we want, again, without having to iterate through every possibility. 然后,再次计算所需的数字应该相对简单,而不必遍历所有可能性。

The above sketch should generate some ideas. 上面的草图应该产生一些想法。 Code to follow once I've written it. 我写完之后要遵循的代码。

Code: 码:

def find_nth_number(n):
    # First, skip cohorts until we reach the one with the answer
    digits = 1
    while True:
        half_len = digits // 2
        cohort_size = 9 ** half_len
        if cohort_size >= n:
            break
        n -= cohort_size
        digits += 1

    # Next, find correct number within cohort

    # Convert n to base 9, reversed
    base9 = []
    # Adjust n so first number is zero
    n -= 1
    while n:
        n, r = divmod(n, 9)
        base9.append(r)
    # Add zeros to get correct length
    base9.extend([0] * (half_len - len(base9)))
    # Construct number
    left = [i+1 for i in base9[::-1]]
    mid = [5] * (digits % 2)
    right = [9-i for i in base9]
    return ''.join(str(n) for n in left + mid + right)

n = 2 * 10**3

print(find_nth_number(n))

This is a function that exploits the pattern where the number of "valid" numbers between adjacent powers of 10 is a power of 9. This allows us to skip over very many numbers. 这是一个利用以下模式的函数:相邻幂10之间的“有效”数字数量为9的幂。这使我们可以跳过很多数字。

def get_starting_point(k):
    i = 0
    while True:
        power = (i + 1) // 2
        start = 10 ** i
        subtract = 9 ** power
        if k >= subtract:
            k -= subtract
        else:
            break
        i += 1
    return k, start

I combined this with the method you've defined. 我将其与您定义的方法结合在一起。 Supposing we are interested in the 45th number, this illustrates the search starts at 1000, and we only have to find the 26th "valid" number occurring after 1000. It is guaranteed to be less than 10000. Of course, this bound gets worse and worse at scale, and you would want to employ the techniques suggested by the other community members on this post. 假设我们对第45个数字感兴趣,这说明搜索从1000开始,我们只需要找到在1000之后出现的第26个“有效”数字。保证小于10000。当然,此界限会变得更糟,并且在规模上更差,您可能想使用本文中其他社区成员建议的技术。

k = 45
new_k, start = get_starting_point(k)
print('new_k: {}'.format(new_k))
print('start at: {}'.format(start))
ctr = 0
for i in range(start, 10**9):
    if check(i):
        ctr += 1
        if ctr == new_k:
            break
print(i)

Output: 输出:

new_k: 26
start at: 1000
3827

It seems the 45th number is 3827. 看来第45个数字是3827。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM