是否有一种规范的方式来表达一个由根函数树组成的函数?
这是一个具体的例子,我的意思是“功能树的组成”。获取一个根节树,其节点由函数标记,如下所示:
节点上的每个函数都是其子节点上函数的组合。与树相关联的函数本身就是组合
F = a0(b0(c0(e0, e1, e2)), b1(d0(f0), d1(g0, g1)))
更明确地说,F
是6个参数的函数,由叶子上的函数计算:
F(x0, ... , x5) == a0(b0(c0(e0(x0), e1(x1), e2(x2))),
b1(d0(f0(x3)), d1(g0(x4), g1(x5))))
一般问题
T
,以及与L
节点对应的函数列表T
,是否有规范方式来编写函数参数F
和T
的{{1}}返回L
根据树L
构建的函数的组合?通过这种方式,组合的“布线” - 树T
- 与其内部“组件” - 列表T
分开。 “规范”解决方案应特别包括L
和T
的表示,这些表示自然适应此问题。
我怀疑这个问题在函数式编程语言中有一个简单的解决方案,但理想情况下我希望有一个动态类型命令式语言的解决方案,如 Python ,类似
L
与此同时,我想出了自己的解决方案(发布在下面)。
虽然我对其经济和概念简单性感到满意,但我仍然对其他本质上不同的方法感兴趣,特别是那些利用Python中缺乏或支持不足的另一种语言的优势的方法。
使用适当的数据结构 - 基本上不会重现所需的输出! - 功能编程习惯用法应该能够实现非常短的解决方案。
答案 0 :(得分:2)
是的,所以这对我来说听起来很有趣。所以我试了一下,这就是结果。
class Node(object):
def __init__(self, parents, fn):
self.parents = parents
self.fn = fn
def get_number_of_args(self):
if not self.parents:
return 1
if not hasattr(self, '_cached_no_args'):
self._cached_no_args = sum(
parent.get_number_of_args() for parent in self.parents
)
return self._cached_no_args
def compose(self):
if not self.parents:
def composition(*args):
return self.fn(*args)
return composition
fns = []
fns_args = []
for parent in self.parents:
fns.append(parent.compose())
fns_args.append(parent.get_number_of_args())
number_of_args = sum(fns_args)
length = len(self.parents)
def composition(*args):
if len(args) != number_of_args:
raise TypeError
sub_args = []
last_no_args = 0
reached_no_args = 0
for i in range(length):
fn = fns[i]
no_args = fns_args[i]
reached_no_args += no_args
args_cut = args[last_no_args: reached_no_args]
sub_call = fn(*args_cut)
sub_args.append(sub_call)
last_no_args = no_args
return self.fn(*sub_args)
return composition
您没有指定实现树结构的方式,因此我将节点和函数合并为一个结构(您可以自己进行映射)。现在用法:
>>> def fn(x):
... return x
>>> def fn2(x):
... return 1
>>> def add(x, y):
... return x + y
>>> n1 = Node([], fn)
>>> n2 = Node([], fn2)
>>> n3 = Node([n1, n2], add)
>>> fn = n3.compose()
>>> print(fn(5, 7))
6
正如所料。随意测试它(我实际上没有在更深的树上尝试过它),如果你发现任何问题,请告诉我。
答案 1 :(得分:2)
我们定义一个函数treecomp
,它通过取L
和{{1}来返回根据根树T
构造的函数列表L
的组合。作为单独的参数:
T
与目前提出的其他解决方案不同,它不会因不必要的簿记而变得复杂,例如跟踪叶子或参数的数量(除了装饰者更好地处理)。
F = treecomp(T, L)
treecomp
的简单实现如下:它只生成树组合的符号(字符串)表达式。对参数的评估只是将它们插入并评估结果表达式的问题。
这个天真的想法可以使用相当基本的数据结构来实现:树和函数的列表,以及功能标记树的简单类。 (Namedtuples也可以。但是,通过使用具有特殊比较方法的类,我们可以编写更多语义自然代码。)
作为 flat 列表的根树的最经济的编码是"节点地址的列表。"在对@JeD的评论中,我暗示这可以通过" drawing"树:
treecomp
此处T = [(0,),
(0, 0),
(0, 0, 0),
(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 0, 2),
(0, 1),
(0, 1, 0),
(0, 1, 0, 0),
(0, 1, 1),
(0, 1, 1, 0), (0, 1, 1, 1)]
是与(0,)
对应的节点,a0
是与(0, 0)
对应的节点,b0
是与(0, 1)
对应的节点等等,就像书中各节的编号一样。最长的(或#34;最高的#34;)元组是叶子。
然后可以将函数列表b1
作为与L
中的节点顺序匹配的列表给出:
T
由于树L = [a0, b0, c0, e0, e1, e2, b1, d0, f0, d1, g0, g1]
的节点由T
中的函数标记,因此为此建立数据结构会很方便。我们定义一个类来记录节点的地址和标记它的函数的文字名称;它的方法实现了相对于树的部分排序的比较(其中根是最小元素):
L
class SymbNode:
'''Class that records a node's address and symbol.'''
def __init__(self, addr, symb):
self.addr = addr
self.symb = symb
def __len__(self): # how "high" a node is above the root
return len(self.addr)
def _compare(self, other, segment):
return self.addr == other.addr[:segment]
def __le__(self, other):
return self._compare(other, segment=len(self))
def begets(self, other):
return self._compare(other, segment=-1)
的简单两步机制在下面实现。通过规范化SymbNode列表的顺序,我们可以简单地通过"剥离"来构建符号表达式。当我们向上移动树的每一层时。
treecomp
由于from functools import partial
from operator import attrgetter
def treecomp(tree, funcs):
'''Returns the composition of a tree of functions.'''
symbtree = makesymbtree(tree, funcs)
symbexp = makesymbexp(symbtree)
return partial(evalsymbexp, symbexp=symbexp)
FUNC_CALL = '{func}({{}})'
def makesymbtree(tree, funcs):
'''Returns the symbolic expression of a tree composition.'''
symbols = [FUNC_CALL.format(func=func.__name__) for func in funcs]
symbtree = sorted((SymbNode(*x) for x in zip(tree, symbols)),
key=attrgetter('addr'))
symbtree.sort(key=len)
return symbtree
def makesymbexp(symbtree):
root = symbtree[0]
if len(symbtree) == 1: # symbtree is a leaf node
return root.symb
symbargs = [makesymbexp(subsymbtree(symbtree, root=node))
for node in symbtree if root.begets(node)]
return root.symb.format(','.join(symbargs))
def subsymbtree(symbtree, root):
subsymbtree = [node for node in symbtree if root <= node]
return subsymbtree
ARGS = 'args[{idx}]'
def evalsymbexp(symbexp, *args):
'''Returns the evaluation of a symbolic expression on arguments.'''
argnames = [ARGS.format(idx=str(n)) for n, _ in enumerate(args)]
return eval(symbexp.format(*argnames))
的划分,我们只需要验证函数treecomp
生成正确的符号表达式,并且函数makesymbexp
正确评估符号表达式。
(基本上是单行)函数evalsymbexp
应该采用字符串模板并插入参数名称evalsymbexp
,'args[0]'
等,然后评估结果。显然这样做。
对于'args[1]'
,我们可以通过检查其在某些测试数据上的输出来获得对其正确性的信任,代替正式证明(我们避开)。例如,以下功能:
makesymbexp
如上所述def D(x): return 2*x
def M(x): return -x
def S(*xs): return sum(xs)
a0 = S
b0, b1 = D, S
c0, d0, d1 = S, D, S
e0, e1, e2, f0, g0, g1 = D, M, D, M, D, M
和T
,我们可以检查我们是否获得了正确的符号表达式:
L
确实会产生字符串
makesymbexp(makesymbtree(T, L))
要检查'S(D(S(D({}),M({}),D({}))),S(D(M({})),S(D({}),M({}))))'
到treecomp
的委派,作为部分功能,我验证了
evalsymbexp
同意
的价值F = treecomp(T, L)
F(x0, x1, x2, x3, x4, x5)
在1000个随机样本中a0(b0(c0(e0(x0), e1(x1), e2(x2))), b1(d0(f0(x3)), d1(g0(x4), g1(x5))))
,...,x0
从-100到100之间的整数绘制。
答案 2 :(得分:1)
这是我做过的一个简单例子:
from collections import deque
class Node(object):
def __init__(self, children, func):
self.children = children
if children:
self.leaf_count = sum(c.leaf_count for c in children)
else:
self.leaf_count = 1 # It is a leaf node.
self.func = func
def __call__(self, *args):
if not self.children:
assert len(args) == 1, 'leaf can only accept 1 argument.'
return self.func(*args) # Base case.
d_args = deque(args)
func_results = []
for child in self.children:
f_args = [d_args.popleft() for _ in xrange(child.leaf_count)]
func_results.append(child(*f_args))
assert not d_args, 'Called with the wrong number of arguments'
return self.func(*func_results)
基本上,&#34;技巧&#34;是跟踪每个节点有多少叶节点,因为叶节点的数量是它预期接受的参数数量。
一些实施说明:
我使用collections.deque
拉出正确的数字args传递给孩子。这是为了提高效率,因为deque
允许我们在O(1)时间内获取这些参数。否则,我们会留下类似的东西:
for child in self.children:
f_args = args[:child.leaf_count]
args = args[child.leaf_count:]
func_results.append(child(*args))
但是每次传球都是O(N)时间。对于小树,它可能并不重要。对于大树来说,它可能很重要: - )。
我还为leaf_count使用了一个静态成员,这意味着你需要从leaf到root构建你的树。当然,您可以根据问题限制使用不同的策略。例如您可以构建树,然后在构建树之后将leaf_count填充为单个遍,然后再开始评估函数,或可以将leaf_count
转换为函数({{1} })计算每次调用时的叶子(大树可能会花费很多)。
现在有些测试...我能想到的最简单的情况是叶节点是否都与身份函数相关联,然后非叶节点是一个添加输入值的函数。在这种情况下,结果应始终为输入值的总和:
@property
答案 3 :(得分:1)
由于你想要解耦函数和树,你可以这样做:
#root=RootNode, funcs=Map from Node to function, values=list of inputs
#nodes need isLeaf and children field
def Func(root,funcs,values):
#check if leaf
if root.isLeaf:
#removes value from list
val=values.pop(0)
#returns function of root
#(can be identity if you just want to input values)
return funcs[root](val)
#else we do a recursive iteration:
else:
nextVals=[]
#for each child
for child in root.children:
#call this function->DFS for roots, removes values that are used
nextVals.append(Func(child,funcs,values))
#unpack list and call function
return funcs[root](*nextVals)
这是一个例子:
class Node:
children=[]
isLeaf=False
def __init__(self,isLeaf):
self.isLeaf=isLeaf
def add(self,n):
self.children.append(n)
def Func(root,funcs,values):
#check if leaf
if root.isLeaf:
#removes value from list
val=values.pop(0)
#returns function of root
#(can be identity if you just want to input values)
return funcs[root](val)
#else we do a recursive iteration:
else:
nextVals=[]
#for each child
for child in root.children:
#call this function->DFS for roots, removes values that are used
nextVals.append(Func(child,funcs,values))
#unpack list and call function
return funcs[root](*nextVals)
def sum3(a,b,c):
return a+b+c
import math
funcMap={}
funcMap[root]=sum3
root=Node(False)
layer1=[Node(True) for i in range(3)]
for i in range(3):
root.add(layer1[i])
funcMap[layer1[i]]=math.sin
print Func(root,funcMap,[1,2,3])
print math.sin(1)+math.sin(2)+math.sin(3)
返回相同的值 (使用python 2.7)
答案 4 :(得分:0)
这将是面向对象编程(OOP)的理想选择。例如,您可以使用这三个类
对于使用树结构,递归方法通常更容易。
或者,您也可以通过在元组中嵌入元组来直接构建递归结构。例如
n1 = ( 'L', 'e0' )
n2 = ( 'L', 'e1' )
n3 = ( 'L', 'e2' )
n4 = ( 'N', 'c0', n1, n2, n3 )
n5 = ( 'N', 'b0', n4 )
这不是您的完整树,但可以轻松扩展。只需使用print (n5)
即可查看结果。
这不是唯一的方法,可能会有变化。对于每个元组,第一个项目是一个字母,指定它是否是叶子&#34; L&#34;或节点&#34; N&#34; - 这将使递归函数更容易。第二项是名称(取自您的图纸)。对于节点,其他项是子节点。
(n.b。我曾经在元组中使用&#34;元组&#34;来实现Huffmann编码算法 - 它也适用于树结构)。