2

Цель – в одну строку скопировать все значения из std::map в std::vector.

Да, вместо использования std::copy можно просто в цикле перебрать все значения словаря и закинуть их в вектор. Тем не менее, интересует именно реализация с помощью std::copy.

Минимальный пример:

#include <iostream>
#include <string>
#include <map>
#include <algorithm>

int main() { std::map<int, std::string> m; m[0] = "Hello"; m[7] = "Heey"; m[-59] = "R"; m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";

std::vector&lt;std::string&gt; res;

//  Хочется, чтобы строка работала как-то так, но проблема в том, что элементы словаря – пары, а не только значения
// std::copy(m.begin(), m.end(), res.begin());

// Здесь планирую получить вывод в виде всех значений словаря m
for (std::vector&lt;std::string&gt;::const_iterator elem = res.begin(); elem != res.end(); ++elem)
    std::cout &lt;&lt; *elem &lt;&lt; std::endl;

return 0;

}

Таким образом, с моей точки зрения, вопрос сводится к следующему: как получить итератор на последовательность значений словаря, не беря во внимание ключи? То есть, чтобы, разыменовав итератор, получать не std::pair, а std::string в моём случае.

P.S. Использую C++07, то есть, мне не доступен функционал, появившийся в C++11 (Просьба не спрашивать, почему. Так нужно и нужно не мне.)

V-Mor
  • 5,127
  • @user7860670 спасибо, поправил. Основывался на https://ru.stackoverflow.com/questions/415383/%D0%9F%D0%BE%D0%BB%D0%BD%D1%8B%D0%B9-%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA-%D1%81%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BE%D0%B2-%D0%A1-%D0%A1 – V-Mor Sep 01 '20 at 04:45
  • 3
    /минутка занудства/ с++/tr1 не является ни самостоятельной версией языка ни стандартом оного, как таковым... а также название «c++07» ни где не фигурирует и не является общеупотребительным... это скорее предложение с расширениями стандартной библиотеки, которые позже войдут в с++11... – Fat-Zer Sep 01 '20 at 06:54
  • Тоже не понял, что такое C++07. Для стандартов же есть канонический вопрос-ответ здесь Где взять стандарт С++? – dIm0n Sep 01 '20 at 07:10

2 Answers2

7

В С++ итератора, для значений словаря из коробки нет до сих пор (ещё не смотрел, поправят ли что-то в C++20). Печально, но факт. Зато своя обёртка для этого делается на коленке довольно просто. В элементарном случае это будет выглядеть как-то так:

template <typename T>
class second_unwrap_iterator : public T {
public:
  second_unwrap_iterator(T base)
    : T(base)
  { }

const typename T::value_type::second_type& operator() { return T::operator().second; } };

template <typename T> second_unwrap_iterator<T> second_unwraper(T base) { return second_unwrap_iterator<T>(base); }

// ...

std::copy(second_unwraper(m.cbegin()), second_unwraper(m.cend()), std::back_inserter(res));

second_unwraper() необходим только для того чтобы не указывать параметры шаблона в конструкторе. Начиная с С++17 вывод типов в шаблонах улучшили и можно обойтись без оного:

std::copy(second_unwrap_iterator(m.begin()), second_unwrap_iterator(m.end()), 
          std::back_inserter(res));
Fat-Zer
  • 23,138
  • Не думаю, что итератора, для значений словаря из коробки будут иметь обширное использование => такого рода поправок не ожидаю ... – AR Hovsepyan Sep 01 '20 at 09:17
  • @ARHovsepyan, я скорее ожидаю, что boost'овский transform_iterator добавят... – Fat-Zer Sep 01 '20 at 09:22
  • не спорю, но мне так кажется. А что вы думаете по поводу итератора, умеющий проходить по коллекции с заданными срезами?... – AR Hovsepyan Sep 01 '20 at 09:37
  • @ARHovsepyan, ты имеешь в виду итерировать по срезам массива заданной длины? если честно, как-то не задумывался об этом... в состоянии «на сейчас» не особо понятно, что operator* должен возвращать... пару итераторов? а в состоянии «скоро», т.к. вроде как Range'и и boost::adaptors перетаскивают в стандарт (но я пока не разбирался, что там грядёт), то в принципе можно сделать такой адаптер... – Fat-Zer Sep 01 '20 at 10:14
  • Да, к примеру я часто выполняю экономические расчеты, а там матрицы часто используются, и у меня класс матрицы использует итератор, у которого есть методы void set_mode(const std::gslice& ) noexcept и void set_mode(const std::slice& ) noexcept, после вызова которых итератор ходит по заданным срезам. Очень удобно (могу итерировать практически как хочу), даже при наличии всех соответствующих методов матричного класса. – AR Hovsepyan Sep 01 '20 at 11:16
  • Ещё typename перед T::value_type с 20 необязательно писать – dIm0n Sep 01 '20 at 14:10
