我有一个对象obj
和许多函数
def func1(obj):
#...
def func2(obj):
#...
def func3(obj):
#...
每个都更改obj
属性的值。
我希望我的输入类似于
obj = MyObject()
obj.attr=22
这应该传递给函数closure()
,该函数计算上述函数的所有可能的应用,意味着func1(func2(obj))
,func3(func1(func1(obj)))
等直到某个停止条件(例如,不超过20个功能组合物。)
输出应该是所有可能输出的列表以及那里的所有路径。因此,如果104
和93
是obj.attr=22
可能的最终输出,那么有两种方式可以到达104
而另一种方式可以到达93
}。然后
print closure(obj)
应该是
[22, 64, 21, 104] #first path to 104 through , func1(obj),func1(func1(obj)), func1(func1(func3(obj)))
[22, 73, 104] #second path to 104 through , func3(obj),func3(func2(obj)),
[22, 11, 93] #the only path to arrive at 94
我怎么能实现这个?正如评论中所建议的那样,这最好用树来完成,但是虽然我尝试了2天但我几乎没有取得任何进展(我是Python /编程的新手)!
我的例子非常简单,我可以直接使用func(obj)
而不是func(22)
,但我需要处理的示例更复杂,我肯定需要使用对象,所以这只是最小的这方面的工作实例。
树可能不是一个完整的n-ary树,因为每个函数应用程序都将包含一个测试,它是否可以应用于obj
的当前状态(属性),在某些情况下还可以测试将obj
的{属性}保持不变。
答案 0 :(得分:3)
这是一个简单的例子,它试图找出一个数字(goal
)是否是前身
在Collatz conjecture中应用规则时的另一个(inital_state
)。
在您的示例中,obj
是state
,[func1, func2, ...]
在我的示例中是functions
。
此版本将返回最终输出的路径,从而最大限度地减少功能应用程序的数量。
您可以通过删除目标测试并在循环结束后返回prev_states
来列出所有状态,而不是搜索。
from collections import deque
def multiply_by_two(x):
return x * 2
def sub_one_div_three(x):
if (x - 1) % 3 == 0:
return (x - 1) // 3
else:
return None # invalid
functions = [multiply_by_two, sub_one_div_three]
# find the path to a given function
def bfs(initial_state, goal):
initial_path = []
states = deque([(initial_state, initial_path)]) # deque of 2-tuples: (state, list of functions to get there)
prev_states = {initial_state} # keep track of previously visited states to avoid infinite loop
while states:
# print(list(map(lambda x: x[0], states))) # print the states, not the paths. useful to see what's going on
state, path = states.popleft()
for func in functions:
new_state = func(state)
if new_state == goal: # goal test: if we found the state, we're done
return new_state, path + [func]
if (new_state is not None and # check that state is valid
new_state not in prev_states): # and that state hasn't been visited already
states.append((new_state, path + [func]))
prev_states.add(new_state) # make sure state won't be added again
else:
raise Exception("Could not get to state")
print(functions)
print(bfs(1, 5))
# prints (5, [<function multiply_by_two at 0x000002E746727F28>, <function multiply_by_two at 0x000002E746727F28>, <function multiply_by_two at 0x000002E746727F28>, <function multiply_by_two at 0x000002E746727F28>, <function sub_one_div_three at 0x000002E7493C9400>]). You can extract the path from here.
答案 1 :(得分:1)
听起来很有趣,让我们把它分解为步骤。
找出可能的功能组合。
评估可能的功能组合。
找出可能的组合
执行此操作的一种方法是使用生成器。它们在内存方面非常高效,因此您最终不会创建一堆值并最大化堆。
那么我们如何获得所有组合。快速搜索Python文档建议使用itertools。所以,让我们这样做。
from itertools import combinations
def comb(fns, n):
return combinations(fns, n)
到目前为止,我们有一个生成器可以为我们提供一系列函数的所有组合(无需替换)。每个提供的组合将是一个函数列表n
大。我们可以简单地依次调用每个,我们可以得到组合结果。
树中的一个级别。我们如何进入下一个级别。好吧,我们可以获得大小为1的所有组合,然后是大小为2的所有组合,依此类推。由于发电机是懒惰的,我们应该能够做到这一点,而不会炸毁解释器。
def combo_tot(fns):
start=1
while (start <= len(fns)):
for c in comb(fns, start):
yield c
start += 1
评估可能的组合
所以现在我们有了所有可能的组合。让我们用它来评估一些东西。
def evalf(fns_to_compose, initval):
v = initval
for fn in fns_to_compose:
v = fn(v)
return v
那就是第二部分。现在您需要做的就是连锁。
def results(fns, init):
return (evalf(fn, init) for fn in combo_tot(fns))
现在只需要尽可能多的结果。
<强>下行强>
与不克隆obj
的任何方法相同。它会改变对象。此外,我们有发电机的开销(可能比for循环稍慢)。我们也有可读性(特别是如果有人不熟悉发电机)。
免责声明:我在手机上输入此内容。可能存在轻微的错别字等。