Как получить те элементы вектора, где другой вектор имеет «1» в соответствующих индексах

у меня два вектора

std::vector<int>   markedToBeRead(7);   // contains: 1,1,0,0,1,0,1
std::vector<float> myVec(7);            // contains: 1,2,3,4,5,6,7

Как лучше всего получить те элементы из myVec, где соответствующие индексы markedToBeRead имеют значение 1.
Возможно ли это без использования цикла for, но с помощью stl-методов?

std::vector<float> myResult;            // contains: 1,2,5,7

Благодарю вас!


person Massoud    schedule 19.06.2012    source источник
comment
Также опубликуйте все, что вы пробовали.   -  person iammilind    schedule 19.06.2012
comment
@Potatoswatter, предположительно, они попадают в myResult.   -  person juanchopanza    schedule 19.06.2012
comment
Вы застряли с vector или могли бы вместо этого использовать valarray? Если это так, вы можете использовать перегрузку operator[], которая возвращает masked_array — это именно то, что вы здесь делаете.   -  person Björn Pollex    schedule 19.06.2012
comment
Спасибо за ваши быстрые ответы! Я пробовал именно это, что @Mohammad предложил в своем ответе. Но я пытаюсь найти способ без цикла for, если это возможно. Мне нравится использовать собственные методы STL. @Björn Pollex Я не зациклен на векторе. Хорошо, я почитаю справку о masked_arrayдля val_array   -  person Massoud    schedule 19.06.2012
comment
@BjörnPollex и все: что, если markedToBeRead будет явно содержать индексы? как 2_ ?   -  person Massoud    schedule 19.06.2012
comment
Это также напрямую поддерживается valarray.   -  person Björn Pollex    schedule 19.06.2012


Ответы (5)


Очевидно, что здесь предпочтительнее использовать простой цикл for, а не какой-либо алгоритм STL.

Но просто как доказательство концепции можно взять здесь stl::equals и лямбду из C++11:

std::equal(myVec.begin(), myVec.end(), markedToBeRead.begin(), [&](float item, int mark)->bool {
    if (mark)
        myResult.push_back(item);
    return true;
});

Это работает, но выглядит некрасиво.

person Gart    schedule 19.06.2012
comment
Одним из возможных преимуществ является то, что это работает с любыми последовательностями, а не только с std::vector. - person Gart; 19.06.2012
comment
Я не согласен (категорически) с первым предложением. Прямой алгоритм всегда предпочтительнее цикла, это просто более высокий уровень абстракции. Но поскольку прямого алгоритма не существует, этот вопрос является спорным. - person Konrad Rudolph; 19.06.2012
comment
@Konrad: странно, я согласен как с вашим утверждением (прямой алгоритм был бы предпочтительнее цикла, если бы он существовал), так и с утверждением Гарта (цикл был бы предпочтительнее любого стандартного алгоритма). Я не вижу никакого противоречия. Как вы заметили, ни один стандартный алгоритм не является простым. Ключ, я думаю, заключается в том, чтобы скрыть безобразие (будь то цикл или непрямой алгоритм) внутри шаблона функции, оформленного в стиле стандартных алгоритмов. Назовите его select или что-то в этом роде и возьмите выходной итератор вместо вызова push_back. - person Steve Jessop; 19.06.2012
comment
@Gart: я не думаю, что std::equal дает какие-либо гарантии относительно порядка сравнений, не так ли? Тот факт, что он работает с InputIterator, конечно, очень наводит на размышления, но теоретически он может выполнять диспетчеризацию на типе итератора. Я подозреваю, что это один из тех случаев, когда он действительно работает везде, хотя и не гарантируется. - person Steve Jessop; 19.06.2012
comment
@Steve Jessop: Конечно, в этом примере злоупотребляют std::equal, и я не знаю, действительно ли ОП нуждается в строгой гарантии заказа. Да, теоретически он может дать все те же результаты, но в неправильном порядке, но практически - маловероятно. - person Gart; 19.06.2012
comment
UPD: Мы можем использовать std::mismatch вместо std::equal - я думаю, что это дает больше гарантий относительно порядка сравнений - person Gart; 19.06.2012

Вот как я бы написал алгоритм для этого:

template <typename I, typename O, typename M>
void mask_copy(I begin, I end, O obegin, M mbegin) {
    for (; begin != end; ++begin, ++mbegin)
        if (*mbegin)
            *obegin++ = *begin;
}

Вызывается так:

int a[] = { 1, 2, 3, 4, 5, 6, 7, 8 , 9 };
bool m[] = { true, false, false, false, true, false, false, true, false };

std::vector<int> out;
mask_copy(begin(a), end(a), back_inserter(out), begin(m));

