How to insert values in set directly from input stream?

Viewed 247

How to Insert input directly into set container from input stream?

This is how I need

while(n--)
{
    cin>>s.emplace();
}

Assume,I need to get n inputs and set container name is 's'

while(n--)
{
    int x;
    cin>>x;
    s.emplace(x);
}

This works fine but I need to cut this step.

2 Answers

Since C++20 you can use std::ranges::copy, std::counted_iterator, std::istream_iterator,std::default_sentinel and std::inserter to do it. The counted_iterator + default_sentinel makes it copy n elements from the stream.

Example:

#include <algorithm> // ranges::copy
#include <iostream>
#include <iterator>  // counted_iterator, default_sentinel, istream_iterator, inserter
#include <set>
#include <sstream>   // istringstream - only used for the example

int main() {
    // just an example istream:
    std::istringstream cin("1 1 2 2 3 3 4 4 5 5");

    int n = 5;

    std::set<int> s;

    std::ranges::copy(
        std::counted_iterator(std::istream_iterator<int>(cin), n),
        std::default_sentinel,
        std::inserter(s, s.end())
    );

    for(auto v : s) std::cout << v << ' ';
}

The output will only contain 3 elements since the first 5 elements in the stream only had 3 unique elements:

1 2 3

Prior to C++20, you could use copy_n in a similar fashion:

std::copy_n(std::istream_iterator<int>(cin), n, std::inserter(s, s.begin()));

Caution: If there are actually fewer than n elements in the stream, both versions will have undefined behavior. Streams are notoriously unpredictable when it comes to delivering exactly what you want and copy_n makes error checking really hard.

To make it safe, you could create a counting_istream_iterator to copy at most n elements from a stream using std::copy like this:

std::copy(counting_istream_iterator<foo>(cin, n),
          counting_istream_iterator<foo>{},
          std::inserter(s, s.end()));

Such an iterator could, based on std::istream_iterator, look something like this:

template<class T,
         class CharT = char,
         class Traits = std::char_traits<CharT>,
         class Distance = std::ptrdiff_t>
struct counting_istream_iterator {
    using difference_type = Distance;
    using value_type = T;
    using pointer = const T*;
    using reference = const T&;
    using iterator_category = std::input_iterator_tag;
    using char_type = CharT;
    using traits_type = Traits;
    using istream_type = std::basic_istream<CharT, Traits>;

    counting_istream_iterator() : // end iterator
        isp(nullptr), count(0) {}

    counting_istream_iterator(std::basic_istream<CharT, Traits>& is, size_t n) :
        isp(&is), count(n + 1)
    {
        ++*this; // read first value from stream
    }
    counting_istream_iterator(const counting_istream_iterator&) = default;
    ~counting_istream_iterator() = default;

    reference operator*() const { return value; }
    pointer operator->() const { return &value; }

    counting_istream_iterator& operator++() {        
        if(count > 1 && *isp >> value) --count;
        else count = 0; // we read the number we should, or extraction failed
        return *this;
    }
    counting_istream_iterator operator++(int) {
        counting_istream_iterator cpy(*this);
        ++*this;
        return cpy;
    }

    bool operator==(const counting_istream_iterator& rhs) const {
        return count == rhs.count;
    }
    bool operator!=(const counting_istream_iterator& rhs) const {
        return !(*this == rhs);
    }

private:
    std::basic_istream<CharT, Traits>* isp;
    size_t count;
    T value;
};

What you could do is create your own reusable function that provides as terse a syntax as you desire. You may as well put the loop in there too.

This way, you can even have correct error handling while keeping you main's code clean and simple.

A fully generic one could look like this:

#include <iostream>
#include <set>
#include <stdexcept>

template<typename ContainerT>
void populate(ContainerT& container, std::istream& stream, std::size_t n) {
  using T = typename ContainerT::value_type;

  while(n--) {
    T tmp;
    if(stream >> tmp) {
      container.insert(container.end(), std::move(tmp));
    }
    else {
      throw std::runtime_error("bad input");
    }
  }
}

int main() {
  std::size_t n = 5;

  std::set<int> s;
  populate(s, std::cin, n);  
}

You could even get a little fancier. For example, containers supporting reserve() could have it called before starting to populate the container:

template<typename T>
concept HasReserve = requires(T x) {
   {x.reserve(std::size_t{})};
};

// ...

ContainerT result;
if constexpr(HasReserve<ContainerT>) {
  container.reserve(container.size() + n);
}

//...


Related