Add a generic container-joining algorithm qJoin()

Introduce a generic container-joining algorithm, qJoin(), to
qalgorithms.h. qJoin() combines the elements of a container
into a single result such as a string separated by a specified
'Separator'. Optionally, elements can be transformed using a
projection function.

Add tests and documentation.

[ChangeLog][QtCore][QtAlgorithms] Added qJoin().

Task-number: QTBUG-64318
Change-Id: Ia9abd45645fbbbcc87ff3e5474878609398d9eb6
Reviewed-by: Marc Mutz <marc.mutz@qt.io>
This commit is contained in:
Rym Bouabid 2024-11-27 12:19:56 +01:00
parent 929466ba64
commit 01fc6aeaff
3 changed files with 167 additions and 0 deletions

View File

@ -9,6 +9,7 @@
#endif
#include <QtCore/qglobal.h>
#include <QtCore/q20functional.h>
#if __has_include(<bit>) && __cplusplus > 201703L
#include <bit>
@ -435,6 +436,25 @@ QT_POPCOUNT_RELAXED_CONSTEXPR inline uint qCountLeadingZeroBits(unsigned long v)
#undef QT_POPCOUNT_RELAXED_CONSTEXPR
template <typename InputIterator, typename Result, typename Separator = Result,
typename Projection = q20::identity>
Result qJoin(InputIterator first, InputIterator last, Result init, const Separator &separator = {},
Projection p = {})
{
if (first != last) {
init += std::invoke(p, *first);
++first;
}
while (first != last) {
init += separator;
init += std::invoke(p, *first);
++first;
}
return init;
}
QT_END_NAMESPACE
#endif // QALGORITHMS_H

View File

@ -240,3 +240,24 @@
For example, qCountLeadingZeroBits(quint64(1)) returns 63 and
qCountLeadingZeroBits(quint64(8)) returns 60.
*/
/*!
\fn template <typename InputIterator, typename Result, typename Separator = Result, typename Projection = q20::identity> Result qJoin(InputIterator first, InputIterator last, Result init, const Separator &separator, Projection p)
\relates <QtAlgorithms>
\since 6.10
Starts with initial value \a init and cumulatively adds to it each entry in
the range from \a first to \a last. Each entry is, before adding, mapped
through the projection \a p (which defaults to the identity). Between each
entry and the next, an optional \a separator is added. For example:
\code
QList<int> l = {1, 2, 3};
QString res = qJoin(l.cbegin(), l.cend(), QString(), u" / ",
[](int n) { return QString::number(2 * n);});
// res == "2 / 4 / 6"
\endcode
\note \c{q20::identity} is a C++17 backport of C++20's
\l{https://en.cppreference.com/w/cpp/utility/functional/identity}{\c{std::identity}}.
*/

View File

