Concurrent Map and Map-Reduce

The QtConcurrent::map(), QtConcurrent::mapped() and QtConcurrent::mappedReduced() functions run computations in parallel on the items in a sequence such as a QList . QtConcurrent::map() modifies a sequence in-place, QtConcurrent::mapped() returns a new sequence containing the modified content, and QtConcurrent::mappedReduced() returns a single result.

These functions are part of the Qt Concurrent framework.

Each of the above functions has a blocking variant that returns the final result instead of a QFuture . You use them in the same way as the asynchronous variants.

images = ...
# Each call blocks until the entire operation is finished.
future = QtConcurrent.blockingMapped(images, scaled)
QtConcurrent.blockingMap(images, scale)
collage = QtConcurrent.blockingMappedReduced(images, scaled, addToCollage)

Note that the result types above are not QFuture objects, but real result types (in this case, QList < QImage > and QImage ).

Concurrent Map

QtConcurrent::mapped() takes an input sequence and a map function. This map function is then called for each item in the sequence, and a new sequence containing the return values from the map function is returned.

The map function must be of the form:

function = U(T t)

T and U can be any type (and they can even be the same type), but T must match the type stored in the sequence. The function returns the modified or mapped content.

This example shows how to apply a scale function to all the items in a sequence:

scaled = QImage(QImage image)

    return image.scaled(100, 100)

images = ...
thumbnails = QtConcurrent.mapped(images, scaled)

The results of the map are made available through QFuture . See the QFuture and QFutureWatcher documentation for more information on how to use QFuture in your applications.

If you want to modify a sequence in-place, use QtConcurrent::map(). The map function must then be of the form:

function = U(T t)

Note that the return value and return type of the map function are not used.

Using QtConcurrent::map() is similar to using QtConcurrent::mapped():

def scale(image):

    image = image.scaled(100, 100)

images = ...
future = QtConcurrent.map(images, scale)

Since the sequence is modified in place, QtConcurrent::map() does not return any results via QFuture . However, you can still use QFuture and QFutureWatcher to monitor the status of the map.

Concurrent Map-Reduce

QtConcurrent::mappedReduced() is similar to QtConcurrent::mapped(), but instead of returning a sequence with the new results, the results are combined into a single value using a reduce function.

The reduce function must be of the form:

function = V(T result, U intermediate)

T is the type of the final result, U is the return type of the map function. Note that the return value and return type of the reduce function are not used.

Call QtConcurrent::mappedReduced() like this:

def addToCollage(collage, thumbnail):

    p = QPainter(collage)
    offset = QPoint(0, 0)
    p.drawImage(offset, thumbnail)
    offset += ...

images = ...
collage = QtConcurrent.mappedReduced(images, scaled, addToCollage)

The reduce function will be called once for each result returned by the map function, and should merge the intermediate into the result variable. QtConcurrent::mappedReduced() guarantees that only one thread will call reduce at a time, so using a mutex to lock the result variable is not necessary. The QtConcurrent::ReduceOptions enum provides a way to control the order in which the reduction is done. If QtConcurrent::UnorderedReduce is used (the default), the order is undefined, while QtConcurrent::OrderedReduce ensures that the reduction is done in the order of the original sequence.

Additional API Features

Using Iterators instead of Sequence

Each of the above functions has a variant that takes an iterator range instead of a sequence. You use them in the same way as the sequence variants:

images = ...
thumbnails = QtConcurrent.mapped(images.constBegin(), images.constEnd(), scaled)
# Map in-place only works on non-const iterators.
future = QtConcurrent.map(images.begin(), images.end(), scale)
collage = QtConcurrent.mappedReduced(images.constBegin(), images.constEnd(), scaled, addToCollage)

Blocking Variants

Each of the above functions has a blocking variant that returns the final result instead of a QFuture . You use them in the same way as the asynchronous variants.

images = ...
# Each call blocks until the entire operation is finished.
future = QtConcurrent.blockingMapped(images, scaled)
QtConcurrent.blockingMap(images, scale)
collage = QtConcurrent.blockingMappedReduced(images, scaled, addToCollage)

