Сортировка списка для формирования максимально возможного числа

Я пытаюсь написать функцию, которая дает список не отрицательных целых чисел, упорядочивает их так, что они образуют наибольшее возможное число.

Например, при заданном [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

В 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 http://stackoverflow.com/q/30140796/4014959

    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 http://stackoverflow.com/q/30140796/4014959

    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:! Это потому, что метод __cmp__ больше не поддерживается в Python 3. Поэтому я изменил свой старый метод __cmp__ на _cmp и использовал его для реализации всех 6 богатые методы сравнения.

Важное примечание

Я должен упомянуть, что эта функция сравнения немного странная: она не транзитивна, другими словами, a > b и b > c не обязательно означает a > c. А это означает, что результаты его использования в .sort() непредсказуемы. Кажется, он делает правильные вещи для данных, которые я тестировал, например, он возвращает правильный результат для всех перестановок [1, 5, 10], но я думаю, на самом деле не следует доверять этому для всех входных данных.забастовкa >

Альтернативной стратегией, гарантирующей работу, является грубая сила: сгенерируйте все перестановки входного списка и найдите перестановку, которая дает максимальный результат. Но, надеюсь, существует более эффективный алгоритм, так как генерация всех перестановок большого списка довольно медленная.


Как отмечает Антти Хаапала в комментариях, мои старые функции сравнения были неустойчивыми при сравнении разных чисел, которые состоят из одних и тех же последовательностей повторяющихся цифр, например 123123 и 123123123. Такие последовательности должны сравниваться равными, мои старые функции не выполнялись что. Последняя модификация устраняет эту проблему.


Обновление

Оказывается, что mycmp() / _cmp() фактически транзитивен. Он также стабилен, теперь он корректно обрабатывает корпус ab == ba, поэтому он безопасен для использования с TimSort (или любым другим алгоритмом сортировки). И можно показать, что он дает тот же результат, что и ключевая функция Antti Haapala fractionalize().

В дальнейшем я буду использовать прописные буквы для представления целых чисел в списке, и я буду использовать строчную версию буквы для представления числа цифр в этом целочисленном. Например, a - количество цифр в a. Я буду использовать _ как оператор инфикса для представления конкатенации цифр. Например, A_B - int(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 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(), поскольку ключевой функции достаточно найти перестановку, которая дает максимально возможное целое число созданный путем конкатенации цифр. Подробности этого аргумента будут оставил упражнение для читателя.:)

Ответ 2

Один лайнер, используя идеи от Антти Хаапалы, PM 2Ring и Stefan Pochmann:

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

Учитывая a = [50, 5, 51, 59, 2, 1, 9, 98]:

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

Ответ 3

Вот уродливое решение, которое работает без передачи функции сравнения 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) здесь, можно также написать следующий oneliner:

from math import ceil, log10

print(sorted(a, key=lambda i: i and i/(10**ceil(log10(i+1))-1), reverse=True))

Защиты частей i and для деления на нулевую ошибку, в случае, если 0 относится к числу.

Ответ 4

Надеюсь, я не слишком сильно меняюсь. Мой ввод представлен как список строк. Я генерирую список перестановок, создавая список списков, а затем сортирует подсписок от наименьшего до наибольшего. Наконец, я беру последний элемент отсортированного списка.

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]

Эта вторая на самом деле также служит для показа первой, правильно сортируя по спискам.

Я новичок в Python и буду благодарен за комментарии/улучшения.

Ответ 5

Самый простой способ - использовать itertools.permutations() для моделирования того, как вы могли бы решить это вручную:

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

Ответ 6

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))