Introduction: Understanding Data Streams

Warm greetings! This lesson introduces data streams, which are essentially continuous datasets. Think of a weather station or a gaming application gathering data per second — both are generating data streams! We will master handling these data streams using C++, learning to access elements, slice segments, and even convert these streams into strings for easier handling.

Representing Data Streams in C++

In C++, data streams can be represented using vectors and maps from the STL (Standard Template Library). Let’s consider a straightforward C++ class named DataStream. This class encapsulates operations related to data streams in our program:

#include <iostream>
#include <vector>
#include <map>
#include <string>

class DataStream {
public:
    DataStream(const std::vector<std::map<std::string, int>>& data) : data(data) {}

private:
    std::vector<std::map<std::string, int>> data;
};

To use it, we create a sample data stream as an instance of our DataStream class, where each element is a map:

std::vector<std::map<std::string, int>> data = {
    {{"id", 1}, {"value", 100}},
    {{"id", 2}, {"value", 200}},
    {{"id", 3}, {"value", 300}},
    {{"id", 4}, {"value", 400}},
};
DataStream stream(data);
Accessing Elements - Key Operation

To look into individual elements of a data stream, we use indexing. The get() method we introduce below fetches the i+1-th element from the data stream:

#include <stdexcept>  // For std::out_of_range

class DataStream {
public:
    DataStream(const std::vector<std::map<std::string, int>>& data) : data(data) {}

    std::map<std::string, int> get(int i) const {
        if (i < 0) {
            i += data.size();
        }
        if (i >= 0 && i < data.size()) {
            return data[i];
        } else {
            throw std::out_of_range("Index out of range");
        }
    }

private:
    std::vector<std::map<std::string, int>> data;
};

Here, we can see the get() method in action:

int main() {
    std::vector<std::map<std::string, int>> data = {
        {{"id", 1}, {"value", 100}},
        {{"id", 2}, {"value", 200}},
        {{"id", 3}, {"value", 300}},
        {{"id", 4}, {"value", 400}},
    };
    DataStream stream(data);

    try {
        std::map<std::string, int> elem = stream.get(2);
        std::cout << "id: " << elem["id"] << ", value: " << elem["value"] << std::endl;

        elem = stream.get(-1);
        std::cout << "id: " << elem["id"] << ", value: " << elem["value"] << std::endl;
    } catch (const std::out_of_range& e) {
        std::cerr << e.what() << std::endl;
    }
}

In essence, stream.get(2) fetched us {"id": 3, "value": 300} — the third element (since indexing starts from 0). At the same time, stream.get(-1) fetches the last element, which is {"id": 4, "value": 400}.

The code snippet if (i < 0) { i += data.size(); } is particularly relevant here. It allows negative indexing by converting a negative index into the corresponding positive index, effectively letting -1 refer to the last element, -2 to the second last, and so on. This approach is useful for accessing elements from the end of the data stream efficiently.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal