c++c++20stdvectorstl-algorithmstd-ranges

How to filter and transform cpp vector to another type of vector?


I have a class called InfoBlob and two enums called Action and Emotion. My function is supposed to take in a vector<InfoBlobs> blobs, and return a vector<Action> actions, corresponding to certain attributes of the blob. However, I have to perform this conversion only in the range of 'first HAPPY blob to the last SAD blob'. Further, within that range, it can only convert blobs that are either HAPPY or SAD. I have 2 questions:

  1. How do I apply this to only the range of 'first HAPPY blob to last SAD blob'
  2. How do I transform the vector of InfoBlobs to the vector of actions given the filter condition 'The blobs must be either HAPPY or SAD'.

Right now I am trying to filter it and then transform but I get the error

error: no match for ‘operator=’ (operand types are ‘std::vector<ex4::Action>’ and ‘std::ranges::transform_view<std::ranges::filter_view<std::ranges::ref_view<std::vector<ex4::InfoBlob> >, task03(std::vector<ex4::InfoBlob>)::<lambda(ex4::InfoBlob)> >, task03(std::vector<ex4::InfoBlob>)::<lambda(ex4::InfoBlob)> >’)
   35 |     });

This is the class:

enum class Emotion : char {
  HAPPY, SAD, PERPLEXED, STRESSED
};

enum class Action : char {
  RUN, LAUGH, WORRY, WEEP, PLAN, PLOT, READ
};


class InfoBlob {
public:
  InfoBlob(int entropy, float spin, Emotion emotion) noexcept
    : entropy{entropy},
      spin{spin},
      emotion{emotion}
      { }

  int getEntropy()     const noexcept { return entropy; }
  float getSpin()      const noexcept { return spin; }
  Emotion getEmotion() const noexcept { return emotion; }

  
  operator==(const InfoBlob& other) const noexcept {
    return entropy == other.entropy
      && spin == other.spin
      && emotion == other.emotion;
  }

This is my code:


#include "task03.h"
#include <iostream>
#include <algorithm>
#include <ranges>

using namespace ex4;



std::vector<ex4::Action> task03(std::vector<InfoBlob> blobs){
    
    std::vector<ex4::Action> actions;

    actions = blobs 
    | std::ranges::views::filter([](ex4::InfoBlob blob){
        return blob.getEmotion() == ex4::Emotion::HAPPY || blob.getEmotion() == ex4::Emotion::SAD;
    })
    | std::ranges::views::transform([](ex4::InfoBlob blob){
                        if(blob.getSpin() > blob.getEntropy())
                            return ex4::Action::PLOT;
                        else 
                            return ex4::Action::RUN;
    });

    
return actions;
}

Solution

  • How do I apply this to only the range of 'first HAPPY blob to last SAD blob'

    You want drop_while. To remove elements past the last SAD blob, you can do a reverse, then a drop_while, then another reverse.

    How do I transform the vector of InfoBlobs to the vector of actions given the filter condition 'The blobs must be either HAPPY or SAD'

    You've figured out the filter part, so what remains is to create a new vector from a view. See c++20 ranges view to vector for a general answer to this problem, but in this case I believe you can just use the iterator pair constructor (you don't know the size in advance, so no missed reserve opportunities, and you shouldn't need a common_view, since the views used model common_range when their underlying views do). C++23 std::ranges::to should make this nicer.

    Working code:

    template<typename Pred>
    constexpr auto drop_while_end(Pred &&pred) {
        using namespace std::views;
        return reverse | drop_while(std::forward<Pred>(pred)) | reverse;
    }
    
    std::vector<Action> task03(std::vector<InfoBlob> const &blobs) {
        constexpr auto notHappy = [](InfoBlob const &blob) { return blob.getEmotion() != Emotion::HAPPY; };
        constexpr auto notSad = [](InfoBlob const &blob) { return blob.getEmotion() != Emotion::SAD; };
        constexpr auto happyOrSad = [](InfoBlob const &blob) {
            return blob.getEmotion() == Emotion::HAPPY || blob.getEmotion() == Emotion::SAD;
        };
        constexpr auto blobToAction = [](InfoBlob const &blob) {
            return blob.getSpin() > blob.getEntropy() ? Action::PLOT : Action::RUN;
        };
    
        using namespace std::views;
        auto actions = blobs
                       | drop_while(notHappy)
                       | drop_while_end(notSad)
                       | filter(happyOrSad)
                       | transform(blobToAction);
        return {actions.begin(), actions.end()};
    }