444,089 Members | 2,159 Online
Need help? Post your question and get tips & solutions from a community of 444,089 IT Pros & Developers. It's quick & easy.

# trictionary?

 P: n/a i have some code which looks kinda like bin = {} for whatever: for [a, b] in foo: x = 42 - a y = 42 - b if bin.has_key(x): bin[x] += 1 else: bin[x] = 1 for i, j in bin.iteritems(): print i, j now i want to add a second count column, kinda like bin = {} for whatever: for [a, b] in foo: x = 42 - a if bin.has_key(x): bin[x.b] += 1 else: bin[x.b] = 1 bin[x.not b] = 0 for x, y, z in bin.iteritems(): print x, y, z should the dict value become a two element list, or is there a cleaner way to do this? randy Aug 28 '05 #1
15 Replies

 P: n/a Randy Bush wrote: now i want to add a second count column, kinda like bin = {} for whatever: for [a, b] in foo: x = 42 - a if bin.has_key(x): bin[x.b] += 1 else: bin[x.b] = 1 bin[x.not b] = 0 for x, y, z in bin.iteritems(): print x, y, z should the dict value become a two element list, or is there a cleaner way to do this? It would probably help if you explained what the real problem is you're trying to solve. Using a two element list to store a pair of counts has a bad code smell to me. That said, you could write your code something like: bin = {} for whatever: # NOTE: brackets are unnecessary for a, b in foo: x = 42 - a # NOTE: 'in' is generally faster than has_key() if x in bin bin[x][0] += 1 else: bin[x] = [1, 0] # NOTE: extra parens necessary to unpack count list for x, (y, z) in bin.iteritems(): print x, y, z STeVe Aug 28 '05 #2

 P: n/a Steven Bethard wrote: .... Using a two element list to store a pair of counts has a bad code smell to me. .... Why is that? It strikes me as the cleanest way to solve that problem, as long as it's easy enough to figure out what each element really represents. You could name each element, but for a block of code eight lines long, doing anything more descriptive would be too verbose. Adam Aug 28 '05 #3

 P: n/a Adam Tomjack wrote: Steven Bethard wrote: ... Using a two element list to store a pair of counts has a bad code smell to me. ... Why is that? Note that "code smell"[1] doesn't mean that something is actually wrong, just that it might be. In Python, pairs are usually handled with tuples[2], but tuples would be inconvenient in this case, since the first value must be modified. Declaring a class with two attributes as you suggested is often a good substitute, but if the OP's code is really what it looks like, I get another code smell because declaring a class to be used by only 10 lines of code seems like overkill. I also get a code smell from a dict holding two-element lists because I've been writing in Python and answering questions on the Python-list for a couple of years now, and I've never needed one yet. ;-) STeVe [1]http://en.wikipedia.org/wiki/Code_smell [2]http://www.python.org/doc/faq/general.html#why-are-there-separate-tuple-and-list-data-types Aug 29 '05 #4

 P: n/a Steven Bethard wrote: In Python, pairs are usually handled with tuples[2], but tuples would be inconvenient in this case, since the first value must be modified. Instead of modifying the tuple (which you can't do), you can create a new one: a, b = myDict[key] myDict[key] = (a+1, b) It's a bit inefficient, but get it working first, with clear, easy to understand code, then worry about how efficient it is. Declaring a class with two attributes as you suggested is often a good substitute, but if the OP's code is really what it looks like, I get another code smell because declaring a class to be used by only 10 lines of code seems like overkill. But, classes are so lightweight in Python. You can get away with nothing more than: class Data: pass and then you can do things like: myData = Data myData.a = a myData.b = b More likely, I would want to write: class Data: def __init__ (self, a, b): self.a = a self.b = b but I've done the minimilist Data class more than once. It doesn't cost much, and it's often more self-documenting than just a tuple. Aug 29 '05 #6

 P: n/a >> bin = {} for whatever: for [a, b] in foo: x = 42 - a if bin.has_key(x): bin[x.b] += 1 else: bin[x.b] = 1 bin[x.not b] = 0 for x, y, z in bin.iteritems(): print x, y, z should the dict value become a two element list, or is there a cleaner way to do this? It would probably help if you explained what the real problem is you're trying to solve. actually, that code fragment was meant to do that. it's pretty much what i needed to do at that point, just the variable names made simple. Using a two element list to store a pair of counts has a bad code smell to me. exactly. which is why i was asking. That said, you could write your code something like: bin = {} for whatever: # NOTE: brackets are unnecessary for a, b in foo: x = 42 - a # NOTE: 'in' is generally faster than has_key() if x in bin bin[x][0] += 1 else: bin[x] = [1, 0] # NOTE: extra parens necessary to unpack count list for x, (y, z) in bin.iteritems(): print x, y, z so, to do this using the real names, it looks like for [start, end, AS, full] in heard: week = int((start-startDate)/aWeek) if week in bin: if full: bin[week][0] += 1 else: bin[week][1] += 1 else: if full: bin[week] = [1, 0] else: bin[week] = [0, 1] ... for i, (j, k) in bin.iteritems(): if j == 0: print str(i) + ",," + str(k) elif k == 0: print str(i) + "," + str(j) else: print str(i) + "," + str(j) + "," + str(k) which is still pretty darned grotty and unexpressive. of course, i could be a bit more obscure and do if week in bin: bin[week][not full] += 1 else: bin[week] = [ full, not full ] except i probably have to coerce the types or something. less code but less obvious. randy Aug 29 '05 #7

 P: n/a Randy Bush wrote:... i could be a bit more obscure and do if week in bin: bin[week][not full] += 1 else: bin[week] = [ full, not full ] If you cannot take the setdefault advice, at least do: try: bin[week][not full] += 1 except KeyError: bin[week] = [full, not full] --Scott David Daniels Sc***********@Acm.Org Aug 29 '05 #9

 P: n/a Adam Tomjack wrote: I'd write it like this: bin = {} for start, end, AS, full in heard: week = int((start-startDate)/aWeek) counters = bin.setdefault(week, [0, 0]) if full: counters[0] += 1 else: counters[1] += 1 for week, (times_full, times_not_full) in bin.iteritems(): print ... Seriously, use setdefault() as often as you can. It may be a little awkward at first, but after a little bit, you instantly know what it means. It takes out a whole if/else statement which will make your code smaller and more readable at the same time. However, you should be aware that it can make your code slower. If the default value is expensive to create, value = dct.setdefault(key, expensive_func()) will often be slower and/or more memory intensive than try: value = dct[key] except KeyError: value = expensive_func() or if key in dct: value = dct[key] else: value = expensive_func() Personally, I waver back and forth between using setdefault. It's kind of handy, but it never reads quite right. While I've used it often enough to grok it pretty quickly, it's still consumes more of my mental processing than some other approaches. I also tend to agree with the assessment[1] (I believe due to Raymond Hettinger) that setdefault is poorly designed, and should instead set a default value for the entire dictionary, e.g. so that you could do: counts = {} counts.setdefault(value=0) for elem in data: counts[elem] += 1 Unfortunately, this is pretty badly backwards incompatible, so I can only really hope for this change in Python 3.0 which is still a number of years off. STeVe [1]http://wiki.python.org/moin/Python3%2e0Suggestions#head-b384410f8b2dc16fd74c9eec764680e428643d73 Aug 29 '05 #10

 P: n/a Randy Bush wrote: Steven Bethard wrote: It would probably help if you explained what the real problem is you're trying to solve. actually, that code fragment was meant to do that. it's pretty much what i needed to do at that point, just the variable names made simple. Yeah, I gathered that. Sometimes though, you may not ever need to get to "that point" if the surrounding code can be properly reorganized. But I can't tell if that's possible unless you explain what the goal of the program is, not just how your current code tries to approach that goal. So I'm going to try to pump you for a little more information here. Is your goal to count, for each week, how many times it's "full" and how many times it's "not full"? What do you use the counts for? What does "full" mean? Is it always a 0 or 1? What's the importance of the output formatting? so, to do this using the real names, it looks like for [start, end, AS, full] in heard: week = int((start-startDate)/aWeek) if week in bin: if full: bin[week][0] += 1 else: bin[week][1] += 1 else: if full: bin[week] = [1, 0] else: bin[week] = [0, 1] ... for i, (j, k) in bin.iteritems(): if j == 0: print str(i) + ",," + str(k) elif k == 0: print str(i) + "," + str(j) else: print str(i) + "," + str(j) + "," + str(k) For the current code, I'd probably go with something like: for start, end, AS, full in heard: week = int((start-startDate)/aWeek) if week in bin: bin[week][not full] += 1 else: # I'm assuming "full" takes the values 0 or 1 # but if not, you can coerce it with bool() bin[week] = [full, int(not full)] ... for i, (j, k) in bin.iteritems(): result = [str(i), j and str(j) or ''] # special-case k because if it's zero, the reference # code drops a comma if k: result.append(str(k)) print ','.join(result) But if you're just trying to count the number of times a week is full or not full, I'd probably do something like: for start, end, AS, full in heard: week = int((start-startDate)/aWeek) if week in bin: bin[week].append(full) else: bin[week] = [full] ... for i, fulls in bin.iteritems(): j = sum(fulls) k = len(fulls) - j ... STeVe Aug 29 '05 #12

 P: n/a > bin = {} for start, end, AS, full in heard: week = int((start-startDate)/aWeek) counters = bin.setdefault(week, [0, 0]) if full: counters[0] += 1 else: counters[1] += 1 yes! thanks! Using an idea you used earlier, you could get smaller code by saying: for start, end, AS, full in heard: week = int((start-startDate)/aWeek) counters = bin.setdefault(week, [0, 0]) counters[not full] += 1 Or for start, end, AS, full in heard: week = int((start-startDate)/aWeek) bin.setdefault(week, [0, 0])[not full] += 1 Or even for start, end, AS, full in heard: bin.setdefault(int((start-startDate)/aWeek), [0, 0])[not full] += 1 as you say, too clever. Using lists to represent structs is perfectly fine if the list doesn't live longer than about one screen of code. i can definitely see that. in last weeks installment, i buried a complex trinary tree in a class. thanks for the advice! randy Aug 29 '05 #13

 P: n/a > So I'm going to try to pump you for a little more information here. Is your goal to count, for each week, how many times it's "full" and how many times it's "not full"? What do you use the counts for? What does "full" mean? Is it always a 0 or 1? What's the importance of the output formatting? 'full' is boolean. it says whether a particular bgp announcement was for the entire ip address allocation, or is a longer prefix. e.g., if an allocation was for 666.42.0.0/16 and we heard a bgp announcement for 666.42.1.0/24 that is !full, while an announcement for the prefix 666.42.0.0/16 is full. you asked :-) for start, end, AS, full in heard: week = int((start-startDate)/aWeek) if week in bin: bin[week][not full] += 1 else: # I'm assuming "full" takes the values 0 or 1 # but if not, you can coerce it with bool() bin[week] = [full, int(not full)] hmmm. this also reads well. as an old pascal and modula-2 bondage and discipline type, i gotta say is it a breath of fresh air to be in a language and community which care about how code reads more than how clever it is. randy Aug 29 '05 #14