为无向循环序列创建唯一标识符
Posted
技术标签:
【中文标题】为无向循环序列创建唯一标识符【英文标题】:Create unique identifier for undirected circular sequences 【发布时间】:2021-12-07 18:11:45 【问题描述】:假设我有一个如下所示的无向循环序列:
1 —— 2 —— 3
/ \
1 1
| |
3 2
\ /
3 —— 2 —— 3
假设我有以下 3 个序列,由数字列表表示:
seq1 = [1,1,3,3,2,3,2,1,3,2] # anticlockwise from top left
seq2 = [3,2,3,3,1,1,2,3,1,2] # clockwise from bottom right
seq3 = [3,1,2,3,2,3,3,1,1,2] # clockwise from top right
由于序列是无方向的,所有3个序列本质上是相同的,并且代表上面的循环序列。实际上,我有数千个这样的无向循环序列,因此不可能对每一对进行比较。因此,我想创建一个唯一标识符,可以代表每个唯一的无向循环序列。例如,上面 3 个序列的标识符应该相同。
我的想法是将这种类型的序列视为圆形图。然后我可以将边权重分配为两个连接节点之间的差异,并找到遍历所有节点的路径,同时最大化所有边权重的总和。下面是我的 Python 实现:
def identifier(seq):
delta_sum = float('-inf')
res_seq = []
for i in range(len(seq)):
new_seq = seq[i:] + seq[:i]
ds = sum([new_seq[j+1] - new_seq[j] for j in range(len(seq)-1)])
if ds > delta_sum:
delta_sum = ds
res_seq = new_seq
if -ds > delta_sum:
delta_sum = -ds
res_seq = new_seq[::-1]
return ','.join(map(str, res_seq))
print(identifier(seq1))
print(identifier(seq2))
print(identifier(seq3))
输出:
1,1,2,3,1,2,3,2,3,3
1,1,2,3,1,2,3,2,3,3
1,2,3,2,3,3,1,1,2,3
显然我的算法不起作用。它为前两个序列创建相同的标识符,但为第三个序列创建一个不同的标识符。谁能建议一种相对快速的算法(最好是 Python 代码)来为这种序列创建唯一标识符?
以下是一些相关的问题,但不完全是我想要实现的目标:
How to check whether two lists are circularly identical in Python
Fast way to compare cyclical data
【问题讨论】:
您链接的第二个线程有什么问题,使用字典顺序最小的字符串旋转?如果问题只是你的字符串是可逆的,你可以只使用原始或反向字符串的最小旋转。 我认为这可能更属于cs.stackexchange.com/questions/tagged/algorithms,因为它基本上是圆形图的哈希方法,不是吗? @kcsquared 它只适用于有向序列 是的,我在评论的第二部分中提到了这一点。您的“无向序列”只是普通字符串在反转和循环旋转下的等价类。以顺时针顺序在序列上运行一次 LMSR 算法,以逆时针顺序运行一次,并将两者中的最小值作为您的标识符,有什么问题? @kcsquared 如果他们相等怎么办? 【参考方案1】:您可以使用元组作为可散列标识符,并从序列的可能轮换中选择最小的一个:
def identifier(s):
return min((*s[i::d],*s[:i:d]) for d in (1,-1) for i in range(len(s)))
输出:
seq1 = [1,1,3,3,2,3,2,1,3,2] # anticlockwise from top left
seq2 = [3,2,3,3,1,1,2,3,1,2] # clockwise from bottom right
seq3 = [3,1,2,3,2,3,3,1,1,2] # clockwise from top right
print(identifier(seq1))
print(identifier(seq2))
print(identifier(seq3))
(1, 1, 2, 3, 1, 2, 3, 2, 3, 3)
(1, 1, 2, 3, 1, 2, 3, 2, 3, 3)
(1, 1, 2, 3, 1, 2, 3, 2, 3, 3)
鉴于最小的元组将从最小值开始,您可以通过首先找到最小值并仅比较从最小值索引开始形成的元组来对此进行一些优化:
def identifier(seq):
start = min(seq)
starts = [i for i,v in enumerate(seq) if v == start]
return min((*seq[i::d],*seq[:i:d]) for d in (1,-1) for i in starts)
【讨论】:
这是 O(n^2),对吧?如果图表很短,可能就足够了。 取决于序列中最小值的重复频率,但最坏的情况是 O(n^2)。如果最小值的平均频率是每个序列 3,那么它是 T(3n) 或 O(n x f) @AlainT。我喜欢你的回答。它看起来非常整洁。然而,“优化”版本是不正确的。你可以试试seq1 = [1,1,1,1,1,2,1,1,1,2,2]
和seq2 = [1,1,1,2,1,1,1,1,1,2,2]
。它分别返回(1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 2)
和(1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 2)
。您的第一个功能运行良好。
我的错,我从一个特定的位置向后退了 1 分(当我经历所有位置时并不重要,但它确实进行了优化)。现已修复。
尽管可能有更有效的想法,但它很简短,Pythonic 且易于理解。 +1以上是关于为无向循环序列创建唯一标识符的主要内容,如果未能解决你的问题,请参考以下文章