Note that the result types above are not QFuture objects, but real result types (in this case, QList < QImage > and QImage ).

Using Member Functions

QtConcurrent::map(), QtConcurrent::mapped(), and QtConcurrent::mappedReduced() accept pointers to member functions. The member function class type must match the type stored in the sequence:

# Squeeze all strings in a QStringList.
strings = ...
squeezedStrings = QtConcurrent.map(strings, QString.squeeze)
# Swap the rgb values of all pixels on a list of images.
images = ...
bgrImages = QtConcurrent.mapped(images,()
    QImage (QImage.)() (QImage.rgbSwapped))
# Create a set of the lengths of all strings in a list.
strings = ...
> wordLengths = QtConcurrent.mappedReduced(strings, QString.length, QSet<int>.insert)

Note that when using QtConcurrent::mappedReduced(), you can mix the use of normal and member functions freely:

# Can mix normal functions and member functions with QtConcurrent::mappedReduced().
# Compute the average length of a list of strings.
void = extern(int average, int length)
strings = ...
averageWordLength = QtConcurrent.mappedReduced(strings, QString.length, computeAverage)
# Create a set of the color distribution of all images in a list.
int = extern(QImage string)
images = ...
> totalColorDistribution = QtConcurrent.mappedReduced(images, colorDistribution, QSet<int>.insert)

Using Function Objects

QtConcurrent::map(), QtConcurrent::mapped(), and QtConcurrent::mappedReduced() accept function objects for the map function. These function objects can be used to add state to a function call:

class Scaled():

    Scaled(int size)
    self.m_size = size
    QImage = typedef()
    operator = QImage()(QImage image)

        return image.scaled(m_size, m_size)

    m_size = int()

images = ...
thumbnails = QtConcurrent.mapped(images, Scaled(100))

For the reduce function, function objects are not directly supported. Function objects can, however, be used when the type of the reduction result is explicitly specified:

class ImageTransform():

    def operator(result, value):

thumbNails =
  QtConcurrent.mappedReduced<QImage>(images,
                                      Scaled(100),
                                      ImageTransform(),
                                      QtConcurrent.SequentialReduce)

Using Lambda Expressions

QtConcurrent::map(), QtConcurrent::mapped(), and QtConcurrent::mappedReduced() accept lambda expressions for the map and reduce function:

vector = { 1, 2, 3, 4 }
QtConcurrent.blockingMap(vector, [](int x) { x *= 2; })
size = 100
images = ...
thumbnails = QtConcurrent.mapped(images,()
        [size](QImage image) {
            return image.scaled(size, size)

    ).results()

When using QtConcurrent::mappedReduced() or QtConcurrent::blockingMappedReduced(), you can mix the use of normal functions, member functions and lambda expressions freely.

collage = QtConcurrent.mappedReduced(images,()
        [size](QImage image) {
            return image.scaled(size, size)
        },
        addToCollage
   ).results()

For the reduce function, lambda expressions are not directly supported. Lambda expressions can, however, be used when the type of the reduction result is explicitly specified:

collage = QtConcurrent.mappedReduced<QImage>(images,()
        [size](QImage image) {
            return image.scaled(size, size)
        },
        [](QImage result, QImage value) {
            # do some transformation

   ).results()

Wrapping Functions that Take Multiple Arguments

If you want to use a map function that takes more than one argument you can use a lambda function or std::bind() to transform it onto a function that takes one argument.

As an example, we’ll use scaledToWidth() :

QImage.scaledToWidth = QImage(int width, Qt.TransformationMode)

scaledToWidth takes three arguments (including the “this” pointer) and can’t be used with QtConcurrent::mapped() directly, because QtConcurrent::mapped() expects a function that takes one argument. To use scaledToWidth() with QtConcurrent::mapped() we have to provide a value for the width and the transformation mode:

images = ...
std::function<QImage(QImage )> scale = [](QImage img) {
    return img.scaledToWidth(100, Qt.SmoothTransformation)

thumbnails = QtConcurrent.mapped(images, scale)