Select to view content in your preferred language

Python Dictionary Order

1903
8
07-23-2014 07:22 PM
XanderBakker
Esri Esteemed Contributor
3 8 1,903

When you create a dictionary in Python the order will not necessary be the order in which you create it. There is a solution for this and that is the use of an OrderedDict (can be found in the collections module).

Take the following example; you have a dictionary you want to use to classify an angle (0-360) into a cardinal direction. You define the dictionary, but if you print the dictionary directly afterwards you will notice that the order is not necessary the same as defined:

dctCard = {22.5:"E", 67.5: "NE", 112.5: "N",

           157.5: "NW", 202.5: "W", 247.5: "SW",

           292.5: "S", 337.5: "SE", 360: "E"}

print dctCard

results in:

>> {22.5: 'E', 67.5: 'NE', 247.5: 'SW', 202.5: 'W', 292.5: 'S', 112.5: 'N', 360: 'E', 337.5: 'SE', 157.5: 'NW'}

So if you would use code like below to obtain the cardinal direction based on an angle, this will give erroneous results:

dctCard = {22.5:"E", 67.5: "NE", 112.5: "N",

           157.5: "NW", 202.5: "W", 247.5: "SW",

           292.5: "S", 337.5: "SE", 360: "E"}

myangle = 75

for angle, cardinal in dctCard.items():

    if myangle < angle:

        result = cardinal

        break

print "Angle {0} gives {1}".format(myangle, result)

could result in:

>> Angle 75 gives SW

Ordered dictionary

It is possible to solve this by using collections.OrderedDict(). Please be aware that the code below will still result in a randomly sorted dictionary:

from collections import OrderedDict

dctCard = OrderedDict({22.5:"E", 67.5: "NE", 112.5: "N",

           157.5: "NW", 202.5: "W", 247.5: "SW",

           292.5: "S", 337.5: "SE", 360: "E"})

The reason is, that first the dictionary is created in a random order and than that random order is stored as "ordered". There are two ways of solving this.

Either you loop through a sorted list of keys (the angle values):

dctCard = {22.5:"E", 67.5: "NE", 112.5: "N",

           157.5: "NW", 202.5: "W", 247.5: "SW",

           292.5: "S", 337.5: "SE", 360: "E"}

myangle = 75

for angle in sorted(dctCard.keys()):

    if myangle < angle:

        result = dctCard[angle]

        break

Or you create a list of key value pairs and convert that to OrderedDict

from collections import OrderedDict

lstCardinal = [(22.5,"E"), (67.5, "NE"), (112.5, "N"),

               (157.5, "NW"), (202.5, "W"), (247.5, "SW"),

               (292.5, "S"), (337.5, "SE"), (360, "E")]

dctCard2 = OrderedDict(lstCardinal)

myangle = 75

for angle, cardinal in dctCard2.items():

    if myangle < angle:

        result = cardinal

        break

Tags (1)
8 Comments
DanPatterson_Retired
MVP Emeritus

Xander... re-reading this again and the order dictionary would be useful for base python namespace.  Is there a way to get the namespace from the vars() ( or locals() or globals() ) as the dictionary is being added to as variables are being created and functions run.  I have been testing by getting the existing dictionary between various program steps ( ie getting a copy of the vars() as a list), but the dictionary information is not in sorfed order.  Converting the keys to sets and determining the set difference, allows me to determine what has happened between stages, but not as the stage is being implemented

i.e. for a hypothetical program

stage 1  os, sys, numpy imported  their namespace is not in the layer added

stage 1  Xs, Yx, XYs, np_array, rec_array  variables created....ditto their namespace not in added order.

So in short, ordered dictionaries should be the norm

XanderBakker
Esri Esteemed Contributor

Will have to look into that. Don't have an answer right now...

XanderBakker
Esri Esteemed Contributor

Did not have the time to look into this yet, and I wonder if I will be able to get you a decent answer.

Maybe some of the Python experts want to join in... Jason Scheirer, Jake Skinner‌, Curtis Price‌, Joshua Bixby‌, Filip Král‌... anyone?

DanPatterson_Retired
MVP Emeritus

Xander... I have searched many places and even had some suggestions to use various tracking options...they essentially tracked everything, making them useless.  Sorted dictionaries should be the norm for namespace, but alas, that won't happen..

I just judicious use of

.... herenow = locals().keys() ....  do some work...do some work ...

.....herelater = locals().keys() .... then convert to sets, get their difference, leaving me with any namespace that has been added between steps..

For short code, it is better to keep a list of namespace as you are writing code and document accordingly.  For programs that are long, and/or you want to monitor namespace there is no simple answer.

So thanks, Xander...anyone else have any simple solution...I am all years, but the results can't track everything, just additions to namespace

JoshuaBixby
MVP Esteemed Contributor

Dan Patterson wrote:

Sorted dictionaries should be the norm for namespace, but alas, that won't happen..

Sorted or ordered?  If you meant ordered, there is some light around the corner with ArcGIS Pro since it uses Python 3.x.  With Python 3.x, one can create a metaclass and use the __prepare__() method to replace the normal/default/unordered namespace dictionary with an ordered dictionary.  I can't recall at the moment whether I have seen someone implement similar functionality in Python 2.x, but it surely wouldn't be as straightforward as in Python 3.x.

JoshuaBixby
MVP Esteemed Contributor

Using a list of key-value pairs is definitely the way to go.  Sorting a list of keys works in this example, but in general, it doesn't ensure the original ordering is maintained like using a list of key-value pairs.

DanPatterson_Retired
MVP Emeritus

Thanks Joshua...I know that locals().keys() can return the keys at every step, but it makes the code sloppy.  If I want to convert to set, then get the difference between processing steps, I can get the newly added keys, however, the order can be anything.  I gave up on the idea since it was much faster for me to maintain a namespace list in Notepad++ to capture the keys as I created them.  At the end, I can use the keys, which are in correct order, to get the values I need step by step without having to rely on print statements, logging options etc etc.  I guess my biggest complaint is I still can't find a reason why dictionaries can't be ordered like the option in the collections module.  It would make monitoring namespace flawless and do away with all the fiddly steps that one has to go through.  I was hoping I was missing something.  Thanks... (I have issues with tuples as well...but that is another story)

Dan

JoshuaBixby
MVP Esteemed Contributor

I understand, both the situation and frustration.  For reasons only a shrink could speculate about, it bothers me more than it should when I have to stick with a workaround because something that seems so simple simply isn't.  Although addressing this issue has gotten easier with Python 3.x, it still seems more complicated than it needs to be.

About the Author
Solution Engineer for the Utilities Sector @ Esri Colombia - Ecuador - Panamá sr GIS Advisor / Python - Arcpy developer / GIS analyst / technical project leader / lecturer and GeoNet moderator, focusing on innovations in the field of GIS. Specialties: ArcGIS, Python, ArcGIS Enterprise, ArcGIS Online, Arcade, Configurable Apps, WAB, Mobile Apps, Insights, Spatial Analysis, LiDAR / 3D Laser Scanning / Point Clouds. UNME http://nl.linkedin.com/in/xanderbakker/ http://www.slideshare.net/XanderBakker http://www.scribd.com/xbakker http://twitter.com/#!/XanderBakker
Labels