Home > other >  How to insert values in set directly from input stream?
How to insert values in set directly from input stream?

Time:11-10

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.

CodePudding user response:

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 make sure that at most n elements are copied 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, the copy_n version 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.

I made some observations in two of the major implementations of what undefined behavior can mean:

  • libstc (gcc): If the stream is absolutely empty the above operation will insert one uninitialized element in the set. Since an istream_iterator (constructed with a stream) reads one element from the stream when it's constructed, one can check the state of the stream after constructing the istream_iterator and everything seems to work fine:

    if(std::istream_iterator<foo> it(cin); cin)
         std::copy_n(it, n, std::inserter(s, s.end()));
    

    Since I've never used copy_n on an empty stream before, but from streams with too few elements for copy_n, I have falsely believed that this operation was safe. The above seems to work no matter what I throw at it - but it's undefined behavior.

  • libc (clang): Makes a reference binding to null pointer and croaks when it tries to use that reference in operator>>.

I made a little test bench to explore what happens in these two implementations, and I might as well share that if someone else want to play with it: UB playground @ Compiler Explorer

CodePudding user response:

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