繁体   English   中英

Python Combinatorics,第2部分

[英]Python Combinatorics, part 2

这是Python中Combinatorics的后续问题

如果您将使用以下结构,我有一棵树或有向无环图:

替代文字

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

我在寻找约束条件下的所有分支组合时感到非常紧张:

  1. 如果这些父节点不共享根节点,则可以由任意数量的父节点共享子节点。
  2. 有效组合不应是另一种组合的子集

在此示例中,约束下只能有两种有效组合:

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

这个问题可以通过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)

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

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

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

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

我将通过首先遍历“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

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


算法

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

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

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'))]

上面似乎有用,虽然我还没有测试过。

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

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

递归地实现该选择将产生期望的组合。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM