Python Combinatorics,第2部分

时间:2010-11-10 15:10:51

标签: python combinatorics

这是Combinatorics in Python

的后续问题

如果你的结构为:

,我有一棵树或有向无环图

alt text

其中r是根节点,p是父节点,c是子节点,b是假设分支。根节点不直接链接到父节点,它只是一个引用。

我在限制条件下找到分支的所有组合:

  1. 如果这些父节点不共享根节点,则可以由任意数量的父节点共享子节点。
  2. 有效组合不应是其他组合的子集
  3. 在此示例中,在约束条件下只能使用两种有效组合:

    combo[0] = [b[0], b[1], b[2], b[3]]
    combo[1] = [b[0], b[1], b[2], b[4]]
    

    数据结构如b是一个分支对象列表,它具有属性r,c和p,例如:

    b[3].r = 1
    b[3].p = 3
    b[3].c = 2
    

4 个答案:

答案 0 :(得分:3)

这个问题可以在Python中轻松优雅地解决,因为有一个名为“itertools”的模块。

假设我们有HypotheticalBranch类型的对象,它们具有属性r,p和c。就像你在帖子中描述的那样:

class HypotheticalBranch(object):
  def __init__(self, r, p, c):
    self.r=r
    self.p=p
    self.c=c
  def __repr__(self):
    return "HypotheticalBranch(%d,%d,%d)" % (self.r,self.p,self.c)

因此,您的假设分支就是

b=[ HypotheticalBranch(0,0,0),
  HypotheticalBranch(0,1,1),
  HypotheticalBranch(1,2,1),
  HypotheticalBranch(1,3,2),
  HypotheticalBranch(1,4,2) ]

返回所有可能的分支组合列表的神奇函数可以这样编写:

import collections, itertools

def get_combos(branches):
  rc=collections.defaultdict(list)
  for b in branches:
    rc[b.r,b.c].append(b)
  return itertools.product(*rc.values())

确切地说,此函数返回一个迭代器。通过迭代获取列表。这四行代码将打印出所有可能的组合:

for combo in get_combos(b):
  print "Combo:"
  for branch in combo:
    print "  %r" % (branch,)

该程序的输出是:

Combo:
  HypotheticalBranch(0,1,1)
  HypotheticalBranch(1,3,2)
  HypotheticalBranch(0,0,0)
  HypotheticalBranch(1,2,1)
Combo:
  HypotheticalBranch(0,1,1)
  HypotheticalBranch(1,4,2)
  HypotheticalBranch(0,0,0)
  HypotheticalBranch(1,2,1)

......这正是你想要的。

那么脚本的作用是什么?它为每个组合(根节点,子节点)创建所有假设分支的列表。然后它产生这些列表的乘积,即每个列表中一个项目的所有可能组合。

我希望我能得到你真正想要的东西。

答案 1 :(得分:1)

您的第二个约束意味着您需要最大组合,即所有长度等于最大组合的组合。

我首先遍历“b”结构并创建一个名为“c”的结构来存储所有分支,以存储到每个子节点的所有分支,并由到达它的根节点进行分类。

然后构造输出组合,对于每个子项,您可以包含每个非空的根集中的一个条目。算法的顺序(执行时间)将是输出的顺序,这是您可以获得的最佳顺序。

例如,您的“c”结构将如下所示:

c[i][j] = [b_k0, ...]  
--> means c_i has b_k0, ... as branches that connect to root r_j)

对于您提供的示例:

c[0][0] = [0]
c[0][1] = []
c[1][0] = [1]
c[1][1] = [2]
c[2][0] = []
c[2][1] = [3, 4]

使用这种方法对它进行编码应该相当容易。您只需要遍历所有分支“b”并填充“c”的数据结构。然后写一个小的递归函数,遍历“c”中的所有项目。

这是代码(为了测试,我在顶部输入了样本数据):

class Branch:
  def __init__(self, r, p, c):
    self.r = r
    self.p = p
    self.c = c

