How can I collect the results of a repeated calculation in a list, dictionary etc. (make a copy of a list with each element modified)?

General approaches

There are three ordinary ways to approach the problem: by explicitly using a loop (normally a for loop, but while loops are also possible); by using a list comprehension (or dict comprehension, set comprehension, or generator expression as appropriate to the specific need in context); or by using the built-in map (results of which can be used to construct a list, set or dict explicitly).

Using an explicit loop

Create a list or dictionary before the loop, and add each value as it’s calculated:

def make_list_with_inline_code_and_for():
    ys = []
    for x in [1, 3, 5]:
        ys.append(x + 1)
    return ys

def next_collatz(value):
    if value % 2 == 0:
        return value // 2
    else:
        return 3 * value + 1

def make_dict_with_function_and_while():
    x = 19
    ys = {}
    while x != 1:
        y = next_collatz(x)
        ys[x] = y # associate each key with the next number in the Collatz sequence.
        x = y # continue calculating the sequence.
    return ys

In both examples here, the loop was put into a function in order to label the code and make it reusable. These examples return the ys value so that the calling code can use the result. But of course, the computed ys could also be used later in the same function, and loops like these could also be written outside of any function.

Using a comprehension or generator expression

A list comprehension gives elegant syntax for creating a list from an existing sequence of values. It should be preferred where possible, because it means that the code does not have to focus on the details of how to build the list, making it easier to read. It can also be faster, although this will usually not matter.

It can work with either a function call or other calculation (any expression in terms of the “source” elements), and it looks like:

xs = [1, 3, 5]

ys = [x + 1 for x in xs]
# or
def calc_y(an_x):
    return an_x + 1
ys = [calc_y(x) for x in xs]

Note that this will not replace a while loop; there is no valid syntax replacing for with while here. In general, list comprehensions are meant for taking existing values and doing a separate calculation on each – not for any kind of logic that involves “remembering” anything from one iteration to the next (although this can be worked around, especially in Python 3.8 and later).

Similarly, a dictionary result can be created using a dict comprehension – as long as both a key and value are computed in each iteration. Depending on exact needs, set comprehensions (produce a set, which does not contain duplicate values) and generator expressions (produce a lazily-evaluated result; see below about map and generator expressions) may also be appropriate.

Using map

This is similar to a list comprehension, but even more specific. map is a built-in function that can apply a function repeatedly to multiple different arguments from some input sequence (or multiple sequences).

Getting results equivalent to the previous code looks like:

xs = [1, 3, 5]

def calc_y(an_x):
    return an_x + 1

ys = list(map(calc_y, xs))
# or
ys = list(map(lambda x: x + 1, xs))

As well as requiring an input sequence (it doesn’t replace a while loop), the calculation needs to be done using a function or other callable, such as the lambda shown above (any of these, when passed to map, is a so-called “higher-order function”).

In Python 3.x, map is a class, and calling it therefore creates an instance of that class – and that instance is a special kind of iterator (not a list) that can’t be iterated more than once. (We can get something similar using a generator expression rather than a list comprehension; simply use () instead of [].)

Therefore, the code above explicitly creates a list from the mapped values. In other situations, it might not be necessary to do this (i.e., if it will only be iterated over once). On the other hand, if a set is necessary, the map object can be passed directly to set rather than list in the same way. To produce a dictionary, the map should be set up so that each output element is a (key, value) tuple; then it can be passed to dict, like so:

def dict_from_map_example(letters):
    return dict(map(lambda l: (l, l.upper()), letters))
    # equivalent using a dict comprehension:
    # return {l:l.upper() for l in letters}

Generally, map is limited and uncommon compared to list comprehensions, and list comprehensions should be preferred in most code. However, it does offer some advantages. In particular, it can avoid the need to specify and use an iteration variable: when we write list(map(calc_y, xs)), we don’t need to make up an x to name the elements of xs, and we don’t have to write code to pass it to calc_y (as in the list comprehension equivalent, [calc_y(x) for x in xs] – note the two xs). Some people find this more elegant.

Leave a Comment