问题在于您的 rotations 函数:
def rotations(t):
''' Return list of rotations of input string t '''
tt = t * 2
return [ tt[i:i+len(t)] for i in xrange(0, len(t)) ]
看看它的作用:
>>> rotations('x')
['x']
>>> rotations('xx')
['xx', 'xx']
>>> rotations('xxxxx')
['xxxxx', 'xxxxx', 'xxxxx', 'xxxxx', 'xxxxx']
这样做的结果将呈指数级增长。因此,500000 字符的文件将产生长度为500000^2 的结果。
从计算上讲,对于这么大的输入,不太可能有一种方法可以完成您尝试的操作:即让字符串的每次旋转都长 500k 个字符。我们知道输入中的每个元素都有一个输出,每个输出都有一个原始输入的长度。因此,最小大小为n*n 或n^2。除非你知道你只需要有限数量的这些(并且可以及早淘汰它们),否则你总是会遇到这个问题。
如何解决问题
首先我们需要找出问题所在。让我们看看代码在做什么。假设一个简单的起始集:
bacb
rotation() 提供该集合的所有可能旋转:
>>> rotations('bacb')
['bacb', 'acbb', 'cbba', 'bbac']
然后您对该列表进行排序。
>>> sorted(rotations('bacb'))
['acbb', 'bacb', 'bbac', 'cbba']
然后你取每个元素的最后一个元素,得到bdac。这意味着对于输入中的每个元素 n,您正在分配一个排序顺序,例如 n+1 ... n(环绕)将按字母数字排序。
为了解决这个问题,算法将是:
- 创建一个空列表“final_order”,它将是输入列表的“排序”索引列表。
- 对于每个元素
- 获取从该元素加一开始的原始字符串的“旋转”
- 以排序方式将轮换放入“final_order”列表中:
- 获取“final_order”列表中第一个元素的“旋转”。
- 比较两个旋转。
- 如果新旋转小于旧旋转,则在该点插入到列表中。否则转到下一个轮换。
- 如果没有其他旋转,则将新的旋转放在那里。
(可能有一种更快的排序方式,但为了便于解释,我将采用这种方式。)
我们首先需要的是get_rotation(input, idx):
def get_rotation(input, idx):
return input[idx + 1:] + input[:idx + 1]
现在是困难的部分(见 cmets):
def strange_sort(input):
sorted_indices = list() # Initialize the list
for idx in range(len(input)): # For each element in the list
new_rotation = get_rotation(input, idx) # Get the rotation starting at that index
found_location = False # Need this to handle the sorting
for sorted_idx in range(len(sorted_indices)): # Iterate through all 'found' indices
old_rotation = get_rotation(input, sorted_indices[sorted_idx]) # Get the rotation starting at the found/old rotation
if new_rotation < old_rotation: # Which comes first?
# If this one, insert the new rotation's starting index before the index of the already sorted rotation
sorted_indices.insert(sorted_idx, idx)
found_location = True
break
if not found_location: # If greater than everything, insert at end
sorted_indices.insert(len(sorted_indices), idx)
return "".join(map(lambda x: input[x], sorted_indices)) # Join and return result
运行这个我们得到一个短输入的预期结果:
>>> print("Final result={}".format(strange_sort('bacb')))
Final result=bbca
这是带有测试/计时器的完整程序:
import random, string, datetime
def get_rotation(input, idx):
return input[idx + 1:] + input[:idx + 1]
def strange_sort(input):
sorted_indices = list()
for idx in range(len(input)):
new_rotation = get_rotation(input, idx)
found_location = False
for sorted_idx in range(len(sorted_indices)):
old_rotation = get_rotation(input, sorted_indices[sorted_idx])
if new_rotation < old_rotation:
sorted_indices.insert(sorted_idx, idx)
found_location = True
break
if not found_location:
sorted_indices.insert(len(sorted_indices), idx)
return "".join(map(lambda x: input[x], sorted_indices))
n1 = 5
n2 = 50
n3 = 500
n4 = 5000
n5 = 50000
n6 = 500000
n = [n1, n2, n3, n4, n5, n6]
def test(lst):
for l in range(len(lst)):
input = ''.join(random.choice(string.ascii_uppercase+string.digits) for x in range(lst[l]))
start = datetime.datetime.now()
result = strange_sort(input)
end = datetime.datetime.now()
runtime = end - start
print("n{} runtime={} head={} tail={}".format(l, runtime.seconds, result[:5], result[-5:]))
test(n)
尝试是利用不需要存储所有内容,只需为初始排序的每个索引存储最终排序的索引即可。可悲的是,上面的实现显然太慢了,从运行中我们可以看出:
$ python2 strange_sort.py
n0 runtime=0 head=SJP29 tail=SJP29
n1 runtime=0 head=5KXB4 tail=59WAK
n2 runtime=0 head=JWO54 tail=7PH60
n3 runtime=4 head=Y2X2O tail=MFUGK
(Still running)
好的,所以我们知道这种情况很糟糕。我们可以让它更快吗?我们从Python Wiki Entry on Big-O 中看到,需要O(M) 来获取字符串切片。对我们来说,这意味着O(N),因为我们正在拍摄两个切片,它们会增加整个长度。这在计算上是一场灾难,因为我们每次都在这样做。
与其每次都获得完整的旋转,不如让我们迭代和比较。一个旋转的一个索引与另一个旋转的一个索引的单个比较应该是O(2)。在最坏的情况下,我们必须这样做O(N) 次,但不太可能每次都这样。
我们添加了一个额外的 for 循环并将其重新设计为只查看下一个索引:
for offset in range(len(input)):
if new_rotation[offset] < input[(sorted_indices[sorted_idx] + offset) % len(input)]:
sorted_indices.insert(sorted_idx, idx)
found_location = True
break
if found_location:
break
我们现在用我们的计时器来执行它:
$ python2 strange_sort.py
n0 runtime=0 head=VA6KY tail=VA6KY
n1 runtime=0 head=YZ39U tail=63V0O
n2 runtime=0 head=JFYKP tail=8EB2S
n3 runtime=0 head=IR4J9 tail=VLR4Z
n4 runtime=28 head=EYKVG tail=7Q3NM
n5 runtime=4372 head=JX4KS tail=6GZ6K
正如我们所见,这次我们只用了 28 秒就到达了n4。不过,这对n6 来说并不是个好兆头。唉,看起来这种计算的复杂性表明我们需要一种比Insertion Sort 更好的排序方法,Insertion Sort 最差(甚至平均)是O(n^2)。在输入500K 时,至少需要进行250B(十亿)计算。 (次数n,计算机每次计算实际执行的指令数)。
我们已经了解到的是,您实际上并不需要将旋转放在一边。要解决这个问题,你必须编写一个快速排序算法,它的输入不是实际值,而是一个可以在给定精度下计算值的函数。
把整个事情放在头上,我想尝试创建一个对象,该对象可以对自身进行足够深入的搜索,以了解它如何与另一个对象进行排序,并使用 Python 的内置排序。
import random, string, datetime
from functools import total_ordering
@total_ordering
class Rotation(object):
"""Describes a rotation of an input based on getting the original and then offsetting it."""
def __init__(self, original, idx):
self.original = original
self.idx = idx
def getOffset(self, offset):
return self.original[(self.idx + offset) % len(self.original)]
def __eq__(self, other):
print("checking equality")
if self.idx == other.idx:
return True
for offset in range(len(self.original)):
if self.getOffset(offset) is not other.getOffset(offset):
print("this={} is not that={}".format(self.getOffset(offset), other.getOffset(
offset)))
return False
return True
def __lt__(self, other):
for offset in range(len(self.original)):
if self.getOffset(offset) < other.getOffset(offset):
return True
elif self.getOffset(offset) > other.getOffset(offset):
return False
return False
def __str__(self):
return self.getOffset(-1)
def __repr__(self):
return "".join(map(lambda x: str(x), [self.getOffset(idx) for idx in range(len(
self.original))]))
def improved_strange_sort(input):
original = list(input)
rotations = [Rotation(original, idx) for idx in range(len(original))]
result = sorted(rotations)
# print("original={} rotations={} result={}".format(original, rotations, result))
return "".join(map(lambda x: str(x), result))
def test(input):
start = datetime.datetime.now()
result = improved_strange_sort(input)
end = datetime.datetime.now()
runtime = end - start
print("input={} runtime={} head={} tail={}".format(input[:5], runtime.seconds, result[:5],
result[-5:]))
def timed_test(lst):
for l in range(len(lst)):
print("Test {} with length={}".format(l, lst[l]))
test(''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(lst[l])))
n1 = 5
n2 = 50
n3 = 500
n4 = 5000
n5 = 50000
n6 = 500000
n = [n1, n2, n3, n4, n5, n6]
test('bacb')
timed_test(n)
这似乎产生了正确的结果:
$ python2 strange_sort.py
input=bacb runtime=0 head=bbca tail=bbca
Test 0 with length=5
input=FB2EH runtime=0 head=BF2HE tail=BF2HE
Test 1 with length=50
input=JT3ZP runtime=0 head=W8XQE tail=QRUC3
Test 2 with length=500
input=TL8L7 runtime=0 head=R4ZUG tail=M268H
Test 3 with length=5000
input=PYFED runtime=1 head=L5J0T tail=HBSMV
Test 4 with length=50000
input=C6TR8 runtime=254 head=74IIZ tail=U69JG
Test 5 with length=500000
(still running)