2

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.

Nez
  • 23
  • 4

6 Answers6

4

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.)

chepner
  • 497,756
  • 71
  • 530
  • 681
3

You can use a dict comprehension for the same result:

{l: 1 for l in set("Hello World!".lower())}
deceze
  • 510,633
  • 85
  • 743
  • 889
3

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

Vulwsztyn
  • 2,140
  • 1
  • 12
  • 20
  • Don't worry about that flaw; it is, as stupid as it might sound, in fact very much on purpose. Your solution is definetly very nice though and I thank you for taking your time! :) – Nez Sep 10 '21 at 12:56
  • 2
    You shouldn't call your functions in a manner indicating they do something they do not :) – Vulwsztyn Sep 10 '21 at 13:03
  • @Nez Now I'm curious. What *is* your purpose? – Stef Sep 10 '21 at 13:03
  • @Vulwsztyn yeah you are right. Might be a bit misleading. I was curious where I could look up the syntax you introduced, I have no idea what I am looking at – Nez Sep 10 '21 at 13:59
  • @Stef Really it's only for learning python. I'm looking for fun ways to implement an algorithm which finds a unique letter for each word in a list. Part of it is counting occurences of letters in the words in general, ignoring doubles though. Did this in Haskell before so my approach might seam a bit odd (and I loooove folds/reduce) – Nez Sep 10 '21 at 14:02
  • @Nez [very short python doc on `**`](https://docs.python.org/3/tutorial/controlflow.html#unpacking-argument-lists) and also [Question about unpacking and assignment](https://stackoverflow.com/questions/6967632/unpacking-extended-unpacking-and-nested-extended-unpacking) – Stef Sep 10 '21 at 14:12
  • @Nez If you want to transform a sentence into a list of words, you will be interested in [`str.split`](https://docs.python.org/3.3/library/stdtypes.html?highlight=split#str.split) – Stef Sep 10 '21 at 14:14
  • 1
    @Nez also note that python is a terrible language for functional programming; solutions with iteration (and possibly [`itertools`](https://docs.python.org/3/library/itertools.html)) are usually preferred. – Stef Sep 10 '21 at 14:16
  • @Nez Don't look if you don't want spoilers, but would this solve your problem: [Try it online!](https://tio.run/##dY@7bsMwDEV3fQW3SECRJUtQwFM/wzAMR5YbtTKpUFReP@/KdRxkaLcrQudcMt7kSLjbR54mP0ZiAUshOCueMCnVuwEGj32b0Z@ya@2x4zY6bi/EvU4OxaF15l1B4XJ5cYIK6hfH9mOZ65kwMBDDnMAjrPg2xeBFm6ZYhKQLRZHyqFfjGyTpWKq/rMYUiJ1kRqjRXUXb3w47FzwE4Ic11raBqlpaSl72eX57EqlRKrJH0f8fvzllb7/hwHTBornCVx5jAjoXU@juN@jpc2PMNP0A "Python 3.8 (pre-release) – Try It Online") – Stef Sep 10 '21 at 14:26
  • @Stef are you saying python is terrible in a sense that code becomes unreadable or are you also referring to perfomance issues? For reference: https://stackoverflow.com/a/10366417/15813942 – Nez Sep 10 '21 at 14:29
2

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.

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
1

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.

timgeb
  • 76,762
  • 20
  • 123
  • 145
0

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.

Stef
  • 13,242
  • 2
  • 17
  • 28