С ++ расширяет вектор другим вектором

Я программист на C / Python на C++, впервые работаю с STL.

В Python для расширения списка другим списком используется метод .extend:

>>> v = [1, 2, 3]
>>> v_prime = [4, 5, 6]
>>> v.extend(v_prime)
>>> print(v)
[1, 2, 3, 4, 5, 6]

В настоящее время я использую этот алгоритмический подход для расширения векторов в C++:

v.resize(v.size() + v_prime.size());
copy(v_prime.begin(), v_prime.end(), v.rbegin());

Это канонический способ расширения векторов или есть более простой способ, который мне не хватает?

Возможный дубликат Объединение двух std :: vectors

Tharindu Kumara 12.12.2016 19:03
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
52
1
36 509
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

От здесь

// reserve() is optional - just to improve performance
v.reserve(v.size() + distance(v_prime.begin(),v_prime.end()));
v.insert(v.end(),v_prime.begin(),v_prime.end());

Я не думаю, что есть специализация vector :: insert для итераторов ввода произвольного доступа, поэтому, если производительность имеет значение, сначала резервируйте ().

Greg Rogers 24.11.2008 08:04

И VC++ 9.0, и GCC 4.3.2 определяют категорию итератора внутренне, поэтому резервировать не нужно.

Vadim Ferderer 17.01.2009 21:07

Я знаю, что ему 8 лет, но есть ли причина, по которой вы использовали distance() вместо просто v_prime.size()?

Holt 04.03.2016 16:43

@Holt Он, вероятно, думал о более общем случае, когда вы хотите расширить вектор на некоторый диапазон элементов. Если вы, например, не хотите использовать все элементы из v_prime, вы можете просто использовать этот код и заменить итераторы.

Apollys supports Monica 18.01.2019 02:20
copy(v_prime.begin(), v_prime.end(), back_inserter(v));

Я думаю, что пространство все еще должно быть зарезервировано () - d для повышения производительности

Dmitry Khalatov 24.11.2008 15:12

+1, так как спрашивающий просил «самый простой», а не «самый быстрый», поэтому резервировать место (хотя стоит упомянуть как вариант) не нужно.

Steve Jessop 24.11.2008 16:23

Я думаю, что решение Дмитрия и проще, и быстрее. Проголосовать за этого парня сейчас :)

Johannes Schaub - litb 24.11.2008 19:44

Этот ответ не следует поощрять. См. «Эффективный STL», пункт 5: Слишком много программистов на STL злоупотребляют copy, поэтому совет, который я только что дал, повторяется: почти все случаи использования copy, когда диапазон назначения указывается с помощью итератора вставки, следует заменить вызовами функций-членов диапазона.

Chris Jester-Young 25.11.2008 11:38

@Chris, таким образом побеждая архитектуру STL.

Frank Krueger 24.03.2009 03:43

Мне понадобились два разных варианта функции extend в C++ 14, один из которых поддерживал семантику перемещения для каждого добавляемого элемента вектора.

vec - это ваш v, а ext - это ваш v_prime.

/**
 * Extend a vector with elements, without destroying source one.
 */
template<typename T>
void vector_extend(std::vector<T> &vec, const std::vector<T> &ext) {
    vec.reserve(vec.size() + ext.size());
    vec.insert(std::end(vec), std::begin(ext), std::end(ext));
}

/**
 * Extend a vector with elements with move semantics.
 */
template<typename T>
void vector_extend(std::vector<T> &vec, std::vector<T> &&ext) {
    if (vec.empty()) {
        vec = std::move(ext);
    }
    else {
        vec.reserve(vec.size() + ext.size());
        std::move(std::begin(ext), std::end(ext), std::back_inserter(vec));
        ext.clear();
    }
}

Есть несколько способов достичь поставленной цели.

std::vector::insert

Вектор может быть расширен путем вставки новых элементов перед элементом в указанной позиции, эффективно увеличивая размер контейнера на количество вставленных элементов. Вы можете следовать одному из следующих подходов. Вторая версия использует C++ 11, и ее можно рассматривать как более общий ответ, поскольку b также может быть массивом.

a.insert(a.end(), b.begin(), b.end());
a.insert(std::end(a), std::begin(b), std::end(b));

Иногда при использовании рекомендуется использовать функцию резерва перед использованием std :: vector :: insert. Функция std :: vector :: резерв увеличивает емкость контейнера до значения, которое больше или равно new_cap. Если new_cap больше текущей емкости (), выделяется новое хранилище, в противном случае метод ничего не делает.

a.reserve(a.size() + distance(b.begin(), b.end()));

