我有一个算法可以创建一个图形,其中包含以最短图形路径形式编码的3位二进制字符串的所有表示形式,其中路径中的偶数表示0,而奇数表示1:
from itertools import permutations, product
import networkx as nx
import progressbar
import itertools
def groups(sources, template):
func = permutations
keys = sources.keys()
combos = [func(sources[k], template.count(k)) for k in keys]
for t in product(*combos):
d = {k: iter(n) for k, n in zip(keys, t)}
yield [next(d[k]) for k in template]
g = nx.Graph()
added = []
good = []
index = []
# I create list with 3-bit binary strings
# I do not include one of the pairs of binary strings that have a mirror image
list_1 = [list(i) for i in itertools.product(tuple(range(2)), repeat=3) if tuple(reversed(i)) >= tuple(i)]
count = list(range(len(list_1)))
h = 0
while len(added) < len(list_1):
# In each next step I enlarge the list 'good` by the next even and odd number
if h != 0:
for q in range(2):
good.append([i for i in good if i%2 == q][-1] + 2)
# I create a list `c` with string indices from the list` list_1`, that are not yet used.
# Whereas the `index` list stores the numbering of strings from the list` list_1`, whose representations have already been correctly added to the `added` list.
c = [item for item in count if item not in index]
for m in c:
# I create representations of binary strings, where 0 is 'v0' and 1 is 'v1'. For example, the '001' combination is now 'v0v0v1'
a = ['v{}'.format(x%2) for x in list_1[m]]
if h == 0:
for w in range(2):
if len([i for i in good if i%2 == w]) < a.count('v{}'.format(w)):
for j in range(len([i for i in good if i%2 == w]), a.count('v{}'.format(w))):
good.insert(j,2*j + w)
sources={}
for x in range(2):
sources["v{0}".format(x)] = [n for n in good if n%2 == x]
# for each representation in the form 'v0v0v1' for example, I examine all combinations of strings where 'v0' is an even number 'a' v1 'is an odd number, choosing values from the' dobre2 'list and checking the following conditions.
for aaa_binary in groups(sources, a):
# Here, the edges and nodes are added to the graph from the combination of `aaa_binary` and checking whether the combination meets the conditions. If so, it is added to the `added` list. If not, the newly added edges are removed and the next `aaa_binary` combination is taken.
g.add_nodes_from (aaa_binary)
t1 = (aaa_binary[0],aaa_binary[1])
t2 = (aaa_binary[1],aaa_binary[2])
added_now = []
for edge in (t1,t2):
if not g.has_edge(*edge):
g.add_edge(*edge)
added_now.append(edge)
added.append(aaa_binary)
index.append(m)
for f in range(len(added)):
if nx.shortest_path(g, aaa_binary[0], aaa_binary[2]) != aaa_binary or nx.shortest_path(g, added[f][0], added[f][2]) != added[f]:
for edge in added_now:
g.remove_edge(*edge)
added.remove(aaa_binary)
index.remove(m)
break
# Calling a good combination search interrupt if it was found and the result added to the `added` list, while the index from the list 'list_1` was added to the` index` list
if m in index:
break
good.sort()
set(good)
index.sort()
h = h+1
表示来自added
:
[[0, 2, 4], [0, 2, 1], [2, 1, 3], [1, 3, 5], [0, 3, 6], [3, 0, 7]]
所以这些是3位二进制字符串的表示:
[[0, 0, 0], [0, 0, 1], [0, 1, 1], [1, 1, 1], [0, 1, 0], [1, 0, 1]]
在步骤h = 0
中找到前4个子列表,并在步骤h = 1
中添加了最后两个子列表。
当然,正如您所看到的,镜像字符串没有反射,因为在无向图中没有这种需要。
图表:
上述解决方案创建了一个最小的图形,并具有唯一的最短路径。这意味着二进制字符串的一个组合在图中仅以最短路径的形式具有一个表示。因此,给定路径的选择是给定二进制序列的单指示。
现在我想在for m in c
循环上使用多处理,因为查找元素的顺序在这里无关紧要。
我尝试以这种方式使用多处理:
from multiprocessing import Pool
added = []
def foo(i):
added = []
# do something
added.append(x[i])
return added
if __name__ == '__main__':
h = 0
while len(added)<len(c):
pool = Pool(4)
result = pool.imap_unordered(foo, c)
added.append(result[-1])
pool.close()
pool.join()
h = h + 1
多处理在while循环中进行,而在foo
函数中进行
added
列表已创建。在循环中的每个后续步骤h
中,列表added
应按后续值递增,并且当前列表added
应在函数foo
中使用。是否可以在循环的每个后续步骤中将列表的当前内容传递给函数?因为在上面的代码中,foo
函数每次都从头开始创建added
列表的新内容。怎么解决这个问题?
结果导致不良后果:
[[0, 2, 4], [0, 2, 1], [2, 1, 3], [1, 3, 5], [0, 1, 2], [1, 0, 3]]
因为对于这样的图形,节点和边缘,nx.shortest_path (graph, i, j) == added[k]
的每个最终节点i, j
都不满足added[k] for k in added list
的条件。
元素h = 0
的{{1}}的位置是好的,而在[0, 2, 4], [0, 2, 1], [2, 1, 3], [1, 3, 5]
步骤中添加的元素,即h = 1
显然是在不影响上一步的元素的情况下找到的
如何解决这个问题?
我意识到这是一种顺序算法,但我也对部分解决方案感兴趣,即甚至算法的部分并行处理。例如,循环中[0, 1, 2], [1, 0, 3]
的步骤顺序运行,但h
循环是多处理的。或者其他部分解决方案将改进整个算法以获得更大的组合。
我将非常感谢在我的算法中显示和实现多处理的一些想法。
答案 0 :(得分:7)
我认为您不能像现在这样并行化代码。您想要并行化的部分,for m in c
循环访问三个全局good
,added
和index
以及图g
的列表本身。您可以使用multiprocessing.Array
作为列表,但这会破坏整个并行化点,因为multiprocessing.Array
(docs)已同步,因此进程实际上并不是并行运行。 / p>
因此,代码需要重构。我首选的并行算法方法是使用一种生产者/消费者模式
在这种情况下,1.
将成为list_1
,count
以及h == 0
案例的设置代码。之后,您将构建&#34; 作业订单&#34;的队列,这将是c
列表 - &gt;将该列表传递给一群工人 - &gt;得到结果并汇总。问题是for m in c
循环的每次执行都可以访问全局状态,并且每次迭代后全局状态都会发生变化。这在逻辑上意味着您无法并行运行代码,因为第一次迭代会更改全局状态并影响第二次迭代的作用。也就是说,根据定义,顺序算法。您不能(至少不容易)并行化迭代构建图形的算法。
您可以使用multiprocessing.starmap
和multiprocessing.Array
,但这并不能解决问题。您仍然可以在所有进程之间共享图形g
。因此整个事情需要以这样的方式进行重构:for m in c
循环上的每次迭代都独立于该循环的任何其他迭代或必须更改整个逻辑,以便1}}循环不需要开始。
<强>更新强>
我原以为你可以通过以下更改将算法转向稍微不那么顺序的版本。我非常确定代码已经做了类似的事情,但代码对我来说有点过于密集,图形算法并不是我的专长。
目前,对于新的三元组(例如for m in c
),您将在现有图形中生成所有可能的连接点,然后将新的三元组添加到图形中,并根据测量最短路径消除节点。这需要检查图形上的最短路径和修改,这会阻止并行化。
注意:以下是关于如何重构代码的相当粗略的大纲,我没有对此进行测试或在数学上验证它实际上是否正常工作
注2:在下面的讨论中'101'
(注意引号'101'
是二进制字符串,''
和'00'
也是{{1} },'1'
,1
等(不带引号)是图表中的顶点标签。
如果您要在现有图表上进行某种子字符串搜索,我将使用第一个三元组作为示例。初始化
0
4
,这将是(job_queue
,'000'
,0
) - 这是微不足道的,无需检查任何内容因为当你开始时图形为空,所以最短路径是你插入的最短路径。此时,您还拥有2
,4
,'011'
和相反('001'
和'010'
的部分路径,因为图表是无向的)。我们将利用现有图表包含'110'
中剩余三元组的子解决方案这一事实。让我们说下一个三元组是'001'
,你迭代二进制字符串job_queue
或'010'
'010'
的路径/顶点 - &gt;继续list('010')
的路径/顶点 - &gt;继续'0'
的路径/顶点存在,则表示您已完成,无需添加任何内容(这实际上是一个失败案例:'01'
不应再出现在作业队列中了,因为它已经解决了。)第二个项目符号点将失败,因为图表中不存在'010'
。在这种情况下将'010'
插入到图表中的节点'01'
,并将其连接到三个'1'
节点之一,我不认为哪个但你必须记录它所连接的那个,让我们说你选择了1
。该图现在看起来像
even
完成路径的最佳边缘是0
(标有星号),以获取 0 - 2 - 4
\ *
\ *
\*
1
的路径1 - 2
,这是最大化编码的三元组数量的路径,如果边0 - 1 - 2
被添加到图表中。如果您添加'010'
,则只会对1-2
三元组进行编码,其中1-4
会对'010'
进行编码,但也会对1 - 2
和'010'
进行编码。
顺便说一句,让我们假装你首先将'001'
连接到'100'
,而不是1
(第一个连接是随机选择的),你现在有了一个图表
2
您可以将0
与 0 - 2 - 4
|
|
|
1
或1
联系起来,但是您再次获得一个图表,该图表会对4
中剩余的最大三元组进行编码。
那么如何检查潜在新路径编码的三元组数?您可以相对轻松地检查,更重要的是检查可以并行完成,而无需修改图0
,对于3位字符串,并行节省的费用并不大,但是对于32位字符串,它们将是。这是它的工作原理。
job_queue
生成所有可能的完整路径 - &gt; g
。0-1
中。
(0-1-2), (0-1-4)
解决了另外两个三元组job_queue
和(0-1-2)
。'001' (4-2-1) or (2-0-1)
只解决了三重'100' (1-0-2) or (1-2-4)
,即本身解决(0-1-4)
中剩余最多三元组的边/路径是最佳解决方案(我没有证明这一点)。
您在上面运行'010'
并行将图表复制到每个工作人员。因为您不修改图形,只检查它解决了多少三元组,您可以并行执行此操作。每个工人都应该有一个类似
job_queue
2.
可以是check_graph(g, path, copy_of_job_queue):
# do some magic
return (n_paths_solved, paths_solved)
或path
,(0-1-2)
应该是(0-1-4)
上剩余路径的副本。对于K工作者,您可以创建队列的K个副本。工作池完成后,您知道哪条路径copy_of_job_queue
或job_queue
解决了最多的三元组。
您然后添加该路径并修改图表,并从作业队列中删除已解决的路径。
RINSE - REPEAT直到作业队列为空。
上面有一些明显的问题,如果你正在处理大比特空间,比如32比特,那么你做了很多复制和循环(0-1-2)
的问题,然后{ {1}}很长,所以你可能不想继续复制给所有工人。
对于上面的并行步骤(2.),您可能希望(0-1-4)
实际上是job_queue
,其中键是三元组,比如job_queue
,并且值是布尔值如果该三元组已经在图中编码,则说明该标志。
答案 1 :(得分:3)
是否有更快的算法?看着这两棵树,(我用二进制表示数字,使路径更容易看到)。现在要将此节点从14个节点减少到7个节点,是否可以将所需的路径从一个树层叠到另一个树上?您可以将任何边缘添加到其中一个树中,只要它不将节点与其祖先连接即可。
_ 000
_ 00 _/
/ \_ 001
0 _ 010
\_ 01 _/
\_ 011
_ 100
_ 10 _/
/ \_ 101
1 _ 110
\_ 11 _/
\_ 111
你能看到例如连接01到00,类似于用01替换树0的头部,因此在一个边缘你添加了100,101和110 ..