b = [
    Branch(0, 0, 0),
    Branch(0, 1, 1),
    Branch(1, 2, 1),
    Branch(1, 3, 2),
    Branch(1, 4, 2)
    ]

total_b = 5   # Number of branches
total_c = 3   # Number of child nodes
total_r = 2   # Number of roots

c = []
for i in range(total_c):
  c.append([])
  for j in range(total_r):
    c[i].append([])

for k in range(total_b):
  c[b[k].c][b[k].r].append(k)

combos = []
def list_combos(n_c, n_r, curr):
  if n_c == total_c:
    combos.append(curr)
  elif n_r == total_r:
    list_combos(n_c+1, 0, curr)
  elif c[n_c][n_r]:
      for k in c[n_c][n_r]:
        list_combos(n_c, n_r+1, curr + [b[k]])
  else:
    list_combos(n_c, n_r+1, curr)

list_combos(0, 0, [])

print combos

答案 2 :(得分:1)

这里确实存在两个问题:首先,您需要制定用于解决此问题的算法;其次,您需要实现它(在Python中)。


算法

我假设你想要一个 maximal 分支集合;也就是说,一旦你不能再添加任何分支。如果不这样做,则可以考虑最大集合的所有子集。

因此,对于子节点,我们希望尽可能多地使用分支,但要遵守没有两个父节点共享根的约束。换句话说,来自每个孩子的每个根节点附近最多可能有一个边缘。这似乎表明你想首先在子节点上迭代,然后在根节点的(邻域)上迭代,最后在这些之间的边缘上迭代。这个概念给出了以下伪代码:

for each child node:
    for each root node:
        remember each permissible edge

find all combinations of permissible edges

代码

>>> import networkx as nx
>>> import itertools
>>> 
>>> G = nx.DiGraph()
>>> G.add_nodes_from(["r0", "r1", "p0", "p1", "p2", "p3", "p4", "c0", "c1", "c2"])
>>> G.add_edges_from([("r0", "p0"), ("r0", "p1"), ("r1", "p2"), ("r1", "p3"),
...                   ("r1", "p4"), ("p0", "c0"), ("p1", "c1"), ("p2", "c1"),
...                   ("p3", "c2"), ("p4", "c2")])
>>> 
>>> combs = set()
>>> leaves = [node for node in G if not G.out_degree(node)]
>>> roots = [node for node in G if not G.in_degree(node)]
>>> for leaf in leaves:
...     for root in roots:
...         possibilities = tuple(edge for edge in G.in_edges_iter(leaf)
...                               if G.has_edge(root, edge[0]))
...         if possibilities: combs.add(possibilities)
... 
>>> combs
set([(('p1', 'c1'),), 
     (('p2', 'c1'),), 
     (('p3', 'c2'), ('p4', 'c2')), 
     (('p0', 'c0'),)])
>>> print list(itertools.product(*combs))
[(('p1', 'c1'), ('p2', 'c1'), ('p3', 'c2'), ('p0', 'c0')), 
 (('p1', 'c1'), ('p2', 'c1'), ('p4', 'c2'), ('p0', 'c0'))]

上述似乎有效,但我还没有测试过。

答案 3 :(得分:0)

对于每个孩子c,假设父母p(c),根r(p(c)),从p(c)中为r(p(c))中的每个根r选择恰好一个父p(如r是p)的根,并且在组合中包括b,其中b将p连接到c(假设只有一个这样的b,意味着它不是多图)。组合的数量将是每个孩子假设连接到每个根的父母数量的乘积。换句话说,该组合的大小将等于所有子根对的假设连接的乘积。在你的例子中,所有这样的子根对只有一个路径,除了r1-c2,它有两个路径,因此组合集的大小是两个。

这满足了无组合作为另一个子集的约束,因为通过为每个子节点的每个根选择恰好一个父节点,我们最大化连接数。随后添加任何边缘b将导致其根连接到其子节点两次,这是不允许的。由于我们只选择一个,所有组合的长度都完全相同。

递归地实现此选择将产生所需的组合。