(Требуется C++11 для std::begin и std::end.)

Тем не менее, правильная реализация в библиотеке, вероятно, будет использовать enable_if (или static_assert), чтобы гарантировать, что используемые типы итераторов совместимы с его использованием, т. е. что I является входным итератором, O совместимым выходным итератором. и M итератор ввода, у которого value_type равно bool. К сожалению, отсутствие концепций приводит к настоящему взрыву шаблона.

person Konrad Rudolph    schedule 19.06.2012
comment
Мне нравится это решение - оно универсальное и четко определенное - person Gart; 19.06.2012

С функциональной точки зрения это просто: это zip-архив двух входных диапазонов, за которым следует фильтр на отметке, равной 1, а затем карта для извлечения только значения.

К сожалению, стандартные алгоритмы C++ не очень хорошо подходят для композиции. Если вы не возражаете против создания промежуточных контейнеров, вы можете применить двоичную версию transform, за которой следует copy_if (или remove_copy_if в C++03 с обратным предикатом, или remove_if, чтобы изменить ваш промежуточный контейнер на месте), а затем унарная версия transform.

В качестве альтернативы Boost предоставляет первые две операции в виде адаптеров итераторов. Что-то вроде этого (не проверено):

struct marked {
    bool operator()(boost::tuple<int, float> t) {
        return t.get<0>() == 1;
    }
};

auto first = boost::make_zip_iterator(boost::make_tuple(markedToBeRead.begin(), myVec.begin());
auto last = boost::make_zip_iterator(boost::make_tuple(markedToBeRead.end(), myVec.end());

std::transform(
    boost::make_filter_iterator<marked>(first, last),
    boost::make_filter_iterator<marked>(last, last),
    std::back_inserter(myResults);
    [](boost:tuple<int, float> t) { return t.get<1>(); }
);

Вы, вероятно, уже убедились (а) что цикл лучше, и (б) замена циклов другими конструкциями — это что-то вроде зрелищного спорта в C++ ;-)

Если вам нужно связать дальнейшие операции, то std::transform также можно заменить адаптером итератора: transform_iterator.

person Steve Jessop    schedule 19.06.2012
comment
В голове я хотел сделать это, потому что знал, как это просто в Python. После 15-минутного путешествия туда и обратно, чтобы узнать об адаптерах итераторов повышения, а затем задаться вопросом, упростит ли auto код, вот он передо мной. +1 - person Phil H; 19.06.2012
comment
Кстати, самое замечательное в этом методе то, что он ленив, поэтому весь отфильтрованный диапазон может быть передан методу без дополнительных требований к памяти и т. д. - person Phil H; 19.06.2012
comment
@PhilH: Согласен. Почему C++ делает (p[1] for p in zip(markedToBeRead, myVec) if p[0] == 1) таким сложным? - person Steve Jessop; 19.06.2012
comment
Чтобы отсеять всех, у кого нет выносливости. - person Phil H; 19.06.2012

что-то вроде этого?

for (unsigned int i = 0; i < myVec.length(); i++)
    if (markedToBeRead[i] == 1)
        myResult.push_back(myVec[i]);
person Mohammad    schedule 19.06.2012
comment
Просто и прямо в точку. Я думаю, что любые ответы, использующие стандартные алгоритмы, в конечном итоге будут менее ясными (хотя я обычно выступаю за использование алгоритмов вместо явных циклов). - person Luc Touraille; 19.06.2012
comment
@ Мохаммад Спасибо за ваше предложение. Да, я уточнил свой вопрос :-). Хорошо, так что продолжим использовать это предложение для цикла. - person Massoud; 19.06.2012

Это работает для меня:

#include <algorithm>
#include <iostream>
#include <vector>

int main()  {
        std::vector<int>   markedToBeRead = {  1,1,0,0,1,0,1 };
        std::vector<float> myVec = { 1, 2, 3, 4, 5, 6, 7};

        // copy
        std::vector<float> result;
        std::copy_if(myVec.begin(), myVec.end(),
                std::back_inserter(result), [&](const float & f) {
                        return markedToBeRead[&f - &myVec[0]] == 1;
                });

        // Check result
        for (std::vector<float>::size_type i = 0; i < result.size(); ++i)
                std::cout << result[i] << " ";
}

Проверено на Ideone.

person StackedCrooked    schedule 19.06.2012
comment
Для индекса следует использовать size_t, а не unsigned -- гигантские векторы в 64-битных реализациях и все такое. - person Steve Jessop; 19.06.2012
comment
Хорошо, исправлено удалением локальной переменной. - person StackedCrooked; 19.06.2012