@ -13,13 +13,30 @@ QT_WARNING_DISABLE_DEPRECATED
#include <algorithm>
#include <qalgorithms.h>
#include <QList>
#include <QMap>
#include <QRandomGenerator>
#include <QSet>
#include <QString>
#include <QStringList>
#include <QVarLengthArray>
#define Q_TEST_PERFORMANCE 0
using namespace std;
using namespace Qt::StringLiterals;
template <typename Container, typename T>
Container make(int size, T factor)
{
Container c;
c.reserve(size);
using V = typename Container::value_type;
int i = 0;
std::generate_n(std::inserter(c, c.end()), size, [factor, &i] { return V(++i * factor); });
return c;
}
constexpr auto QString_number = [](auto v) { return QString::number(v); };
class tst_QAlgorithms : public QObject
{
@ -56,6 +73,71 @@ private slots:
void countLeading32() { countLeading_impl<quint32>(); }
void countLeading64() { countLeading_impl<quint64>(); }
void join_QList_int()
{ join_impl(make<QList<int>>(3, 10), u" / ", u"10 / 20 / 30"_s, QString_number); }
void join_QList_float()
{ join_impl(make<QList<float>>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s, QString_number); }
void join_QList_char()
{ join_impl(make<QList<char>>(3, 33), u" / ", u"! / B / c"_s); }
void join_QList_QChar()
{
join_impl(make<QList<QChar>>(3, 33), " / ", std::string("! / B / c"),
[](auto v) { return v.toLatin1(); });
}
void join_QList_pair()
{
join_impl(QList<std::pair<QString, int>>{{u"one"_s, 1}, {u"two"_s, 2}}, u'/' ,
u"one/two"_s, [](const std::pair<QString, int>& p) { return p.first; });
}
void join_QVarLengthArray_int()
{
join_impl(make<QVarLengthArray<int>>(3, 10), " / ", std::string("10 / 20 / 30"),
[](auto v) { return std::to_string(v); });
}
void join_QVarLengthArray_float()
{
join_impl(make<QVarLengthArray<float>>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s,
QString_number);
}
void join_std_vector_int()
{ join_impl(make<std::vector<int>>(3, 10), u" / ", u"10 / 20 / 30"_s, QString_number); }
void join_std_vector_float()
{
join_impl(make<std::vector<float>>(3, 10.1f), u" / ", u"10.1 / 20.2 / 30.3"_s,
QString_number);
}
void join_std_array_int()
{ join_impl(std::array<int, 3> {10, 20, 30}, u" / ", u"10 / 20 / 30"_s, QString_number); }
void join_std_array_float()
{
join_impl(std::array<float, 3> {10.1f, 20.2f, 30.3f}, u" / ", u"10.1 / 20.2 / 30.3"_s,
QString_number);
}
void join_QMap_int()
{ join_impl(QMap<QString, int>{{u"one"_s, 1}, {u"two"_s, 2}}, ' ', u"1 2"_s, QString_number); }
void join_QMap_float()
{
join_impl(QMap<QString, float>{{u"one"_s, 1.1f}, {u"two"_s, 2.2f}}, ' ', u"1.1 2.2"_s,
QString_number);
}
void join_QSet_int()
{ join_impl_qset(make<QSet<int>>(3, 10), u"/"_s, QString_number); }
void join_QSet_float()
{ join_impl_qset(make<QSet<float>>(3, 10.1f), u" - "_s, QString_number); }
void join_InputIterator_words()
{
join_impl_InputIterator<ushort>(std::stringstream("10 20 30"), u" / ", u"10 / 20 / 30"_s,
QString_number);
}
void join_InputIterator_single()
{ join_impl_InputIterator<ushort>(std::stringstream("123"), u'-', u"123"_s, QString_number); }
private:
void popCount_data_impl(size_t sizeof_T_Int);
template <typename T_Int>
@ -68,6 +150,16 @@ private:
void countLeading_data_impl(size_t sizeof_T_Int);
template <typename T_Int>
void countLeading_impl();
template <typename Container, typename Result, typename Separator,
typename Projection = q20::identity>
void join_impl(const Container &input, const Separator &sep, const Result &expectedResult,
Projection p = {});
template <typename T, typename Separator, typename Projection = q20::identity>
void join_impl_qset(const QSet<T> &input, const Separator &sep, Projection p);
template <typename T, typename Result, typename Separator, typename Projection = q20::identity>
void join_impl_InputIterator(std::stringstream ss, const Separator &sep,
const Result &expectedResult, Projection p);
};
template <typename T> struct PrintIfFailed
@ -373,6 +465,40 @@ void tst_QAlgorithms::countLeading_impl()
QCOMPARE(qCountLeadingZeroBits(value), expected);
}
template <typename Container, typename Result, typename Separator, typename Projection>
void tst_QAlgorithms::join_impl(const Container &input, const Separator &sep,
const Result &expectedResult, Projection p)
{
const Result res = qJoin(input.cbegin(), input.cend(), Result{}, sep, p);
QCOMPARE(res, expectedResult);
}
template <typename T, typename Separator, typename Projection>
void tst_QAlgorithms::join_impl_qset(const QSet<T> &input, const Separator &sep, Projection p)
{
// Because a set isn't ordered, check againt an alternative implementation of qJoin()
QString res = qJoin(input.cbegin(), input.cend(), QString(), sep, p);
QString expected;
for (const auto &e : input) {
expected += p(e);
expected += sep;
}
if (!expected.isEmpty())
expected.chop(sep.size());
QCOMPARE(res, expected);
}
template <typename T, typename Result, typename Separator, typename Projection>
void tst_QAlgorithms::join_impl_InputIterator(std::stringstream ss, const Separator &sep,
const Result &expectedResult, Projection p)
{
const Result res = qJoin(std::istream_iterator<T>{ss}, std::istream_iterator<T>{}, Result{},
sep, p);
QCOMPARE(res, expectedResult);
}
QTEST_APPLESS_MAIN(tst_QAlgorithms)
#include "tst_qalgorithms.moc"