Странное поведение std::reduce

Рейтинг: 2Ответов: 1Опубликовано: 07.05.2023
#include <iostream>
#include <string_view>
#include <numeric>

using namespace std;

int CountWords(string_view str) {
    auto op = [](int accum, char c) -> int {
        return accum + ((c == ' ') ? 1 : 0);
    };

    return std::reduce(str.begin(), str.end(), (int)0, op);
}

int main() {
    // должно вывести 6
    cout << CountWords("pretty little octopus dnskdj kjanfkj snfs jknds"sv) << endl;

    return 0;
}

Объясните пожалуйста, что не так с этим кодом? Почему он выводит 2 вместо 6? Код должен посчитать пробелы в строке

Ответы

▲ 3Принят

Вы перепутали алгоритм. То, что вам нужно ::std::accumulate, a ::std::reduce - это обобщенная сумма. При его выполнении первый элемент в функторе может быть значением символа из входящей строки, а не накопленное значение.

Обратите внимание на требования алгоритма:

T must meet the requirements of MoveConstructible. and binary_op(init, *first), binary_op(*first, init), binary_op(init, init), and binary_op(*first, *first) must be convertible to T.

В этом легко убедиться, напечатав значения, поступающие в функтор:

auto op = [](int accum, char c) -> int {
        ::std::cout << accum << " \"" << c << "\"\n";
        return accum + ((c == ' ') ? 1 : 0);
    };

online compiler

112 "r"
101 "t"
112 "e"
0 "p"
116 "y"
32 "l"
116 " "
0 "u"
105 "t"
116 "l"
105 "t"
...

При вызове accumulate происходит то, что вы предполагаете - для каждого элемента вызывается функтор в котором первым аргументом идет накопитель, а вторым - символ из входящей строки. При вызове reduce в функтор и первым, и вторым аргументом могут подавать и накопитель и символы. reduce может складывать элементы последовательности между собой (а может и не складывать).

Также важное отличие заключается в требовании ко входящим итераторам - accumulate работает с input iterator и поэтому работает всегда последовательно и не поддерживает параллелизм, а reduce имеет перегрузку с forward iterator и может быть выполнен параллельно.

Пример для суммирования массива:

accumulate

4 9 8 5 2 1 0 6
 13 ┘ │ │ │ │ │
   21 ┘ │ │ │ │
     26 ┘ │ │ │
       28 ┘ │ │
         29 ┘ │
           29 ┘
             35

reduce

4 9 8 5 2 1 0 6
 13  13   3   6
   26       9
       35

Для использованиея reduce функтор необходимо переписать

#include <iostream>
#include <string_view>
#include <numeric>

using t_Accum = ::std::size_t;

class t_CountSpaces final
{
    private: auto operator ()(char const ch) const noexcept
    {
        return (' ' == ch) ? t_Accum{1} : t_Accum{0};
    }

    public: auto operator ()(t_Accum const accum1, t_Accum const accum2) const noexcept
    {
        return accum1 + accum2;
    }

    public: auto operator ()(char const ch, t_Accum const accum) const noexcept
    {
        return accum + (*this)(ch);
    }

    public: auto operator ()(t_Accum const accum, char const ch) const noexcept
    {
        return accum + (*this)(ch);
    }

    public: auto operator ()(char const ch1, char const ch2) const noexcept
    {
        return (*this)(ch1) + (*this)(ch2);
    }
};

auto CountWords(::std::string_view str)
{
    return ::std::reduce(str.begin(), str.end(), t_Accum{0}, t_CountSpaces{});
}

int main()
{
    // должно вывести 6
    ::std::cout << CountWords("pretty little octopus dnskdj kjanfkj snfs jknds") << ::std::endl;
    return 0;
}

online compiler

А еще стоит отметить, что ни то, ни другое не могут быть использованы в CountWords для подсчета количества слов. По сути тут считается не количество слов, а количество пробелов (для этого подошел бы ::std::count).