目标
如何使用尽可能少的数据对描述如何将静态列表从一个订单重新排序到另一个订单的数据进行编码?
我觉得有一个算法或计算机科学术语可以帮助我,但是现在我太过坚持这个问题,想出其他方法来看待它。
背景动机
我有一个部署到远程位置的程序,所有通信都通过间歇性的极其昂贵的卫星连接进行。这有点夸张,但数据成本接近每千字节一美元,而且每天只能发生几次。
在一天开始时,向用户提供一个项目列表,他们在现场外出并做一些事情,但最终结果或多或少是以不同顺序排序的相同项目列表。还有其他数据,但这对这个问题并不重要。
现在我正在发回所有发生的动作的记录并按顺序播放它们。当用户对系统感到满意时,移动记录列表开始接近只返回所有项目的大小,并且通常某些移动组合会导致撤消先前的移动记录。
假设
最简单的数据结构
出于解决此问题的目的,假设有以下数据结构。
这是一个示例列表。每个列表中的项目是相同的。请注意,即使只有少数项目已更改,但每个项目ID都有一个新的排序顺序,因此您不能只发回新的item_id / sort_order_id对。
**List 1: Original List** **List 2: Re-ordered List**
order - id order - id
1. 10 1. 90
2. 20 2. 30
3. 30 3. 40
4. 40 4. 50
5. 50 5. 60
6. 60 6. 10
7. 70 7. 80
8. 80 8. 70
9. 90 9. 20
如何使用尽可能少的数据编码将List 1的顺序转换为List 2的顺序所需的更改?
好奇心是否可以证明有一个解决方案是最优的?
更新
一位同事指出,“交换”可能不是正确的思考方式。您还可以将项目发送到列表的顶部或底部,这更像是一个移动而不是交换。交换然后变成两个动作的组合。
感谢指点。到目前为止,我没有看到保证最佳解决方案。此外,问题只是改变了一点。
如果我无法证明任何单个方法产生最佳结果,那么我将找出使用每种方法的解决方案,并使用指示所用方法的小标头发回该解决方案。继续建议解决方案,我会用我的研究更新这个问题。
谢谢大家!
答案 0 :(得分:2)
Algo part:
列表的重新排序称为排列。每个排列can be split into a set of loops,每个N个元素的循环需要(N-1)交换。例如
1,2,3,4,5,6-> 3,2,4,1,6,5
这可以拆分成 1 - 4 - 3(需要2个掉期) 2 - 2(0掉期) 5 - 6(1次交换)
要找到解决方案,您只需在错误的位置拾取任何元素并将其放在原位。
详细信息部分:
当然,您可以使用较小的数据类型,RLE或其他一些编码算法等。
非常理论但非实际的部分。
N个数can be lexicographically ordered序列的所有排列,以及从0到(N! - 1)的一个数字足以表示序列。因此,理论上最好的答案是:计算排列的索引,转移它,通过该索引重新创建排列。
答案 1 :(得分:1)
我不确定分析掉期会给你带来什么;正如你所说,他们可以相互撤消,并导致令人困惑的结果。
我认为您最好的选择是在重新排序的列表中识别该列表中与原始列表无法重新排序的段,即使它们是从新位置开始的。在您的示例中,这是从30到60的段。因此,在一种运行长度编码中,我将发回一个描述位置和长度的段映射。
再次,使用您的示例数据:有序起始索引列表,长度:
{(9,1),(3,4),(1,1),(8,1),(7,1),(2,1)}
似乎是您可以发回的最小信息量。数据的可压缩性取决于共同的段的数量和大小。
(编辑) 实际上,如果掉期数量很少,我会发现有一些数据集,交换列表会更短。但是可能会有一些转换点,其中行程编码做得更好;在这种情况下,我会说计算两者并选择较小的一个。
答案 2 :(得分:1)
您想要的是排序列表所需的排列。您可以通过构造从0到n的索引列表来获得此结果,然后使用自定义比较函数对该列表进行排序,该函数比较相应索引处的项目。例如,在Python中:
perm = sorted(range(len(l)), key=lambda x:l[x])
然后您可以通过连接发送'perm',并使用它来获取排序列表:
for x in perm:
print perm[x]
作为进一步的优化,如果大多数元素保持不变,则排列将是高度可压缩的 - 通过使用常规压缩或通过使用差异等变换(例如,将每个元素存储为与前一元素的差异,而不是其绝对值)价值),move to front和run length encoding。
答案 3 :(得分:0)
快速解决方法可能是使用Zobrist hash来查看您返回先前订单的情况。也就是说,在每次交换之后,根据您到达的排列计算哈希值。每个哈希映射到目前为特定排列发现的最短交换序列。
这可以通过一些探索性搜索轻松扩展 - Zobrist哈希被发明为优化游戏树搜索的一种方式。
当然,交换次数的严格下限很容易 - 不在所需位置的项目数量。然而,这个下限是否真的可以实现是一个更难的问题。
答案 4 :(得分:0)
如果您真的想尽量减少通过网络传输的所有数据,那么您如何传输数据?例如,你是否以某种方式压缩它?如果您只有几千个项目,则使用32位数字进行排序可能有点过分。 16位可以获得65000项,一半为$$$。同样的ID也是如此。
答案 5 :(得分:0)
另一种可能的解决方案,忽略您的数据结构...
为已更改的项发送一组ID /索引(如果它是完全随机的稀疏子集,只列出它们)和描述该子集重新排序的置换数。置换数将需要一个大整数表示 - 大小应与log(n!)成比例,其中n是更改的项目数。
当然,置换数是从置换数组定义的,但是在解码时可以避免这个细节。诀窍是对置换数进行编码,这样,一旦将正确的第一项交换到第一个槽中,您还可以导出一个对阵列尾部正确的新置换数。
那是......
while not empty(indexes)
item-to-swap := permutation-no remainder len(indexes)
permutation-no := permutation-no div len(indexes)
if item-to-swap != 0 : swap slot[indexes[0]], slot[indexes[item-to-swap]]
indexes := tail(indexes)
即使所有项目在开始时都需要更改,也需要进行!= 0检查 - 项目可能已经在循环的早期向上交换到了正确的位置。
这并不会尝试优化掉期数量 - 在向下交换到正确的位置之前,项目可能会向上交换几次。也就是说,置换数可能是阵列随机置换的最佳空间表示。鉴于您的排列仅影响完整数组的一小部分,使用该子集的较小排列数很有意义。
答案 6 :(得分:0)
假设:
您最好的解决方案可能是:
不是保留所执行的所有交换的列表,而是在一天结束时比较您的开始和结束数据,然后生成进行更改所需的交换。这将忽略列表中保持不变的任何位置,即使它们只是因为一系列交换“解除”某些更改而保持不变。如果您的数据采用a,b,a,b,...
的形式,其中a
会告诉您下一个要素的索引,它们会以相同的顺序排列,b
会告诉您索引与之交换的项目。
因为您只进行互换而不是轮班,所以您应该很少得到像样本数据这样的数据,其中30,40和50的顺序相同,但位置略有不同。由于交换的数量将在列表中原始项目数量的1/4到1/10之间,因此您通常会以相同的顺序和最初的相同位置拥有大量数据。我们假设进行了以下交换:
1 <-> 9
4 <-> 2
5 <-> 2
结果列表为:
1. 90
2. 50
3. 30
4. 20
5. 40
6. 60
7. 70
8. 80
9. 10
因此,更改数据可以表示为:
1,9,2,4,4,5
这只有六个值,可以表示为16位数字(假设您的初始列表中不会有超过16,000个项目)。因此,每个“有效”交换可以用一个32位数字表示。由于实际互换的数量通常是原始列表大小的1/5到1/2,因此您最终会通过网络发送原始列表中10%到20%的数据(或者更少)如果其中一些交换相互撤销,“有效”掉期的数量可能更少。)
答案 7 :(得分:0)
正如Peter所说,最小化每个整数的大小是理想的 - 但事实上,你可以在不限制项目数量的情况下实现。 可变字节编码是一种仅使用必要的字节数来压缩整数序列的方法。最常见的方法是在每个字节中保留一位,以指示该字节是否是当前列表项中的最后一个字节。
首先使用 delta编码会很有用。这就是你在整数之间存储差异的地方,而不是整数本身 - 这意味着它们最终会用变量字节更好地压缩。当然,存储的整数(可能是在你的情况下被更改的项目的ID)必须先排序,但这对你来说似乎不是一个问题。