Why can't I return a list of values NOT in a dictionary

386
5
11-23-2018 12:13 AM
LindsayRaabe_FPCWA
Regular Contributor

I have 2 dictionaries (A and B). A is a new list of keys which is being checked against B. If a key is in B but not A (is an old value), I'm attempting to get the first value from the associated key in the dictionary (in this case an OBJECTID). 

The issues I'm having are with line 32. As far as I can tell, this should iterate through all the keys in B, see if they're in A, and if not, write their value to a text file. As it happens, the text file is just getting the full list of old values (dictionary B) instead of only the ones not in both feature classes/dictionaries. Where are we going wrong?

I've attached the output text file

import arcpy

# Set environmental variables

arcpy.env.overwriteOutput = True

new = r"W:\Mapping\ArcGIS Online\ArcGIS Online Features.gdb\Collector_patch" #new feature class
old = r"W:\Mapping\ArcGIS Online\Upload.gdb\AGOL_Export" #old feature class
field = 'LinkKey', 'OBJECTID' #feature class fields to compare & return
fout = r"W:\Mapping\ArcGIS Online\FPC Plantations Features to Delete.txt" #output text file

a = {}
b = {}

#The old feature class is loaded into dictionary b
with arcpy.da.SearchCursor(old, (field)) as rows:
    for row in rows:
        if row[0] not in b: 
            b[row[0]] = [row[1]]

#The new feature class is loaded into dictionary a
with arcpy.da.SearchCursor(new, (field)) as rows:
    for row in rows:
        if row[0] not in a: 
            a[row[0]] = [row[1]]
        
#Below compares the dictionaries and extracts features with old Linkkeys"

fo = open(fout, "w")

for k, v in b.iteritems():
    if k not in a.items()[0]:
        print k
        print v
        fo.write(str(v[0]) + ',')

print "Linkkeys not in new feature class exported"

fo.close()‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
Lindsay Raabe
Forest Products Commission WA
5 Replies
NeilAyres
MVP Frequent Contributor

Just focusing on this line :

if k not in a.items()[0]:

Would that be better if it were just:

if k not in a:
DanPatterson_Retired
MVP Esteemed Contributor

let's focus on the keys and skip the values for now.

noo = [1, 3, 5, 6, 7]  # the new key list

old = [1, 2, 4, 6, 7]  # the original key

found = [n for n in noo if n not in old]

found
[3, 5]‍‍‍‍‍‍‍‍

now you have the 'noo' keys that were in the 'old' key list.  Use those for your slicing to get the values if needed.

FYI, your code will fail miserably in python 3 because substantial changes were made to dictionaries, so be forewarned if moving to ArcGIS Pro at any time soon.

JoshuaBixby
MVP Esteemed Contributor

Just a note of caution.  It appears that LinkKey is not unique and that you are interested in the "first" occurrence of it.  Result sets from database SQL queries do not guarantee order unless an ORDER BY clause is used, which means the "first" occurrence of a duplicate value returned from a cursor isn't guaranteed unless the cursor was setup with an ORDER BY clause.

So, instead of using

arcpy.da.SearchCursor(old, (field))

you will want to use

arcpy.da.SearchCursor(old, (field), sql_clause=(None, "ORDER BY OBJECTID"))

if you want to ensure the lowest OID is the "first" record for duplicate LinkKey values.

LindsayRaabe_FPCWA
Regular Contributor

Thanks for the tip. Linkkey should be unique as well so this shouldn't be an issue for us. Good to know how to deal with it if this were the case though. 

Lindsay Raabe
Forest Products Commission WA
0 Kudos
JoshuaBixby
MVP Esteemed Contributor

A few thoughts on form and function of your original code.

In terms of populating Python dictionaries, dictionary comprehensions are both idiomatic and efficient.  The following code

b = {}
with arcpy.da.SearchCursor(old, (field)) as rows:
    for row in rows:
        if row[0] not in b: 
            b[row[0]] = [row[1]]‍‍‍‍‍

can be rewritten to

with arcpy.da.SearchCursor(old, field) as rows:
    b = {k:[v] for k,v in rows}‍‍‍‍‍‍

NOTE:  I am not clear why you are saving the OBJECTID value as a list in the dictionary, but I kept it in my rewritten code.

Not only can/should Python with statements be used with ArcPy cursors, they should definitely be used with Python file objects.  The following code

fo = open(fout, "w")

# code processing data and writing output

fo.close()‍‍‍‍‍

can be rewritten to

with open(fout, "w") as fo:
    # code processing data and writing output‍‍