将一个列表排序以形成最大可能的数字。

15

我正在尝试编写一个函数,它可以给定一个非负整数列表,将它们排列成最大可能的数字。

例如,给定[50, 2, 1, 9],可以组成的最大数字是95021

这是我尝试解决问题的代码:

a = [50, 2, 1, 9]
a.sort()
ans = []
for i in range(len(a)-1,-1,-1):
    ans.append(a[i])

print ''.join(map(str,ans))

然而,我得到了 50921,因为 50 是最大的,但应该先显示 9


1
你已经尝试过什么了吗?它为什么不能正常工作? - David Hoelzer
链接(https://blog.svpino.com/2015/05/07/five-programming-problems-every-software-engineer-should-be-able-to-solve-in-less-than-1-hour)已经失效:“安全连接失败。在与blog.svpino.com的连接中发生了错误。PR_END_OF_FILE_ERROR。无法显示页面...,因为无法验证接收数据的真实性。”如果使用HTTP版本(http://blog.svpino.com/2015/05/07/five-programming-problems-every-software-engineer-should-be-able-to-solve-in-less-than-1-hour),则会重定向并报告“404.页面未找到”。 - Peter Mortensen
也许可以添加一些答案中的示例输入数据(以及正确的结果),以获得更好的测试集?或者添加一些其他的示例输入数据。(但是不要包含“编辑:”,“更新:”或类似的内容——问题应该看起来像是今天写的。)这不会使任何答案无效。 - Peter Mortensen
一些被删除答案中的样本输入是(可能是好的候选):[2, 23, 21][1, 10] - Peter Mortensen
9个回答

23
在Python 2中,您可以使用适当的比较函数传递给"sort"来实现此操作。
#!/usr/bin/env python

''' Sort a list of non-negative integers so that
    if the integers were converted to string, concatenated
    and converted back to int, the resulting int is the highest
    possible for that list

    From https://dev59.com/UV0a5IYBdhLWcg3wmZsX

    Written by PM 2Ring 2015.05.10

    Python 2 version
'''

data = [
    [50, 2, 1, 9],
    [10, 1],
    [2, 23, 21],
]

def mycmp(a, b):
    a, b = str(a), str(b)
    ab, ba = a + b, b + a
    if ab == ba:
        return 0
    if ab < ba:
        return -1
    return 1

for a in data:
    print 'In: ', a
    a.sort(cmp=mycmp, reverse=True)
    print 'Out:', a
    print

输出

In:  [50, 2, 1, 9]
Out: [9, 50, 2, 1]

In:  [10, 1]
Out: [1, 10]

In:  [2, 23, 21]
Out: [23, 2, 21]

在Python 3中,sort不再使用自定义比较函数。scpio的答案展示了如何使用functools将比较函数转换为键函数,但是手动实现也不难。
#!/usr/bin/env python

''' Sort a list of non-negative integers so that
    if the integers were converted to string, concatenated
    and converted back to int, the resulting int is the highest
    possible for that list

    From https://dev59.com/UV0a5IYBdhLWcg3wmZsX

    Written by PM 2Ring 2015.05.10

    Python 3 compatible version
'''

from __future__ import print_function

class cmpclass(object):
    def __init__(self, n):
        self.n = str(n)

    def __str__(self):
        return self.n

    def _cmp(self, other):
        a, b = self.n, str(other)
        ab, ba = a + b, b + a
        if ab == ba:
            return 0
        if ab < ba:
            return -1
        return 1

    def __lt__(self, other): return self._cmp(other) == -1
    def __le__(self, other): return self._cmp(other) <= 0
    def __eq__(self, other): return self._cmp(other) == 0
    def __ne__(self, other): return self._cmp(other) != 0
    def __gt__(self, other): return self._cmp(other) == 1
    def __ge__(self, other): return self._cmp(other) >= 0


data = [
    [50, 2, 1, 9],
    [10, 1],
    [2, 23, 21],
]

for a in data:
    print('In: ', a)
    a.sort(key=cmpclass, reverse=True)
    print('Out:', a)
    print('')

输出

In:  [50, 2, 1, 9]
Out: [9, 50, 2, 1]

In:  [10, 1]
Out: [1, 10]

In:  [2, 23, 21]
Out: [23, 2, 21]

我之前发布的Python 3兼容版本实际上并不能在Python 3上运行 :oops:!这是因为在Python 3中不再支持__cmp__方法。因此,我将旧的__cmp__方法更改为_cmp并使用它来实现所有6个丰富比较方法
重要提示
另一种保证可行的策略是暴力破解:生成输入列表的所有排列,并找到产生最大结果的排列。但希望有一种更有效的算法,因为生成大型列表的所有排列相当缓慢。

正如安蒂·哈帕拉在评论中指出的那样,我的旧比较函数在比较由相同重复数字序列组成的不同数字时是不稳定的,例如123123和123123123。这些序列应该被视为相等,但我的旧函数无法做到这一点。最新的修改解决了这个问题。


更新

事实证明,mycmp() / _cmp() 实际上是可传递的。现在它也是稳定的,因为它正确处理了 ab == ba 的情况,所以可以安全地与TimSort(或任何其他排序算法)一起使用。而且可以证明它给出的结果与 Antti Haapala 的 fractionalize() 关键字函数相同。

接下来,我将使用大写字母表示列表中的整数,并使用字母的小写版本表示该整数的位数。例如,aA 中数字的数量。我将使用 _ 作为中缀运算符表示数字连接。例如,A_Bint(str(A)+str(B);请注意,A_B 具有 a+b 位数字。从算术上讲,A_B = A * 10**b + B

为了简洁起见,我将使用f()来代表Antti Haapala的fractionalize()关键函数。请注意,f(A) = A / (10**a - 1)
现在进入一些代数。我将把它放在代码块中以保持格式简单。
Let A_B = B_A
A * 10**b + B = B * 10**a + A
A * 10**b - A = B * 10**a - B
A * (10**b - 1) = B * (10**a - 1)
A / (10**a - 1) = B / (10**b - 1)
f(A) = f(B)

So A_B = B_A if & only if f(A) = f(B)

Similarly,
A_B > B_A if & only if f(A) > f(B)
This proves that using mycmp() / _cmp() as the sort comparison function
is equivalent to using fractionalize() as the sort key function.

Note that
f(A_B) = (A * 10**b + B) / (10**(a+b)-1)
and
f(B_A) = (B * 10**a + A) / (10**(a+b)-1)

So f(A_B) = f(B_A) iff A_B = B_A, and f(A_B) > f(B_A) iff A_B > B_A

Let's see what happens with 3 integers.

f(A), f(B), f(C) are just real numbers, so comparing them is
transitive.
And so if f(A) > f(B) and f(B) > f(C) then f(A) > f(C).
This proves that mycmp() / _cmp() is also transitive.

Clearly, if f(A) > f(B) > f(C) then
A_B > B_A, B_C > C_B, A_C > C_A

Let B_C > C_B
For any A,
A * 10**(b+c) + B_C > A * 10**(b+c) + C_B
So A_B_C > A_C_B
i.e. adding the same integer to the beginning of B_C and C_B preserves
the inequality.

Let A_B > B_A
For any C,
(A_B) * 10**c + C > (B_A) * 10**c + C
So A_B_C > B_A_C,
i.e. adding the same integer to the end of A_B and B_A preserves the
inequality.

Using these results, we can show that
if f(A) > f(B) > f(C) then
A_B_C > A_C_B > C_A_B > C_B_A and
A_B_C > B_A_C > B_C_A > C_B_A.

This covers all 6 permutations of [A, B, C] and shows that A_B_C is the
largest possible integer for that list.

一种数学归纳法的论证表明,使用mycmp() / _cmp()作为比较函数或使用fractionalize()作为键函数进行成对比较,可以对任何有限长度的列表进行排序,从而找到产生数字连接最大可能整数的排列。具体细节将留给读者作为练习。 :)

Py3版本对我来说不起作用,我得到了TypeError: unorderable types: cmpclass() < cmpclass() - Stefan Pochmann
@StefanPochmann:我已经解决了那个问题。然而,正如我在重要提示中提到的那样,我的算法存在根本性缺陷。幸运的是,Antti Hapala发布了一个更好的解决方案。 - PM 2Ring
@StefanPochmann 它是可传递的,并且与我的算法等效,只是当我问他如何知道它是可传递的时,我让 PM 2Ring 有点困惑了... - Antti Haapala -- Слава Україні
@AnttiHaapala 当比较22和222时,cmp两种方式都返回0,<也两种方式都返回False。这两个数字被视为相等。你有一个出错的完整示例吗? - Stefan Pochmann
@StefanPochmann:实际上,我的比较函数是可传递的。 :oops: 我的困惑来自于原始整数比较与自定义比较函数的不当混合。 - PM 2Ring
显示剩余5条评论

13

使用Antti HaapalaPM 2RingStefan Pochmann的见解,编写一行代码以形成最大可能的数字:

from fractions import Fraction
sorted(a, key=lambda n: Fraction(n, 1-10**len(str(n))))

假设有 a = [50, 5, 51, 59, 2, 1, 9, 98]

[9, 98, 59, 5, 51, 50, 2, 1]

1
10 ** len(str(n)) - 1 看起来相当快速。 - Antti Haapala -- Слава Україні
@AnttiHaapala 是的,看起来确实快了很多。已更新,谢谢! - tzaman
有趣的答案,特别是提到的神秘“洞见”。 - Stef
简化版:sorted(a, key=lambda n: Fraction(n, 1-10**len(str(n)))) - Kelly Bundy
@KellyBundy 当然,为什么不直接反向计算而不是分开做呢?多么优雅,简直不敢相信我没想到! - tzaman
@Stef 这些见解只是从阅读他们对同一问题的回答和评论中获得的,没有什么秘密! :) - tzaman