Использование резервной функции не требуется, но может быть рекомендовано. И лучше всего использовать резерв, если вы многократно вставляете в вектор, для которого вы знаете окончательный размер, и этот размер большой. В противном случае лучше позволить STL увеличивать ваш вектор по мере необходимости.

std::copy

std :: copy - второй вариант, который вы можете рассмотреть для достижения своей цели. Эта функция копирует элементы диапазона (first, last) в диапазон, начинающийся с result.

std::copy (b.begin(), b.end(), std::back_inserter(a));

Однако использование std :: copy медленнее, чем использование std :: vector :: insert (), потому что std :: copy () не может заранее зарезервировать достаточно места (у него нет доступа к самому вектору, только для итератора, у которого есть), тогда как std :: vector :: insert (), будучи функцией-членом, может. Из-за этого std :: copy действительно медленнее, чем использование std :: vector :: insert. Большинство людей чрезмерно используют std :: copy, не зная об этом сценарии.

boost::push_back

Третий вариант, который вы можете рассмотреть, - это использование функции boost отталкивать.

boost::push_back(a, b);

Использование std::vector::insert;

A.reserve(A.size() + B.size());
A.insert(A.end(), B.begin(), B.end());

reserve() не является обязательным, но его использование помогает повысить производительность.


Удобный генератор кода для экономии драгоценных секунд:

<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel = "stylesheet" href = "https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css"><script src = "https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script><script src = "https://cdn.jsdelivr.net/clipboard.js/1.6.0/clipboard.min.js"></script><script>function generateCode(){codeTemplate = "{0}.reserve({0}.size() + {1}.size()); \n{0}.insert({0}.end(), {1}.begin(), {1}.end());",first=document.getElementById("1").value,second=document.getElementById("2").value,""==first&&(first = "A"),""==second&&(second = "B"),document.getElementById("c").innerHTML=String.format(codeTemplate,first,second)}String.format||(String.format=function(a){var b=Array.prototype.slice.call(arguments,1);return a.replace(/{(\d+)}/g,function(a,c){return"undefined"!=typeof b[c]?b[c]:a})});</script><div class = "A" style = "margin:3% 10% 1% 10%;"><label for = "1">First vector name:</label><input id = "1"/><br/><label for = "1">Second vector name:</label><input id = "2"/><div class = "D"><a class = "waves-effect waves-light btn red col" onclick = "generateCode();" style = "margin:0 0 4% 0;">Generate Code</a></div><textarea id = "c" onclick = "this.select()" style = "border:none;height:auto;overflow: hidden;font-family:Consolas,Monaco;">A.reserve(A.size() + B.size());&#13;&#10;A.insert(A.end(), B.begin(), B.end());</textarea></div>

Это копия ответа stackoverflow.com/a/313444/931303, получившего наибольшее количество голосов, 11 лет спустя. Рассмотрите возможность его удаления.

Jorge Leitao 02.08.2018 23:17

Используйте только следующий синтаксис:

a.insert(a.end(), b.begin(), b.end());

Резерв / изменение размера не следует использовать, если вы не знаете, что делаете.

Резервирование может вызвать огромные накладные расходы, поскольку оно не обязательно приводит к экспоненциальному увеличению размера, поэтому каждый резерв может вызывать время O (n).

Это может быть не очень дорого, если выполнить Только один раз, и в таких случаях может оказаться более эффективным с точки зрения времени \ памяти. С другой стороны, если вы продолжите расширять массив таким образом с относительно меньшими массивами, это докажет, что очень сильно неэффективен. В следующем примере показано простое неправильное использование, которое приводит к увеличению времени в 10 000 раз ?

пример:

#include <vector>
#include <iostream>
#include <chrono>

int main() {
    std::vector<int> a, b(50);
    auto t1 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5e4; i++) {
        a.reserve(a.size() + b.size());      // line in question.
        a.insert(a.end(), b.begin(), b.end());
    }
    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::nanoseconds>( t2 - t1 ).count();

    std::cout << 1.0 * duration / 1e9;
    return 0;
}

//run              time        complexity      speed up
//with reserve     114.558 s   O(N)            x1
//without reserve    0.012 s   O(N^2)          x10000 (~O(N/50))

Скомпилировано с -O3 на gcc 17, intel i5.

Этот ответ мог бы лучше объяснить, что происходит. Без ручного резервирования пространства, т. Е. Позволяя вектору управлять своим пространством, стоимость выделения памяти амортизированный равна O (1). Это достигается за счет экспоненциального распределения памяти. При ручном резервировании размер выделения памяти в этом примере является линейным (еще 50 элементов в каждом распределении), что приводит к затратам O (n) на выделение памяти. Конечно, O (1) быстрее, чем O (n).

Samuel Li 12.11.2020 08:02

Другие вопросы по теме