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:
parent
929466ba64
commit
01fc6aeaff
@ -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
|
||||
|
@ -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}}.
|
||||
*/
|
||||
|
@ -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"
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user