9
这里有一个丑陋的解决方案,它可以在不传递cmp比较函数到sorted的情况下工作。基本上,键函数接受每个数字并计算一个有该数字作为重复小数的有理数;也就是说,
0   => 0
100 => 100/999 == 0.100100100...
10  => 10/99   == 0.1010101010...
1   => 1/9     == 0.1111111111...
11  => 11/99   == 0.1111111111...
12  => 12/99   == 0.1212121212...
9   => 9/9     == 1
99  => 99/99   == 1
999 => 999/999 == 1

0被排序为最小值,其排序键为0;1后面跟着大部分的零将具有最接近0.1的排序键,因此排在第二个最小值。由数字9组成的数字的排序键都等于1;如果你将9排在99之前或之后并不重要。
使用这些值作为键进行排序将必然给出正确的输出,除非你使用的数字超过了浮点精度的范围(很可能比2 ** 53还要早)。
因此我们得到以下程序:
# for Python 2, not needed in Python 3
from __future__ import division

a = [50, 5, 51, 59, 2, 1, 9, 98]

def fractionalize(i):
    divisor = 9
    while divisor < i:
        divisor = 10 * divisor + 9 

    return i / divisor

print(sorted(a, key=fractionalize, reverse=True))

这将产生

[9, 98, 59, 5, 51, 50, 2, 1]

