One-line Tree In Python
gist.github.com
gist.github.com
You make a good point about this being written by a known intelligent person and seen by many others who didn't see the bugs, but I think it's ultimately a lesson in thinking about semantics rather than behavior. This code relies too much on the behavior of defaultdict rather than just what such a thing means. As long as what it means is kept in mind I think it should be safe.
But that's just, like, my opinion, man.
I've seen other defaultdict bugs -- this is just the case with the highest eyeball-count-times-attention product I can point to.
defaultdict(lambda: defaultdict(int))
That allows me to organically build dictionaries (mainly for stat building) using operands like +=. x = collections.defaultdict(collections.Counter)
x['foo']['bar'] += 1
X['foo'].most_common(10)
Etc. def tree; Hash.new {|h, k| h[k] = tree }; end
t = tree
t[:foo][:bar] = "foobar" # => {:foo=>{:bar=>"foobar"}}
Probably more idiomatic to do it as a class, though. class Tree < Hash
def initialize
super {|h,k| h[k] = Tree.new }
end
end
Btw. How do you post nicely formatted code?edit - Thanks!
As for code in comments, see: http://news.ycombinator.com/formatdoc
A little known fact is that it works in C++ STL too as long as the objects in your containers have default constructors that make sense. So a map<int, map<int, value_type> > does what you expect when you try: my_map[12][3] = some_value;
Also, I don't think you can have an infinitely expanding std::map in the same way, because all the key/value types have to get rolled into the type signature of the top level map. It's not that hard to write something yourself to achieve it, but it's not going to be the one-liner that it is in Python.
And you may be less cynical, but I've had to explain this sort of thing to a huge number of C++ programmers, most of whom write STL code that looks like Java, with explicit initialization of all the intermediate containers.
std::map<std::string, int> dict;
dict["one"] = 1;
dict["two"] = 2;
dict["three"] = 3;
If using operator[] on a key did not implicitly mean "create an entry for this key if it is not there," the above would not work. Rather, you would have to write the above as: std::map<std::string, int> dict;
dict.insert(std::make_pair("one", 1));
dict.insert(std::make_pair("two", 2));
dict.insert(std::make_pair("three", 3));
The reason being that std::map is purely a library, not a part of the language. It does not "know" that operator[] is actually being used as a part of an assignment. dict["one"] = 1;
without causing this to add "one" to the dictionary: if (dict["one"]) { /* ... */ }
even for a "dict" that is purely a library, not a part of the language.The underlying problem is that C++ calls the same operator[] in both lvalue and rvalue contexts. Which is pretty odd, really, when you think about it; it certainly doesn't generate the same code for indexing into native arrays in lvalue and rvalue contexts. All the other languages distinguish between the lvalue and rvalue contexts. Ruby calls [] or []=, Python calls __getitem__ or __setitem__; Smalltalk has #at: and #at:put:; Common Lisp doesn't have separate names for the two things, but one of them is defined with (defun foo (dict key) ...) and the other is defined with (defun (setf foo) (dict key) ...); in Lua, these are the metamethods __index and __newindex.
So it's sort of true that it's not std::map's fault, but C++'s. But not really. Stroustrup fixed several things about the way templates worked to make STL work better; he should have fixed this one too.
tree = Hash.new { |h, k| h[k] = Hash.new(&h.default_proc) }
(explanation is here: http://matthew.mceachen.us/blog/multi-value-hashes-in-ruby-1...) dicts_all_the_way_down = lambda:defaultdict(dicts_all_the_way_down) do
local mt = {
__index = function(t, k)
t[k] = tree()
return t[k]
end
}
function tree()
return setmetatable({}, mt)
end
end
t = tree()
t.foo.bar = 'foobar' type MTree t = Map t (MTree t)
This version works though. data Tree t = Leaf | Node [(t, Tree t)] Map t (Map t (Map t ...
Try: newtype MTree t = MTree (Map t (MTree t)) users = tree()
users.harold.username = 'hrldcpr'
users.handler.username = 'matthandlersux'(Note though that there is/was a small bug(?) in CPython: http://bugs.python.org/issue14658)
>>> a = tree()
>>> a.__getattr__ = a.__getitem__It errors for you because it is a defaultdict instance and doesn't allow attribute overwrites.
This should work:
class tree(defaultdict):
def __init__(self): defaultdict.__init__(self, tree)
__getattr__ = defaultdict.__getitem__
__setattr__ = defaultdict.__setitem__
Edit: It doesn't work with the simple assignment because of the mentioned bug. Ofc the fix is trivial. See the code from beagle3. class attrdict(defaultdict):
def __getattr__(self, key): return self[key]
def __setattr__(self, key, val): self[key]=val
def tree(): return attrdict(tree)
attrdict is indeed a useful thing to have around. I usually base it off on the built in "dict", but as this example shows, it is useful on top of "defaultdict" as well.Very cool though!
def incr_nestedctr(d, *keys, **kwargs):
"""
>>> a = {}
>>> incr_nestedctr(a, 'a', 'b', 'c', 'd')
{'a': {'b': {'c': {'d': 1}}}}
>>> incr_nestedctr(a, 'a', 'b', 'c', 'd')
{'a': {'b': {'c': {'d': 2}}}}
>>> incr_nestedctr(a, 'a', 'b', 'c', 'd', delta = -4)
{'a': {'b': {'c': {'d': -2}}}}
>>> incr_nestedctr({u'1.0': {u'0': 1, '5': 1}}, '1.0', '5', delta = 2)
{u'1.0': {u'0': 1, '5': 3}}
"""
delta = kwargs.get('delta', 1)
thed = d
for k in keys[:-1]:
thed = thed.setdefault(k, {})
thed.setdefault(keys[-1], 0)
thed[keys[-1]] += delta
return dI've wanted something like that in Python at different times... thanks!
edit: Ha! The Wiki article even has basically the same code:
def hash(): return defaultdict(hash)
i wonder whether it will be made part of more languages as JSON-esque nested objects proliferate in our code and minds.
Python's auto-vivification doens't allow Perl's hap hazard auto-vivification. Perl allows you to say:
my $foo = {};
$foo->{'blah'}[0]->{'bar'}++;
And after this statement, $foo will refer a hash which has the structure as accessed in the statement.I don't think this can be done for a generalized case in Python. Whether I want is a totally different question.