Python:(带替换的采样):从集合中提取不同N元组集合的有效算法

Python:(带替换的采样):从集合中提取不同N元组集合的有效算法,python,algorithm,big-o,Python,Algorithm,Big O,我有一组项,我想从中选择不同的元组(稍后将详细介绍不同元组的定义)。该集合可能包含数千项,但通常只包含几百项 我正在尝试编写一个通用算法,允许我从原始集合中选择N个项以形成N元组。所选N元组的新集合应该是不同的 一个N元组A被认为与另一个N元组B不同,当且仅当: A中出现的每一对(2元组)都不会出现在B中 注:对于该算法,如果2元组(对)包含相同的元素,则认为其相似/相同,即(x,y)与(y,x)相同 这是经典的(可能的变化)。该算法的一个简单(伪代码)实现可以是 def fetch_uni

我有一组项,我想从中选择不同的元组(稍后将详细介绍不同元组的定义)。该集合可能包含数千项,但通常只包含几百项

我正在尝试编写一个通用算法,允许我从原始集合中选择N个项以形成N元组。所选N元组的新集合应该是不同的

一个N元组A被认为与另一个N元组B不同,当且仅当:

  • A中出现的每一对(2元组)都不会出现在B中
注:对于该算法,如果2元组(对)包含相同的元素,则认为其相似/相同,即(x,y)与(y,x)相同

这是经典的(可能的变化)。该算法的一个简单(伪代码)实现可以是

def fetch_unique_tuples(original_set, tuple_size):
    while True:
        # randomly select [tuple_size] items from the set to create first set
        # create a key or hash from the N elements and store in a set
        # store selected N-tuple in a container
        if end_condition_met:
            break
我不认为这是最有效的方法——虽然我不是算法理论家,但我怀疑这个算法运行的时间不是O(n)——事实上,它更可能是O(n!)。我想知道是否有一种更有效的方法来实现这种算法,最好是将时间缩短到O(n)

实际上,正如马克·拜尔斯指出的,还有第二个变量
m
,它是所选元素数量的大小。这(即
m
)通常介于2和5之间

关于示例,这里是一个典型的(尽管缩短了)示例:

[[编辑]]


实际上,在构建示例输出时,我意识到我之前给出的唯一性定义是不正确的。由于这个发现,我更新了我的定义,并引入了一个新的相异度量。

这是算法的一个简单实现。我也不是理论家,但我喜欢算法。我认为这个简单的实现是O(n^m),其中m是维度+组合的一些东西,应该小于O(n!)

我尝试了另一种方法——组合。它似乎运行得相当快:

def fetch_unique_tuples(original_set, tuple_size):
    from itertools import combinations

    good = []
    used = []
    for i in combinations(original_set,tuple_size):
        lst = list([tuple(sorted(j)) for j in combinations(i,2)])
        if not any(l in used for l in lst):
            used.extend(lst)
            good.append(tuple(sorted(i)))
    return sorted(good)

elements = ['CAGG', 'CTTC', 'ACCT', 'TGCA', 'CCTG', 'CAAA', 'TGCC', 'ACTT', 'TAAT', 'CTTG', 'CGGC', 'GGCC', 'TCCT', 'ATCC', 'ACAG', 'TGAA', 'TTTG', 'ACAA', 'TGTC', 'TGGA', 'CTGC', 'GCTC', 'AGGA', 'TGCT', 'GCGC', 'GCGG', 'AAAG', 'GCTG', 'GCCG', 'ACCA', 'CTCC', 'CACG', 'CATA', 'GGGA', 'CGAG', 'CCCC', 'GGTG', 'AAGT', 'CCAC', 'AACA', 'AATA', 'CGAC', 'GGAA', 'TACC', 'AGTT', 'GTGG', 'CGCA', 'GGGG', 'GAGA', 'AGCC', 'ACCG', 'CCAT', 'AGAC', 'GGGT', 'CAGC', 'GATG', 'TTCG']
uniques = fetch_unique_tuples(elements, 3)
print len(uniques)
如果您愿意失去len()的功能,可以轻松转换为生成器


编辑:添加额外的sorted()以使所有元组和最终列表成为alpha。

假设您的意思是M N元组的集合必须成对不同,那么使用图形跟踪“禁止”对似乎是可行的方法

