使用Python dict/set记忆代数表达式

使用Python dict/set记忆代数表达式,python,dictionary,set,Python,Dictionary,Set,我对在自引用类中存储表达式感兴趣,这些表达式可能是重复的。我正在寻找一种方法的建议。我有几个想法 例如: a + b 既然加法是可交换的,如果我们认识到b+a是同一个表达式就好了。在我的应用程序中,识别和折叠这些案例是可以的 我一直在用这个做面条,并想出了几种处理方法: 1) 对所有对象使用set()并定义\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu散列和和\uuuuuuuuuuuuuuuuuuuuuuuu。一个优点是我可以很聪

我对在自引用类中存储表达式感兴趣,这些表达式可能是重复的。我正在寻找一种方法的建议。我有几个想法

例如:

a + b
既然加法是可交换的,如果我们认识到
b+a
是同一个表达式就好了。在我的应用程序中,识别和折叠这些案例是可以的

我一直在用这个做面条,并想出了几种处理方法:

1) 对所有对象使用
set()
并定义
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu散列
\uuuuuuuuuuuuuuuuuuuuuuuu。一个优点是我可以很聪明地使用操作数的异或使检测交换情况变得更简单:

class Expr(object):
    def __init__(self, op, left, right):
        self.op = op
        self.left = left
        self.right = right
    def __hash__(self):
        return hash(op) ^ hash(left) ^ hash(right)
    def __eq__(self, other):
        return self.op == other.op and \
            ((self.left == other.left and self.right == other.right) \
            or (self.left == other.right and self.right == other.left))
这样做的一个缺点是,为了测试给定表达式是否已经存在,我需要构建一个对象来测试成员资格。如果已经在片场里,我会把它扔掉

我相信,正如我写的那样,当你比较两棵树时,这个
\uuuuuueq\uuuu
函数会导致一个完整的树遍历,这是你不想要的。理想情况下,记忆树的目的是通过简单的左/右指针值比较来唯一标识节点

2) 使用
dict()
并使用传递给构造函数的参数元组。为了处理交换操作,我可以为
(“+”、“a”、“b”)
(“+”、“b”、“a”)
安装密钥,并将其分配给同一个对象

key = ('+', left, right)
if key in mydict:
    expr = mydict[key]
else:
    expr = Expr('+', left, right)
    mydict[key] = expr
    mydict[('+', right, left)] = expr

return expr
我不能在这里使用xor方法,因为它不会生成保证唯一的密钥

这里是事情变得更有趣的地方:


如果使用set()/xor方法,则可以检测关联相等性。因此,如果您愿意,
(a+b)+c
a+(b+c)
可以被视为同一个表达式。

您可以通过缓存
Expr
的实例来改进您的解决方案
1
,方法是,参数的顺序不重要:

class CommutativeExpr(object):

    _instances = {}

    def __new__(cls, operator, left, right):
        key = (operator, frozenset([left, right]))
        try:
            return cls._instances[key]
        except KeyError:
            instance = cls._instances[key] = super().__new__(cls)
            return instance

    def __init__(self, operator, left, right):
        self.op = operator
        self.left = left
        self.right = right
        self._hash = hash((operator, frozenset([left, right])))

    def __hash__(self):
        return self._hash

    def __eq__(self, other):
        return self is other
这允许您使用身份检查来验证:

>>> CommutativeExpr('+', 'a', 'b') == CommutativeExpr('+', 'b', 'a')
True
这不支持“关联性”,但是,您也可以通过正确定义
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
并让
进行更复杂的比较来添加该功能



显然,非交换表达式将需要不同的实现,这将是一个不同的类,或者您必须增加此类的复杂性以满足这两种需要。

您真正的问题是什么?在任何情况下,重要的是要知道
Expr
s是否是不可变的。。。如果它们是不可变的,则可以缓存
\uuuuuu散列\uuuuuu
的结果,甚至
\uuuuuu eq\uuuu
。另外:您可以实现
\uuuu new\uuuu
,并使创建实例
Expr('+','a','b')
返回与
Expr('+','b','a')
的现有实例相同的实例。拥有这些“单例”将允许身份比较来检查平等性。。。这就是您想要的吗?您可以使用一个规范化函数,这样每个相同的表达式都会得到相同的结果。例如,在顺序不重要的地方按字母顺序排序。这会增加一些计算,而存储所有表示(例如+ab和+ba)会增加一些内存;对于非交换运算符,将它们存储为元组
(左,右)
,对于交换运算符,将它们存储为冻结集
冻结集([a,b])
。是@Bakuriu表达式应该是不可变的。问题或多或少是1还是2更好。如果是其他的,那也值得考虑。谢谢!出于好奇,您的示例是否真的需要定义一个
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
函数?由于该类没有直接用作键,而您使用的是定制的元组,因此似乎不需要它?@ClintOlsen
left
right
可能是
commissiveexpr
的实例,元组/冻结集散列是从元素计算出来的,是的,如果你想允许嵌套表达式,它是必需的。谢谢你的解释。我认为你的答案解决了这个问题,因为它给了我足够的线索。感谢您提供有关
\uuuuu new\uuuuu
的线索。这种进行对象实习的方法将导致一种更干净的方法。我还没有完全考虑是否可以扩展它来处理关联性,但我认为它没有交换性那么重要。在合并了这种重写
\uuuuu new\uuuuu
的方法后,我发现一个问题是字典中已经存在的实例总是被初始化的(当然是在
\uuuu init\uuuuu()
中)因此,我正在重新计算
self.\u hash
不必要。@ClintOlsen您可以在
\uuu init\uu
内部检查属性是否已设置。例如
self.\u hash=self.\u hash if hasattr(self,“\u hash”)else hash((运算符,frozenset([left,right]))