Patchwork [1,of,4,V2] mercurial: implement diff and join for dicts

login
register
mail settings
Submitter Siddharth Agarwal
Date March 26, 2013, 12:51 a.m.
Message ID <bc4c228833d0a2bcc7c9.1364259080@sid0x220>
Download mbox | patch
Permalink /patch/1198/
State Accepted
Commit a45e44d76c81ad6683c8695bca27fd09e9bbf93e
Headers show

Comments

Siddharth Agarwal - March 26, 2013, 12:51 a.m.
# HG changeset patch
# User Siddharth Agarwal <sid0@fb.com>
# Date 1364258439 25200
#      Mon Mar 25 17:40:39 2013 -0700
# Node ID bc4c228833d0a2bcc7c9a0e3ba4c364fe9157b4e
# Parent  f0d16e97f0b228468807a23fb2b9dc17d5cc6f52
mercurial: implement diff and join for dicts

Given two dicts, diff returns a dict containing all the keys that are present
in one dict but not the other, or whose values are different between the
dicts. The values are pairs of the values from the dicts, with missing values
being represented as an optional argument, defaulting to None.

Given two dicts, join performs what is known as an outer join in relational
database land: it returns a dict containing all the keys across both dicts.
The values are pairs as above, except they aren't compared to see if they're
the same.
Bryan O'Sullivan - March 26, 2013, 4:34 p.m.
On Tue, Mar 26, 2013 at 9:31 AM, Kevin Bullock <
kbullock+mercurial@ringworld.org> wrote:

> I'm a bit uneasy about the names of these, given that they're not closed
> on the domain. I'd expect 'diff' to be something like a set-difference
> resulting in all the k,v pairs in d1 not in d2.
>

That's a good point. I've already pushed the patches, so they'll need
renaming.

Patch

diff --git a/mercurial/dicthelpers.py b/mercurial/dicthelpers.py
new file mode 100644
--- /dev/null
+++ b/mercurial/dicthelpers.py
@@ -0,0 +1,35 @@ 
+# dicthelpers.py - helper routines for Python dicts
+#
+# Copyright 2013 Facebook
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+def _diffjoin(d1, d2, default, compare):
+    res = {}
+    if d1 is d2 and compare:
+        # same dict, so diff is empty
+        return res
+
+    for k1, v1 in d1.iteritems():
+        if k1 in d2:
+            v2 = d2[k1]
+            if not compare or v1 != v2:
+                res[k1] = (v1, v2)
+        else:
+            res[k1] = (v1, default)
+
+    if d1 is d2:
+        return res
+
+    for k2 in d2:
+        if k2 not in d1:
+            res[k2] = (default, d2[k2])
+
+    return res
+
+def diff(d1, d2, default=None):
+    return _diffjoin(d1, d2, default, True)
+
+def join(d1, d2, default=None):
+    return _diffjoin(d1, d2, default, False)
diff --git a/tests/test-dicthelpers.py b/tests/test-dicthelpers.py
new file mode 100644
--- /dev/null
+++ b/tests/test-dicthelpers.py
@@ -0,0 +1,53 @@ 
+from mercurial.dicthelpers import diff, join
+import unittest
+import silenttestrunner
+
+class testdicthelpers(unittest.TestCase):
+    def test_dicthelpers(self):
+        # empty dicts
+        self.assertEqual(diff({}, {}), {})
+        self.assertEqual(join({}, {}), {})
+
+        d1 = {}
+        d1['a'] = 'foo'
+        d1['b'] = 'bar'
+        d1['c'] = 'baz'
+
+        # same identity
+        self.assertEqual(diff(d1, d1), {})
+        self.assertEqual(join(d1, d1), {'a': ('foo', 'foo'),
+                                        'b': ('bar', 'bar'),
+                                        'c': ('baz', 'baz')})
+
+        # vs empty
+        self.assertEqual(diff(d1, {}), {'a': ('foo', None),
+                                        'b': ('bar', None),
+                                        'c': ('baz', None)})
+        self.assertEqual(diff(d1, {}), {'a': ('foo', None),
+                                        'b': ('bar', None),
+                                        'c': ('baz', None)})
+
+        d2 = {}
+        d2['a'] = 'foo2'
+        d2['b'] = 'bar'
+        d2['d'] = 'quux'
+
+        self.assertEqual(diff(d1, d2), {'a': ('foo', 'foo2'),
+                                        'c': ('baz', None),
+                                        'd': (None, 'quux')})
+        self.assertEqual(join(d1, d2), {'a': ('foo', 'foo2'),
+                                        'b': ('bar', 'bar'),
+                                        'c': ('baz', None),
+                                        'd': (None, 'quux')})
+
+        # with default argument
+        self.assertEqual(diff(d1, d2, 123), {'a': ('foo', 'foo2'),
+                                             'c': ('baz', 123),
+                                             'd': (123, 'quux')})
+        self.assertEqual(join(d1, d2, 456), {'a': ('foo', 'foo2'),
+                                             'b': ('bar', 'bar'),
+                                             'c': ('baz', 456),
+                                             'd': (456, 'quux')})
+
+if __name__ == '__main__':
+    silenttestrunner.main(__name__)