How could one reduce the usage of helper functions in lambda expressions?

Viewed 173

In this example I'm taking letters from a set and append them to a dictionary where the letter becomes the key and the literal 1 becomes the value to each pair.

def base_dict_from_set(s):
    return reduce(lambda d,e : addvalue(1, e, d), s, dict())


def addvalue(value, key, d):
    d[key] = value
    return d
>>> base_dict_from_set(set("Hello World!".lower()))
{'o': 1, '!': 1, 'l': 1, 'd': 1, 'w': 1, ' ': 1, 'r': 1, 'e': 1, 'h': 1}

I was wondering whether I could somehow be rid of the 'addvalue' helper function and add the element and reference the modified dictionary within the lambda function itself.

The routine within addvalue itself seams very simple to me, so I would prefer something that looks like this:

def base_dict_from_set(s):
    reutrn reduce(lambda d,e : d[e] = 1, s, dict())

I don't have a lot of experience in python and I come from a functional programming perspective. My goal is to understand pythons functional capabilities but I am too unexperienced to properly phrase and google what I am looking for.

6 Answers

What you are trying to do is why dict.fromkeys exists: create a dict that maps each key to the same constant value.

>>> dict.fromkeys("Hello World!".lower(), 1)
{'h': 1, 'e': 1, 'l': 1, 'o': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1}

There's no need to convert the string to a set first, since any duplicates will just be overwritten by the following occurrences.

(If the constant value is mutable, you should use the dict comprehension to ensure that each key gets its own mutable value, rather than every key sharing a reference to the same mutable value.)

You can use a dict comprehension for the same result:

{l: 1 for l in set("Hello World!".lower())}

To answer exactly the question asked, yes you can get rid of the addvalue by replacing addvalue(1, e, d) with {**d, e:1}.

Nevertheless, your code is still faulty. It is not counting the occurrences, but creates a dict of key: 1 for every letter in the string and it should create a dict of key: number_of_occurences to achieve this you should replace addvalue(1, e, d) with {**d, e: 1 + (d[e] if e in d else 0)} and not convert the string to set as it eliminates duplicates

I'm a bit surprised that you tried to use reduce when your goal is to transform each item in an input collection (the letters in a string) to an output collection (a key/value pair where the key is the letter and the value is a constant number), independently of each other.

In my view, reduce is for when an operation needs to be done to items in a sequence and taking all previous items into account (for instance, when calculating a sum of values).

So in a functional style, using map here would be more appropriate than reduce, in my opinion. Python supports this:

def quant_dict_from_set(s):
    return dict(map(lambda c: (c, 1), s.lower()))

Where map converts the string to key/value pairs and the dict constructor collects these pairs in a dictionary, while eliminating duplicate keys at the same time.

But more idiomatic approaches would be to use a dictionary comprehension or the dict.fromkeys constructor.

Hacky and hard to read, but closest to the lambda you were trying to write, and hopefully educational:

>>> f = lambda d, e: d.__setitem__(e, 1) or d
>>> d = {}
>>> output = f(d, 42)
>>> output
{42: 1}
>>> output is d
True

Using __setitem__ avoids the = assignment.

__setitem__ returns None, so the expression d.__setitem__(e, 1) or d always evaluates to d, which is returned by the lambda.

You can use collections.Counter, a subclass of dict specifically for counting occurrences of elements.

>>> import collections

>>> collections.Counter('Hello, World!'.lower())
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ',': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1, '!': 1})

>>> collections.Counter(set('Hello, World!'.lower()))
Counter({'w': 1, 'l': 1, 'r': 1, ',': 1, 'h': 1, 'd': 1, 'o': 1, 'e': 1, ' ': 1, '!': 1})

Note that Counter is appropriate if you want to count the elements, of if you want to initiate the values to the constant 1. If you want to initiate the values to another constant, then Counter will not be the solution and you should use a dictionary comprehension or the dict.fromkeys constructor.

Related