由于我们这里基本上是在计算i / (10 ** ceil(log10(i + 1)) - 1),因此也可以编写以下一行代码:

from math import ceil, log10

print(sorted(a, key=lambda i: i and i/(10**ceil(log10(i+1))-1), reverse=True))
部分用于防止除以零错误,以防0是其中的一个数字。

1
不妨使用 key=lambda n:str(n)*100,对吧? - Stefan Pochmann
@AnttiHaapala 好的,但是使用 Fraction 怎么样? - Stefan Pochmann
@StefanPochmann:1)str(n)* 100浪费了一些RAM,但我猜它不会减慢比较速度。2)使用Fraction很好,因为它允许列表包含对于浮点精度过高的int。另一方面,Fraction比较比float比较慢,因为它需要在(可能短路的)减法之上进行两次乘法。 - PM 2Ring
你的意思是除以log2(10)吧 ;-) - Antti Haapala -- Слава Україні
@AnttiHaapala:是的,我有这个问题(数字诵读障碍又来了 :))。或者乘以log10(2)。 - PM 2Ring
显示剩余4条评论

1

我的输入被转换为一个字符串列表。我生成排列列表,创建一个列表的列表,然后将子列表从小到大排序。最后,我取排序后列表的最后一个元素。

import itertools

digits = ['50', '2', '1', '9']
perms = itertools.permutations(digits)
sorted_numlist = sorted(perms)
print sorted_numlist[-1]

