Say I have an array of positive whole integers; I\'d like to manipulate the order so that the concatenation of the resultant array is the largest number possible. For exampl
import itertools
def largestInt(a):
b = list(itertools.permutations(a))
c = []
x = ""
for i in xrange(len(b)):
c.append(x.join(map(str, b[i])))
return max(c)
Intuitively, we can see that a reverse sort of single digit numbers would lead to the higest number:
>>> ''.join(sorted(['1', '5', '2', '9'], reverse=True))
'9521'
so reverse sorting should work. The problem arises when there are multi-digit snippets in the input. Here, intuition again lets us order 9
before 95
and 17
before 1
, but why does that work? Again, if they had been the same length, it would have been clear how to sort them:
95 < 99
96 < 97
14 < 17
The trick then, is to 'extend' shorter numbers so they can be compared with the longer ones and can be sorted automatically, lexicographically. All you need to do, really, is to repeat the snippet to beyond the maximum length:
9
and 95
: compare 999
and 9595
instead and thus 999
comes first.1
and 17
: compare 111
and 1717
instead and thus 1717
comes first.132
and 13
: compare 132132
and 1313
instead and thus 132132
comes first.23
and 2341
: compare 232323
and 23412341
instead and thus 2341
comes first.This works because python only needs to compare the two snippets until they differ somewhere; and it's (repeating) matching prefixes that we need to skip when comparing two snippets to determine which order they need to be in to form a largest number.
You only need to repeat a snippet until it is longer than the longest snippet * 2 in the input to guarantee that you can find the first non-matching digit when comparing two snippets.
You can do this with a key
argument to sorted()
, but you need to determine the maximum length of the snippets first. Using that length, you can 'pad' all snippets in the sort key until they are longer than that maximum length:
def largestpossible(snippets):
snippets = [str(s) for s in snippets]
mlen = max(len(s) for s in snippets) * 2 # double the length of the longest snippet
return ''.join(sorted(snippets, reverse=True, key=lambda s: s*(mlen//len(s)+1)))
where s*(mlen//len(s)+1)
pads the snippet with itself to be more than mlen
in length.
This gives:
>>> combos = {
... '12012011': [1201, 120, 1],
... '87887': [87, 878],
... '99713': [97, 9, 13],
... '9955171': [9, 1, 95, 17, 5],
... '99799713': [97, 9, 13, 979],
... '10100': [100, 10],
... '13213': [13, 132],
... '8788717': [87, 17, 878],
... '93621221': [936, 21, 212],
... '11101110': [1, 1101, 110],
... }
>>> def test(f):
... for k,v in combos.items():
... print '{} -> {} ({})'.format(v, f(v), 'correct' if f(v) == k else 'incorrect, should be {}'.format(k))
...
>>> test(largestpossible)
[97, 9, 13] -> 99713 (correct)
[1, 1101, 110] -> 11101110 (correct)
[936, 21, 212] -> 93621221 (correct)
[13, 132] -> 13213 (correct)
[97, 9, 13, 979] -> 99799713 (correct)
[87, 878] -> 87887 (correct)
[1201, 120, 1] -> 12012011 (correct)
[100, 10] -> 10100 (correct)
[9, 1, 95, 17, 5] -> 9955171 (correct)
[87, 17, 878] -> 8788717 (correct)
Note that this solution is a) 3 lines short and b) works on Python 3 as well without having to resort to functools.cmp_to_key()
and c) does not bruteforce the solution (which is what the itertools.permutations
option does).