Соединение ключей / значений из ассоциативных контейнеров C ++ STL - PullRequest
3 голосов
/ 21 июля 2009

У меня есть функция соединения, которая работает со строками STL. Я хочу иметь возможность применить его к контейнеру, как это:

getFoos(const std::multimap<std::string, std::string>& map) {
    return join_values(",", map.equal_range("foo"));

Другими словами, найдите все совпадающие ключи в коллекции и объедините значения в одну строку с заданным разделителем. То же самое с lower_bound() и upper_bound() для диапазона ключей, begin() / end() для всего содержимого контейнера и т. Д.

Самое близкое, что я мог получить, это:

template <typename T>
struct join_range_values : public T::const_iterator::value_type::second_type {
    typedef typename T::const_iterator::value_type pair_type;
    typedef typename pair_type::second_type value_type;

    join_range_values(const value_type& sep) : sep(sep) { }

    void operator()(const pair_type& p) {
        // this function is actually more complex...
        *this += sep;
        *this += p.second;
    }
private:
    const value_type sep;
};

template <typename T>
typename T::const_iterator::value_type::second_type join_values(
    const typename T::const_iterator::value_type::second_type& sep,
    const std::pair<typename T::const_iterator, typename T::const_iterator>& range) {
    return std::for_each(range.first, range.second, join_range_values<T>(sep));
}

(Я понимаю, что наследование от std::string или любого другого типа ключа / значения обычно считается плохой идеей, но я не перегружаю и не перезаписываю никакие функции, и мне не нужен виртуальный деструктор. I ' Я делаю это только для того, чтобы я мог напрямую использовать результат for_each без необходимости определения оператора неявного преобразования.)

Существуют очень похожие определения для join_range_keys, использующие first_type и p.first вместо second_type и p.second. Я предполагаю, что аналогичное определение будет работать для объединения ключей std::set и std::multiset, но мне это не нужно.

Я могу применить эти функции к контейнерам со строками различных типов. Любая комбинация map и multimap с любой комбинацией string и wstring для типов ключей и значений работает:

typedef std::multimap<std::string, std::string> NNMap;
const NNMap col;
const std::string a = join_keys<NNMap>(",", col.equal_range("foo"));
const std::string b = join_values<NNMap>(",", col.equal_range("foo"));

typedef std::multimap<std::string, std::wstring> NWMap;
const NWMap wcol;
const std::string c = join_keys<NWMap>(",", wcol.equal_range("foo"));
const std::wstring d = join_values<NWMap>(L",", wcol.equal_range("foo"));

typedef std::multimap<std::wstring, std::wstring> WWMap;
const WWMap wwcol;
const std::wstring e = join_keys<WWMap>(L",", wwcol.equal_range(L"foo"));
const std::wstring f = join_values<WWMap>(L",", wwcol.equal_range(L"foo"));

Это оставляет меня с несколькими вопросами:

  1. Я упускаю какой-то более простой способ сделать то же самое? Сигнатура функции особенно кажется слишком сложной.
  2. Есть ли способ, чтобы join_values автоматически определял тип параметра шаблона, чтобы мне не приходилось каждый раз вызывать его с join_values<MapType>?
  3. Как я могу реорганизовать функции и функторы join_values и join_keys, чтобы избежать дублирования большей части кода?

Я нашел немного более простое решение, основанное на std::accumulate, но, похоже, требуется две полные операции копирования всей строки для каждого элемента в диапазоне, поэтому, насколько я могу судить, это гораздо менее эффективно.

template <typename T>
struct join_value_range_accum : public T::const_iterator::value_type::second_type
{
    typedef typename T::const_iterator::value_type::second_type value_type;
    join_value_range_accum(const value_type& sep) : sep(sep) {}

    using value_type::operator=;
    value_type operator+(const typename T::const_iterator::value_type& p)
    {
        return *this + sep + p.second;
    }
private:
    const value_type sep;
};

typedef std::multimap<std::string, std::string> Map;
Map::_Pairii range = map.equal_range("foo");
std::accumulate(range.first, range.second, join_value_range_accum<Map>(","));

Ответы [ 2 ]

6 голосов
/ 21 июля 2009

Алгоритмы STL обычно работают с итераторами, а не с контейнерами, поэтому я бы предложил что-то вроде следующего.

template <typename T, typename Iterator>
T join(
    const T sep,
    Iterator b,
    Iterator e)
{
    T t;

    while (b != e)
        t = t + *b++ + sep;

    return t;
}

Затем вам нужен итератор, который будет извлекать ключи или значения. Вот пример:

template <typename Key, typename Iterator>
struct KeyIterator
{
    KeyIterator(
        Iterator i)
        :_i(i)
    {
    }

    KeyIterator operator++()
    {
        ++_i;
        return *this;
    }

    bool operator==(
        KeyIterator ki)
    {
        return _i = ki._i;
    }

    typename Iterator::value_type operator*()
    {
        return _i->first;
    }
};

Как использовать:

string s = join(",", KeyIterator(my_map.begin()), KeyIterator(my_map.end()));
2 голосов
/ 22 июля 2009

Просто к вашему сведению, для всех, кто заинтересовался, я пришел к следующему решению, основанному на вкладе Керабы.

Мне пришлось внести некоторые изменения, в частности:

  1. Создание параметра шаблона T для строки-разделителя именем зависимого типа, чтобы компилятор автоматически определял его (позволяя автоматически преобразовывать заключенные в кавычки литералы в строковые объекты)
  2. Использование функтора, полученного из строки, чтобы обойти тот факт, что зависимое имя, объявленное в итераторе, является const, поэтому локальное временное значение, определенное в join(), в конечном итоге становится const и, следовательно, не изменяется.

template <typename I>
struct MapKeyIterator : public I
{
    typedef typename I::value_type::first_type value_type;
    MapKeyIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->first; }
};

template <typename I>
struct MapValueIterator : public I
{
    typedef typename I::value_type::second_type value_type;
    MapValueIterator(I const &i) : I(i) { }
    value_type const & operator*() const { return (*this)->second; }
};

template <typename I>
struct join_functor : public I::value_type
{
    typedef typename I::value_type value_type;
    join_functor(value_type const &sep) : sep(sep) { }
    void operator()(value_type const &s)
    {
        *this += s;
        *this += sep;
    }
private:
    const value_type sep;
};

template <typename I>
typename I::value_type join(typename I::value_type const &sep, I beg, I const &end)
{
    return std::for_each(beg, end, join_functor<I>(sep));
}

template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapKeyIterator<I>(beg), MapKeyIterator<I>(end));
}
template <typename I>
typename I::value_type::first_type join_keys(typename I::value_type::first_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapKeyIterator<I>(ip.first), MapKeyIterator<I>(ip.second));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, I const &beg, I const &end)
{
    return join(sep, MapValueIterator<I>(beg), MapValueIterator<I>(end));
}
template <typename I>
typename I::value_type::second_type join_values(typename I::value_type::second_type const &sep, std::pair<I, I> const &ip)
{
    return join(sep, MapValueIterator<I>(ip.first), MapValueIterator<I>(ip.second));
}

Это позволяет:

join_keys(",", map.equal_range("foo"));
join_values(",", map.equal_range("foo"));
join_values(",", map.begin(), map.end());

а также:

join(",", set.lower_bound("f"), set.upper_bound("g"));

с контейнерами на основе std::string или std::wstring.

Это все еще довольно сложно, но оно решает пункты 2 и 3 из моего первоначального поста, и кажется, что оно намного лучше соответствует дизайну STL.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...