如果您更喜欢数字本身而不是元素列表...
import itertools

digits = ['11', '68', '4', '12']
perms = itertools.permutations(digits)
numlist = []
for sublist in perms:
    permutated_num = "".join(sublist)
    numlist.append(int(permutated_num))

sorted_numlist = sorted(numlist)
print sorted_numlist[-1]

那个第二个实际上也证明了第一个在列表排序方面的正确性。

-1
def make_it_large_num(l):
    lst = [str(x) for x in l]
    print(sorted(lst, reverse=True))
    res = ''.join(sorted(lst, reverse=True))
    print(res)


lst = [50,2,1,9]
make_it_large_num(lst)

这对我来说很有效。简单而且不需要使用任何库(Python 3)。


1
它只是看起来工作正常,因为你选择的测试数据不好。你应该总是在你有预期输出的数据上测试你的代码。对于lst = [50, 5, 51, 59, 2, 1, 9, 98],你得到了['98', '9', '59', '51', '50', '5', '2', '1'] 989595150521,这显然是错误的。 - Thierry Lathuille
@Thierry Lathuille:请注意,输入数据来自问题中的示例。 - Peter Mortensen
@PeterMortensen 没错,我使用了两个最受欢迎答案中使用的数据进行测试,但没有注意到这些数据并非来自问题本身。 - Thierry Lathuille
@Thierry Lathuille:感谢您分析这个答案。Stack Overflow上的大多数晚回答都没有争议,而且许多答案完全是虚假和/或剽窃的。 - Peter Mortensen

-1

这个版本对我来说可行:

def arrange(lst):
    for i in range(len(lst)):
        for j in range(i+1,len(lst)):
            if int(str(lst[j]+lst[i])) > int(str(lst[i]+lst[j])):
                temp = lst[i]
                lst[i] = lst[j]
                lst[j] = temp
    for i in lst:
        print(i, end="")

lst = [i for i in input().split()]
arrange(lst)

2
目前你的回答不够清晰,请编辑并添加更多细节以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community
使用 冒泡排序 - Peter Mortensen

-1

最直接的方法是使用itertools.permutations()来模拟您手动解决此问题的方式:

>>> from itertools import permutations, imap
>>> a = [50, 2, 1, 9]
>>> int(max(imap(''.join, permutations(map(str, a)))))
95021

1
对于 a = [50, 5, 51, 59, 2, 1, 9, 98] 给出了错误的结果。 - Kostas
Kostas,它可以产生正确的结果,但是如果在32位Python 2上运行,则需要使用longpermutations是在2.6中引入的,但是您必须一直回到2.2才能获得不会产生long(因此不正确)的int。 在Python 3中,您将使用map而不是imap,并且int已经处理任意精度。 - Yann Vernier

-2
import functools

def cmpr(x, y):
    xy = str(x) + str(y)
    yx = str(y) + str(x)
    return -1 if (xy > yx) else 1

a = [50, 2, 1, 9]
a.sort(key=functools.cmp_to_key(cmpr))

7
请考虑为这段代码添加一些解释。 - PM 2Ring
1
我认为即使在给定的示例中这也无法正常工作;你的比较函数没有返回正确的值来进行cmp。 - DSM
匆忙之中犯了一个错误,忘记了排序比较器返回的是-1或1而不是0和1。但我的答案已经被@PM 2Ring大致复制了,所以可以选用任意一个 ;) - scpio
1
@scpio:一个旧式比较函数需要返回三个不同的值,而不仅仅是两个。它需要分别为 (<, ==, >) 返回 (负数,零,正数) - PM 2Ring
Scpio已经离开了: "上次出现已经超过7年了" - Peter Mortensen

-2

列表项

def create_largest_number(number_list):
    res=''
    for i in number_list:
        res= res+ str(i)
        new=''.join(sorted(res))
    return new[::-1]       

number_list=[23,45,67]
largest_number=create_largest_number(number_list)
print(largest_number)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接