3

К сожалению std::copy для этой цели не годится. Рекомендую воспользоваться алгоритмом std::for_each или std::transform. Для старого стандарта придется написать функтор, а для С++11 и выше достаточно будет лямбды:

  1. Пример решения с for_each:

    #include <iostream>
    #include <string>
    #include <map>
    #include <algorithm>
    

    //Функтор для вставки в вектор (в С++11 в нем нет необходимости) class BackInserter { public: explicit BackInserter(std::vector<std::string>& vec) : m_ref_vec(vec) {}

    void operator()(const std::pair&lt;int, std::string&gt;&amp; element)
    {
        m_ref_vec.push_back(element.second);
    }
    
    

    private: std::vector<std::string>& m_ref_vec; };

    int main() { std::map<int, std::string> m; m[0] = "Hello"; m[7] = "Heey"; m[-59] = "R"; m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";

    std::vector&lt;std::string&gt; res;
    BackInserter fInserter(res);
    
    std::for_each(m.begin(), m.end(), fInserter);
    
    // C++11 
    //std::for_each(
    //   m.begin(), 
    //   m.end(), 
    //   [&amp;res](const std::pair&lt;int, std::string&gt;&amp; element)
    //{
    //   res.push_back(element.second);
    //});
    
    // Здесь планирую получить вывод в виде всех значений словаря m
    for (std::vector&lt;std::string&gt;::const_iterator elem = res.begin(); elem != res.end(); ++elem)
        std::cout &lt;&lt; *elem &lt;&lt; std::endl;
    
    return 0;
    

    }

  2. Пример решения с transform:

    #include <iostream>
    #include <string>
    #include <map>
    #include <algorithm>
    

    class TransformFunctor { public: std::string operator()(const std::pair<int, std::string>& element) { return element.second; } };

    int main() { std::map<int, std::string> m; m[0] = "Hello"; m[7] = "Heey"; m[-59] = "R"; m[1024] = "Rrrrrrrrroooooooooocccccccckkkkkk";

    std::vector&lt;std::string&gt; res;
    res.resize(m.size());
    std::transform(m.begin(), m.end(), res.begin(), TransformFunctor());
    
    //C++11
    //std::transform(
    //    m.begin(), 
    //    m.end(),
    //    res.begin(),
    //    [](const std::pair&lt;int, std::string&gt;&amp; element)-&gt;std::string
    //{
    //    return element.second;
    //});
    
    
    // Здесь планирую получить вывод в виде всех значений словаря m
    for (std::vector&lt;std::string&gt;::const_iterator elem = res.begin(); elem != res.end(); ++elem)
        std::cout &lt;&lt; *elem &lt;&lt; std::endl;
    
    return 0;
    

    }

  • А почему не transform? – Andrej Levkovitch Sep 01 '20 at 06:03
  • можно и std::transform, это дело вкуса. Если есть необходимость, могу дополнить ответ. – Alexey Nikolaev Sep 01 '20 at 06:21
  • я скорее не из фкуса, а из того, что: 1) for_each морально устарел после появления range based циклов; 2) с transform код будет меньше и лаконичнее (если, конечно, вопрошающий знаком с лямбдами) – Andrej Levkovitch Sep 01 '20 at 06:27
  • range base как и лямбды появились в C++11, поэтому для решения с функторами большой разницы нет. Добавил в ответ реализацию с transform – Alexey Nikolaev Sep 01 '20 at 06:38
  • Отформатируйте первый пример (там лишний пробел перед каждой строчкой) – dIm0n Sep 01 '20 at 07:09