import random
def select_tuples(original, N, M):
  used = {}
  first = random.sample(original, N)
  updateUsed(used, first)
  answer = [first]
  for i in xrange(M):
    notFound = True
    while notFound:
      remaining = set(original)
      thisTuple = []
      for j in xrange(N):
        if not remaining:
          break
        candidate = random.choice(remaining)
        remaining.remove(candidate)
        for adjacent in used[candidate]:
          remaining.remove(adjacent)
      else:
        notFound = False
    answer.append(thisTuple)
    updateUsed(used, thisTuple)
  return answer

def updateUsed(used, selected):
  for x in selected:
    if x not in used:
      used[x] = []
    for y in selected:
      if y != x:
        used[x].append(y)

我想这有点像
O(MN^2)
。我怀疑你能做得比这更好,因为你正在引入的
N*(N-1)/2
禁止对中的每一个
M
元组都不能再使用了。

你能举个例子吗?你定义了一个变量
N
,但这里有两个变量:集合中元素的数量,以及您希望查找的唯一元组的数量。算法的性能可能取决于这两个变量。@MarkByers:谢谢你指出这一点。实际上还有另一个维度
m
——变量的数量
m
通常在2到5之间-因此,考虑到
n
的潜在大小,这可能不是太大的因素。是否要求结果是随机选择?@VaughnCato:不,要求是结果由不同的n元组组成(假设我们一次选择n个元素)。我已经在上面提供了相异性(布尔度量)的定义。感谢您尝试一下。然而,你提出的解决方案并不完全正确。如果您还记得,异类的定义是,在两个选定的N元组之间,没有任何2元组(对)应该是相同的[记住(x,y)(y,x)]。上面的代码选择元组,如[('CAGG','CTTC','ACCT'),('CAGG','CTTC','TGCA'),…]。这些并不不同,因为这对('CAGG','CTTC')是多个选定的3元组的共同点。哦,好吧,我没有理解。我会考虑一下,然后带着一个新的解决方案回来。是的,它有点慢-但至少,它似乎在工作。我以后会担心“优化”。需要运行一些检查以确保其按预期工作。在pypy上运行以使其运行更快。@hexparrot是的,我知道。因为代码代码中的C++方法+ 1,所以代码应该更快。我对速度上的差异感到惊讶。试图了解速度提升的来源(不太熟悉itertools),请阅读我对luke解决方案的最新评论;与其说是工具,不如说是迭代次数。
def fetch_unique_tuples(original_set, tuple_size):
    from itertools import combinations

    good = []
    used = []
    for i in combinations(original_set,tuple_size):
        lst = list([tuple(sorted(j)) for j in combinations(i,2)])
        if not any(l in used for l in lst):
            used.extend(lst)
            good.append(tuple(sorted(i)))
    return sorted(good)

elements = ['CAGG', 'CTTC', 'ACCT', 'TGCA', 'CCTG', 'CAAA', 'TGCC', 'ACTT', 'TAAT', 'CTTG', 'CGGC', 'GGCC', 'TCCT', 'ATCC', 'ACAG', 'TGAA', 'TTTG', 'ACAA', 'TGTC', 'TGGA', 'CTGC', 'GCTC', 'AGGA', 'TGCT', 'GCGC', 'GCGG', 'AAAG', 'GCTG', 'GCCG', 'ACCA', 'CTCC', 'CACG', 'CATA', 'GGGA', 'CGAG', 'CCCC', 'GGTG', 'AAGT', 'CCAC', 'AACA', 'AATA', 'CGAC', 'GGAA', 'TACC', 'AGTT', 'GTGG', 'CGCA', 'GGGG', 'GAGA', 'AGCC', 'ACCG', 'CCAT', 'AGAC', 'GGGT', 'CAGC', 'GATG', 'TTCG']
uniques = fetch_unique_tuples(elements, 3)
print len(uniques)
import random
def select_tuples(original, N, M):
  used = {}
  first = random.sample(original, N)
  updateUsed(used, first)
  answer = [first]
  for i in xrange(M):
    notFound = True
    while notFound:
      remaining = set(original)
      thisTuple = []
      for j in xrange(N):
        if not remaining:
          break
        candidate = random.choice(remaining)
        remaining.remove(candidate)
        for adjacent in used[candidate]:
          remaining.remove(adjacent)
      else:
        notFound = False
    answer.append(thisTuple)
    updateUsed(used, thisTuple)
  return answer

def updateUsed(used, selected):
  for x in selected:
    if x not in used:
      used[x] = []
    for y in selected:
      if y != x:
        used[x].append(y)