Skip to content
Advertisement

How to chain Python’s set.add() while updating dictionary values being sets?

Let’s create dictionary dctD entries with values being set()s:

dctD = {'key0':set(['value0'])} # static constant (key, value) pair
dctD['key1'] = set(['value1'])  # static constant (key, value) pair

Now the dictionary has to be updated with a new value which should extend its set() value if the at runtime unknown key value is already in the dictionary:

#  v-- simulation of --v-- dynamic assignment
varkey = 'key2'  ;  varval = 'value2' 
#  ^-- with unknown --^-- values during runtime

The GOAL is to extend the set being value in dctD by a value also in case the key does not yet exist. Because the set.add() method does not return the with the new value updated set a tmpSet identifier is required to achieve such goal. The code for extending existing or creating new set is then:

tmpSet = dctD.get(varkey, set())
tmpSet.add(varval)
dctD[varkey] = tmpSet

With help of the walrus operator it is possible to cut down the code above to two lines of code as follows:

(tmpSet:= dctD.get(varkey, set())).add(varval)
dctD[varkey] = tmpSet

And with help of a chainOfSet class as a wrapper for the set() methods to one line (with improved readability):

dctD[varkey] = chainOfSet(dctD.get(varkey, set())).add(varval).get()

Now the question:

Does Python allow a simpler way to code the above one-line assignment without the need of writing a wrapper class?

Below the code of the chainOfSet class and all of the code mentioned above:

class chainOfSet: 
    """ 
    Allows chaining (by dot syntax) else not chainable set() methods  
    and addition/subtraction of other sets. 
    Is doesn't support interaction of objects of this class itself as 
    this is considered to be out of scope of the purpose for which this 
    class was created.  
    """
    def __init__(s, sv=set()):
        s.sv = sv
    # ---
    def add(s, itm):
        s.sv.add(itm)
        return s
    def update(s, *itm):
        s.sv.update(itm)
        return s
    def remove(s, itm):     # key error if not in set
        s.sv.remove(itm)
        return s
    def discard(s, itm):    # remove if present, but no error if not
        s.sv.discard(itm)
        return s
    def pop(s):
        s.sv.pop()
        return s
    def clear(s):
        s.sv.clrear()
        return s
    # ---
    def intersection(s, p):
        s.sv = s.sv.intersection(p)
        return s
    def union(s, p):
        s.sv = s.sv.union(p)
        return s
    def __add__(s, itm):
        if isinstance(itm, set): 
            s.sv = s.sv.union(itm)
        else: 
            s.sv.update(itm)
        return s
    def difference(s,p):
        s.sv = s.sv.difference(p)
        return s
    def __sub__(s, itm):
        if isinstance(itm, set): 
            s.sv = s.sv - itm
        else: 
            s.sv.difference(set(itm))
        return s
    def symmetric_difference(s,p): 
        # equivalent to: union - intersection
        s.sv = s.sv.symmetric_difference(p)
        return s
    def copy(s):
        s.sv = s.sv.copy()
        return s        
    # ---
    def len(s):
        return len(s.sv)
    def isdisjoint(s,p):
        return s.sv.isdisjoint(p)
    def issubset(s,p): 
        return s.sv.issubset(p)
    def issuperset(s,p):
        return s.sv.issuperset(p)
    # ---
    def get(s):
        return s.sv
#:class chainOfSet(set) 

# Let's create dictionary dctD entries with values being set()s: 
dctD = {'key0':set(['value0'])} # static constant (key, value) pair
dctD['key1'] = set(['value1'])  # static constant (key, value) pair

# Now the dictionary has to be updated with a new value which should 
# extend its set() value if the at runtime unknown key value is already 
# in the dictionary: 

#  v-- simulation of --v-- dynamic assignment
varkey = 'key2'  ;  varval = 'value2' 
#  ^-- with unknown --^-- values during runtime

# The GOAL is to extend the set being value in dctD by a value also in 
# case the key does not yet exist.
# Because set.add() method does not return a value a tmpSet identifier
# is required to achieve the goal. The code for extending existing or 
# creating new set is then: 

tmpSet = dctD.get(varkey, set())
tmpSet.add(varval)
dctD[varkey] = tmpSet

# With use of the walrus operator it is possible to cut down the code
# above to two lines of code as follows: 
(tmpSet:= dctD.get(varkey, set())).add(varval)
dctD[varkey] = tmpSet

# And with use of a 'class chainOfSet' as a wrapper for the set() 
# methods to one line (one-liner with improved readability): 
dctD[varkey] = chainOfSet(dctD.get(varkey, set())).add(varval).get()

# Does Python allow a simpler way to code the above one-line assignment
# without need of writing a wrapper class? 

Notice that above provided wrapper class allows not only chaining, but also usage of the addition operator:

print((chainOfSet(set([1,2,3]))+{5,6}-{1}).intersection({1,2,5}).get())

printing as output

{2,5}

Update: evolution of code improvements in 4 steps:

All of the 4 steps produce the same result, so the code below runs without assertion error. Notice how it was possible to get the lines and code length in characters down using the different approaches:

# >>>  My own first version: 
dctD = {}
tmpVal = dctD.get('key1', set())
tmpVal.add('value1.1')
dctD['key1'] = tmpVal
tmpVal = dctD.get('key1', set())
tmpVal.add('value1.2')
dctD['key1'] = tmpVal
tmpVal = dctD.get('key2', set())
tmpVal.add('value2.1')
dctD['key2'] = tmpVal
# >>>  My own second version: 
dctD0 = {}
(tmpVal := dctD0.get('key1', set())).add('value1.1')
dctD0['key1'] = tmpVal
(tmpVal := dctD0.get('key1', set())).add('value1.2')
dctD0['key1'] = tmpVal
(tmpVal := dctD0.get('key2', set())).add('value2.1')
dctD0['key2'] = tmpVal
print(dctD0)
# >>>  Approach provided in answer by John Kugelman :
dctD1 = {}
dctD1.setdefault('key1', set()).add('value1.1')
dctD1.setdefault('key1', set()).add('value1.2')
dctD1.setdefault('key2', set()).add('value2.1')
print(dctD1)
# >>>  Approach provided in answer by Tom McLean: 
from collections import defaultdict
dctD2 = defaultdict(set)
dctD2['key1'].add('value1.1')
dctD2['key1'].add('value1.2')
dctD2['key2'].add('value2.1')
print(dctD2)
# ---
assert dctD == dctD0 == dctD1 == dctD2 == dctD2 

Advertisement

Answer

From what I can understand, this can easily be done with a defaultdict

from collections import defaultdict

dctD = defaultdict(set)

varkey = "key2"
varval = "value"

dctD[varkey].add(varval)

print(dctD["key2"])   # {'value'}
print(dctD["key1"])   # set()
print("key3" in dctD) # False
8 People found this is helpful
Advertisement