// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include #include #include #include #if !defined(Q_CC_MSVC) || _MSC_VER >= 1900 // MSVC2015 #define SUPPORTS_MOVE #endif class tst_MapReduce : public QObject { Q_OBJECT private slots: void mapReduce(); void mapReduceRvalueContainer(); void map(); void orderedMapReduce(); #ifdef SUPPORTS_MOVE void moveOnlyType(); #endif }; static int returnxx(int x) { return x*x; } static void returnxxThroughFutureInterface(QFutureInterface &fi, int x) { fi.reportResult(x*x); } void tst_MapReduce::mapReduce() { QThreadPool pool; const auto initWithFutureInterface = [](QFutureInterface &fi) -> double { fi.reportResult(0.); return 0.; }; const auto reduceWithFutureInterface = [](QFutureInterface &fi, double &state, int value) { state += value; fi.reportResult(value); }; const auto reduceWithReturn = [](double &state, int value) -> double { state += value; return value; }; const auto cleanupWithFutureInterface = [](QFutureInterface &fi, double &state) { state /= 2.; fi.reportResult(state); }; { // map without future interface QList results = Utils::mapReduce(QVector({1, 2, 3, 4, 5}), initWithFutureInterface, returnxx, reduceWithFutureInterface, cleanupWithFutureInterface) .results(); Utils::sort(results); // mapping order is undefined QCOMPARE(results, QList({0., 1., 4., 9., 16., 25., 27.5})); } { // map with future interface QList results = Utils::mapReduce(QList({1, 2, 3, 4, 5}), initWithFutureInterface, returnxxThroughFutureInterface, reduceWithFutureInterface, cleanupWithFutureInterface) .results(); Utils::sort(results); // mapping order is undefined QCOMPARE(results, QList({0., 1., 4., 9., 16., 25., 27.5})); } { // reduce without future interface QList results = Utils::mapReduce(QList({1, 2, 3, 4, 5}), initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface) .results(); Utils::sort(results); // mapping order is undefined QCOMPARE(results, QList({0., 1., 4., 9., 16., 25., 27.5})); } { // reduce with threadpool QList results = Utils::mapReduce(QList({1, 2, 3, 4, 5}), initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface, Utils::MapReduceOption::Unordered, &pool) .results(); Utils::sort(results); // mapping order is undefined QCOMPARE(results, QList({0., 1., 4., 9., 16., 25., 27.5})); } { // lvalue ref container QList container({1, 2, 3, 4, 5}); QList results = Utils::mapReduce(container, initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface) .results(); Utils::sort(results); // mapping order is undefined QCOMPARE(results, QList({0., 1., 4., 9., 16., 25., 27.5})); } { // std::cref QList container({1, 2, 3, 4, 5}); QCOMPARE(Utils::mapReduce(std::cref(container), initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface, Utils::MapReduceOption::Ordered).results(), QList({0., 1., 4., 9., 16., 25., 27.5})); } { // std::cref with threadpool QList container({1, 2, 3, 4, 5}); QCOMPARE(Utils::mapReduce(std::cref(container), initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface, Utils::MapReduceOption::Ordered, &pool).results(), QList({0., 1., 4., 9., 16., 25., 27.5})); } { // std::ref QList container({1, 2, 3, 4, 5}); QCOMPARE(Utils::mapReduce(std::ref(container), initWithFutureInterface, returnxx, reduceWithReturn, cleanupWithFutureInterface, Utils::MapReduceOption::Ordered).results(), QList({0., 1., 4., 9., 16., 25., 27.5})); } { // init and cleanup without future interface QCOMPARE(Utils::mapReduce(QList({1, 2, 3}), []() { return 0.; }, [](int v) { return v*2; }, [](double &state, int v) { return state += v/4.; }, [](double &) { }, Utils::MapReduceOption::Ordered).results(), QList({.5, 1.5, 3.})); } { // simplified map reduce without init and cleanup QCOMPARE(Utils::mapReduce(QList({QLatin1String("blubb"), QLatin1String("foo"), QLatin1String("blah")}), [](const QString &val) { return val.size(); }, 90., [](double &state, int val) { state /= double(val); }, Utils::MapReduceOption::Ordered).result(), 1.5); } { // simplified map reduce without init and cleanup with threadpool QCOMPARE(Utils::mapReduce(QList({QLatin1String("blubb"), QLatin1String("foo"), QLatin1String("blah")}), [](const QString &val) { return val.size(); }, 90., [](double &state, int val) { state /= double(val); }, Utils::MapReduceOption::Ordered, &pool).result(), 1.5); } { // simplified map reduce // std::cref QList container({1, 2, 3}); QCOMPARE(Utils::mapReduce(std::cref(container), [](int val) { return 2*val; }, 10, [](int &state, int val) { state += val; }).result(), 22); } { // simplified map reduce // std::cref with threadpool QList container({1, 2, 3}); QCOMPARE(Utils::mapReduce(std::cref(container), [](int val) { return 2*val; }, 10, [](int &state, int val) { state += val; }, Utils::MapReduceOption::Unordered, &pool).result(), 22); } { // simplified map reduce // std::ref QList container({1, 2, 3}); QCOMPARE(Utils::mapReduce(std::ref(container), [](int &val) { return 2*val; }, 10, [](int &state, int val) { state += val; }).result(), 22); } { // blocking mapReduce = mappedReduced QCOMPARE(Utils::mappedReduced(QList({1, 2, 3}), [](int &val) { return 2*val; }, 10, [](int &state, int val) { state += val; }), 22); } { // blocking mapReduce = mappedReduced // with threadpool QCOMPARE(Utils::mappedReduced(QList({1, 2, 3}), [](int &val) { return 2*val; }, 10, [](int &state, int val) { state += val; }, Utils::MapReduceOption::Unordered, &pool), 22); } } void tst_MapReduce::mapReduceRvalueContainer() { { QFuture future = Utils::mapReduce(QList({1, 2, 3, 4, 5}), []() { return 0; }, [](int value) { return value; }, [](QFutureInterface &, int &state, int value) { state += value; }, [](QFutureInterface &fi, int &state) { fi.reportResult(state); }); // here, lifetime of the QList temporary ends QCOMPARE(future.results(), QList({15})); } } void tst_MapReduce::map() { QCOMPARE(Utils::map(QList({2, 5, 1}), [](int x) { return x*2.5; }).results(), QList({5., 12.5, 2.5})); { // void result QList results; QMutex mutex; Utils::map( // container QList({2, 5, 1}), // map [&mutex, &results](int x) { QMutexLocker l(&mutex); results.append(x); } ).waitForFinished(); // Utils::map is "ordered" by default, but that means that result reporting is ordered, // the map function is still called out-of-order Utils::sort(results); QCOMPARE(results, QList({1, 2, 5})); } { // inplace editing QList container({2, 5, 1}); Utils::map(std::ref(container), [](int &x) { x *= 2; }).waitForFinished(); QCOMPARE(container, QList({4, 10, 2})); Utils::map(container.begin(), container.end(), [](int &x) { x *= 2; }, Utils::MapReduceOption::Unordered, nullptr, QThread::InheritPriority, 3).waitForFinished(); QCOMPARE(container, QList({8, 20, 4})); } // blocking map = mapped { const QSet sizes = Utils::mapped( QStringList({QLatin1String("foo"), QLatin1String("bar"), QLatin1String("blah")}), [](const QString &s) { return s.size(); }); QList vals = sizes.values(); Utils::sort(vals); QCOMPARE(vals, QList({3, 4})); } { const QStringList list({QLatin1String("foo"), QLatin1String("bar"), QLatin1String("blah")}); const QSet sizes = Utils::mapped(list.cbegin(), list.cend(), [](const QString &s) { return s.size(); }); QList vals = sizes.values(); Utils::sort(vals); QCOMPARE(vals, QList({3, 4})); } } void tst_MapReduce::orderedMapReduce() { QCOMPARE(Utils::mapReduce(QList({1, 2, 3, 4}), []() { return 0; }, [](int i) { return i*2; }, [](int &state, int val) { state += val; return state; }, [](int &) { }, Utils::MapReduceOption::Ordered).results(), QList({2, 6, 12, 20})); } #ifdef SUPPORTS_MOVE class MoveOnlyType { public: MoveOnlyType() noexcept {} // <- with GCC 5 the defaulted one is noexcept(false) MoveOnlyType(const MoveOnlyType &) = delete; MoveOnlyType(MoveOnlyType &&) = default; MoveOnlyType &operator=(const MoveOnlyType &) = delete; MoveOnlyType &operator=(MoveOnlyType &&) = default; }; class MoveOnlyState : public MoveOnlyType { public: int count = 0; }; class MoveOnlyInit : public MoveOnlyType { public: MoveOnlyState operator()(QFutureInterface &) const { return MoveOnlyState(); } }; class MoveOnlyMap : public MoveOnlyType { public: int operator()(const MoveOnlyType &) const { return 1; } }; class MoveOnlyReduce : public MoveOnlyType { public: void operator()(QFutureInterface &, MoveOnlyState &state, int) { ++state.count; } }; class MoveOnlyList : public std::vector { public: MoveOnlyList() { emplace_back(MoveOnlyType()); emplace_back(MoveOnlyType()); } MoveOnlyList(const MoveOnlyList &) = delete; MoveOnlyList(MoveOnlyList &&) = default; MoveOnlyList &operator=(const MoveOnlyList &) = delete; MoveOnlyList &operator=(MoveOnlyList &&) = default; }; void tst_MapReduce::moveOnlyType() { QCOMPARE(Utils::mapReduce(MoveOnlyList(), MoveOnlyInit(), MoveOnlyMap(), MoveOnlyReduce(), [](QFutureInterface &fi, MoveOnlyState &state) { fi.reportResult(state.count); } ).results(), QList({2})); } #endif QTEST_GUILESS_MAIN(tst_MapReduce) #include "tst_mapreduce.moc"