some problems for an introductory python test

Chris Angelico rosuav at gmail.com
Tue Aug 10 14:47:42 EDT 2021


On Wed, Aug 11, 2021 at 4:14 AM Hope Rouselle <hrouselle at jevedi.xotimo> wrote:
>
> Chris Angelico <rosuav at gmail.com> writes:
>
> > On Tue, Aug 10, 2021 at 7:25 AM Hope Rouselle <hrouselle at jevedi.xotimo> wrote:
> >> I came up with the following question.  Using strings of length 5
> >> (always), write a procedure histogram(s) that consumes a string and
> >> produces a dictionary whose keys are each substrings (of the string) of
> >> length 1 and their corresponding values are the number of times each
> >> such substrings appear.  For example, histogram("aaaaa") = {"a": 5}.
> >> Students can "loop through" the string by writing out s[0], s[1], s[2],
> >> s[3], s[4].
> >
> > In other words, recreate collections.Counter? Seems decent, but you'll
> > need to decide whether you want them to use defaultdict, use
> > __missing__, or do it all manually.
>
> Yes, the course introduces very little so there is a lot of recreation
> going on.  Hm, I don't know defaultdict and I don't know how to use
> __missing__.  The course does introduce dict.get(), though.  If students
> use dict.get(), then the procedure could essentially be:
>
> def histogram(s):
>   d = {}
>   d[s[0]] = d.get(s[0], 0) + 1
>   d[s[1]] = d.get(s[1], 0) + 1
>   d[s[2]] = d.get(s[2], 0) + 1
>   d[s[3]] = d.get(s[3], 0) + 1
>   d[s[4]] = d.get(s[4], 0) + 1
>   return d

There's nothing wrong with getting students to recreate things, but
there are so many different levels on which you could do this, which
will leave your more advanced students wondering what's legal. :) Here
are several ways to do the same thing:

>>> s = "hello world"

>>> from collections import Counter; Counter(s)
Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})

>>> from collections import defaultdict
>>> hist = defaultdict(int)
>>> for ltr in s: hist[ltr] += 1
...
>>> hist
defaultdict(<class 'int'>, {'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1,
'w': 1, 'r': 1, 'd': 1})

>>> class Incrementer(dict):
...     def __missing__(self, key): return 0
...
>>> hist = Incrementer()
>>> for ltr in s: hist[ltr] += 1
...
>>> hist
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

>>> hist = {}
>>> for ltr in s: hist[ltr] = hist.get(ltr, 0) + 1
...
>>> hist
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

>>> hist = {}
>>> for ltr in s:
...     if ltr in hist: hist[ltr] += 1
...     else: hist[ltr] = 1
...
>>> hist
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

>>> hist = {}
>>> for ltr in s:
...     try: hist[ltr] += 1
...     except KeyError: hist[ltr] = 1
...
>>> hist
{'h': 1, 'e': 1, 'l': 3, 'o': 2, ' ': 1, 'w': 1, 'r': 1, 'd': 1}

A Counter shows the values ranked, all the others retain insertion
order, but they all get to the same result.

It seems *very* strange to have an exercise like this without looping.
That seems counterproductive. But if you're expecting them to not use
loops, you'll want to also be very clear about what other features
they're allowed to use - or alternatively, stipulate what they ARE
allowed to use, eg "Use only indexing and the get() method".

ChrisA


More information about the Python-list mailing list