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.

QList<QImage> images = ...;

// Each call blocks until the entire operation is finished.
QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

QtConcurrent::blockingMap(images, scale);

QImage 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:

U function(const 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:

QImage scaled(const QImage &image)
{
    return image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<QImage> 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:

U function(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():

void scale(QImage &image)
{
    image = image.scaled(100, 100);
}

QList<QImage> images = ...;
QFuture<void> 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:

V function(T &result, const 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:

void addToCollage(QImage &collage, const QImage &thumbnail)
{
    QPainter p(&collage);
    static QPoint offset = QPoint(0, 0);
    p.drawImage(offset, thumbnail);
    offset += ...;
}

QList<QImage> images = ...;
QFuture<QImage> 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:

QList<QImage> images = ...;

QFuture<QImage> thumbnails = QtConcurrent::mapped(images.constBegin(), images.constEnd(), scaled);

// Map in-place only works on non-const iterators.
QFuture<void> future = QtConcurrent::map(images.begin(), images.end(), scale);

QFuture<QImage> 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.

QList<QImage> images = ...;

// Each call blocks until the entire operation is finished.
QList<QImage> future = QtConcurrent::blockingMapped(images, scaled);

QtConcurrent::blockingMap(images, scale);

QImage 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.
QStringList strings = ...;
QFuture<void> squeezedStrings = QtConcurrent::map(strings, &QString::squeeze);

// Swap the rgb values of all pixels on a list of images.
QList<QImage> images = ...;
QFuture<QImage> bgrImages = QtConcurrent::mapped(images,
    static_cast<QImage (QImage::*)() const &>(&QImage::rgbSwapped));

// Create a set of the lengths of all strings in a list.
QStringList strings = ...;
QFuture<QSet<int> > 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.
extern void computeAverage(int &average, int length);
QStringList strings = ...;
QFuture<int> averageWordLength = QtConcurrent::mappedReduced(strings, &QString::length, computeAverage);

// Create a set of the color distribution of all images in a list.
extern int colorDistribution(const QImage &string);
QList<QImage> images = ...;
QFuture<QSet<int> > 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:

struct Scaled
{
    Scaled(int size)
    : m_size(size) { }

    typedef QImage result_type;

    QImage operator()(const QImage &image)
    {
        return image.scaled(m_size, m_size);
    }

    int m_size;
};

QList<QImage> images = ...;
QFuture<QImage> 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:

struct ImageTransform
{
    void operator()(QImage &result, const QImage &value);
};

QFuture<QImage> 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:

QList<int> vector { 1, 2, 3, 4 };
QtConcurrent::blockingMap(vector, [](int &x) { x *= 2; });

int size = 100;
QList<QImage> images = ...;

QList<QImage> thumbnails = QtConcurrent::mapped(images,
        [&size](const 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.

QList<QImage> collage = QtConcurrent::mappedReduced(images,
        [&size](const 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:

QList<QImage> collage = QtConcurrent::mappedReduced<QImage>(images,
        [&size](const QImage &image) {
            return image.scaled(size, size);
        },
        [](QImage &result, const 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 QImage::scaledToWidth(int width, Qt::TransformationMode) const;

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:

QList<QImage> images = ...;
std::function<QImage(const QImage &)> scale = [](const QImage &img) {
    return img.scaledToWidth(100, Qt::SmoothTransformation);
};
QFuture<QImage> thumbnails = QtConcurrent::mapped(images, scale);