Ste*_*ann 3 python sorting timsort
我想对列表进行排序并观察/可视化 Python 的排序算法Timsort如何移动元素。
第一次尝试:list
每次更改后都会打印自己的子类:
class List(list):
def __setitem__(self, index, value):
list.__setitem__(self, index, value)
print(self)
Run Code Online (Sandbox Code Playgroud)
这在我自己更改元素时有效,但在sort
...期间没有:
>>> a = List([None] * 2)
>>> a[0] = 'S'
['S', None]
>>> a[1] = 'P'
['S', 'P']
>>> a.sort()
>>>
Run Code Online (Sandbox Code Playgroud)
第二次尝试:a
在每次比较元素时打印列表(在全局变量中):
class Str(str):
def __lt__(self, other):
print(a)
return other > self
Run Code Online (Sandbox Code Playgroud)
那确实有些作用,但列表总是......空的?
>>> a = list(map(Str, 'test'))
>>> a.sort()
[]
[]
[]
[]
[]
[]
>>>
Run Code Online (Sandbox Code Playgroud)
为什么这些尝试会失败,有没有办法观察 Timsort 正在做什么?
是的,可以观察。我做了,这是一个可视化:
另一个有更多元素:
为什么你的尝试会失败?
list
因为子失败sort
的作品“内部”名单在C代码级别,而不是通过公共的Python要去__setitem__
接口。Run Code Online (Sandbox Code Playgroud)/* The list is temporarily made empty, so that mutations performed * by comparison functions can't affect the slice of memory we're * sorting (allowing mutations during sorting is a core-dump * factory, since ob_item may change).
但是,如果列表为空,我们如何在排序过程中观察列表?简单:我们没有。不是在排序期间。但经过部分排序。
Timsort 是:
Run Code Online (Sandbox Code Playgroud)/* [...] Even in case of error, the * list will be some permutation of its input state (nothing is lost or * duplicated).
所以这个想法是:
这样做的代码(除了它不显示重复状态,当 Timsort 在下一步之前进行多次比较时会发生这种情况):
import string
from random import shuffle
from itertools import count
class Str(str):
def __lt__(self, other):
global allowed_comparisons
if allowed_comparisons == 0:
raise Exception
allowed_comparisons -= 1
return other > self
a = list(string.ascii_letters + string.digits + '<>')
shuffle(a)
shown = None
for allowed_comparisons in count():
try:
b = list(map(Str, a))
b.sort()
except:
pass
state = ''.join(b)
if state != shown:
print(state)
shown = state
if list(state) == sorted(state):
break
Run Code Online (Sandbox Code Playgroud)
输出,讨论如下:
/* The list is temporarily made empty, so that mutations performed
* by comparison functions can't affect the slice of memory we're
* sorting (allowing mutations during sorting is a core-dump
* factory, since ob_item may change).
Run Code Online (Sandbox Code Playgroud)
在上面的输出中,请注意有三个阶段:
1356>ABCDGIJOQRSTVWZabcfijknrsvz
. Timsort 使用二进制插入排序来做到这一点。输出中的每一行对应一个插入。024789<EFHKLMNPUXYdeghlmopqtuwxy
. 再次使用二进制插入排序。1356>ABCDGIJOQRSTVWZabcfijknrsvz024789<EFHKLMNPUXYdeghlmopqtuwxy
01356>ABCDGIJOQRSTVWZabcfijknrsvz24789<EFHKLMNPUXYdeghlmopqtuwxy
0
从下半场移到前面之后,列表看起来像这样:0--------------------------------24789<EFHKLMNPUXYdeghlmopqtuwxy
0
将整个前半部分向右移动一个索引。那将是低效的,而且在 Timsort 正常运行时不会发生这种情况,没有我的例外。您还可以在顶部的动画图像中看到这三个阶段。在这个“屏幕保护程序”版本中,我向下滚动上面的输出。我认为它看起来很酷(希望它看起来有点像Matrix coderain)但我发现它不太清楚:
请注意,这里的第三阶段从右到左合并(右半部分移出到临时空间),Timsort 在更好的时候这样做。
在将状态存储在列表中而不是打印它们之后,使用Pillow生成该图像的代码states
:
from PIL import Image, ImageDraw
images = []
for i in range(len(states) - 14):
img = Image.new('RGB', (384, 225), (0, 0, 0))
text = '\n'.join(states[i:i+15])
d = ImageDraw.Draw(img)
d.multiline_text((0,0), text, fill=(0, 200, 0))
img = img.resize((384*2, 225*2), 0)
images.append(img)
images[0].save('timatrix.png', save_all=True, append_images=images[1:],
optimize=False, duration=100, loop=0)
Run Code Online (Sandbox Code Playgroud)