20

Does the C++ standard library have an "ordered set" datastructure? By ordered set, I mean something that is exactly the same as the ordinary std::set but that remembers the order in which you added the items to it.

If not, what is the best way to simulate one? I know you could do something like have a set of pairs with each pair storing the number it was added in and the actual value, but I dont want to jump through hoops if there is a simpler solution.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249

6 Answers6

17

No single, homogeneous data structure will have this property, since it is either sequential (i.e. elements are arranged in insertion order) or associative (elements are arranged in some order depending on value).

The best, clean approach would perhaps be something like Boost.MultiIndex, which allows you to add multiple indexes, or "views", on a container, so you can have a sequential and an ordered index.

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • +1 for recommending Boost.MultiIndex -- it really is the best solution to these sorts of questions. – ildjarn Oct 18 '11 at 00:25
  • You could use a lambda for the comparison function when initializing the set. He wants uniqueness of elements but in order of insertion. I think this may be doable? I don't understand why this would be prohibited. – dylan Sep 15 '17 at 21:36
  • Maybe use a vector and the unique algorithm to remove non unique elements. Or is this slower than the other methods? I guess a search algoithm would be slower than simply storing the index. – dylan Sep 15 '17 at 21:45
  • @dylan: Yes, but that wouldn't be a single container. You can create any behaviour imaginable with an array and algorithms (computer memory is just one big array)... – Kerrek SB Sep 15 '17 at 23:10
  • This is incorrect. You could have a BST with the values inserted only relative to other values. – Andrew Apr 14 '22 at 08:29
7

Instead of making a std::set of whatever type you're using, why not pass it a std::pair of the object and an index that gets incremented at each insertion?

pg1989
  • 1,010
  • 6
  • 13
  • 2
    That would sort of work, if you took good care when inserting existing values, and if you somehow had a policy how to find the biggest index in constant time. Perhaps a `std::map` would be slightly more elegant, as its value type is already a pair (and you get mutable mapped elements). – Kerrek SB Oct 18 '11 at 00:31
  • 1
    @SethCarnegie: Mind you, you still have to be able to find the highest current index, and that may not actually be possible in constant, or even logarithmic time. – Kerrek SB Oct 18 '11 at 00:43
  • @Kerrek why? If you start from 0 (which I am) you can use `map::size` to find the largest index right? – Seth Carnegie Oct 18 '11 at 00:58
  • @SethCarnegie: Yes, indeed, you could use `size()`, I hadn't thought of that! – Kerrek SB Oct 18 '11 at 01:09
  • 1
    Kerrek's other answer is still better in general, since Boost.MultiIndex will let you *efficiently* access the container indexed by insertion order. As things stand with this `map`, it's an `O(n)` operation to find the element with a given index. Iterating in insertion order is `O(n^2)` with no additional memory, or `O(n log n)` with `O(n)` additional memory if you copy the whole lot into an array and sort before iterating. Might be fine for Seth's purposes, but quite restrictive... – Steve Jessop Oct 18 '11 at 09:40
  • @KerrekSB The OP seems to indicate he **only** wants iteration by _insertion order_. So a solution for `took good care when inserting existing values` would be great. I might take to provide a seperate answer for that. – Opher Mar 06 '23 at 17:00
3

No, it does not.

Such a container presumably would need two different iterators, one to iterate in the order defined by the order of adding, and another to iterate in the usual set order. There's nothing of that kind in the standard libraries.

One option to simulate it is to have a set of some type that contains an intrusive linked list node in addition to the actual data you care about. After adding an element to the set, append it to the linked list. Before removing an element from the set, remove it from the linked list. This is guaranteed to be OK, since pointers to set elements aren't invalidated by any operation other than removing that element.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
0

I thought the answer is fairly simple, combine set with another iteratable structure (say, queue). If you like to iterate the set in the order that the element been inserted, push the elements in queue first, do your work on the front element, then pop out, put into set.

Jun
  • 171
  • 4
  • 16
0

[Disclaimer: I have given a similar answer to this question already]

If you can use Boost, a very straightforward solution is to use the header-only library Boost.Bimap (bidirectional maps).

Consider the following sample program that will display some dummy entries in insertion order (try out here):

#include <iostream>
#include <string>
#include <type_traits>
#include <boost/bimap.hpp>

using namespace std::string_literals;

template <typename T>
void insertByOrder(boost::bimap<T, size_t>& mymap, const T& element) {
  using pos = typename std::remove_reference<decltype(mymap)>::type::value_type;
  // We use size() as index, therefore indexing the elements with 0, 1, ...
  mymap.insert(pos(element, mymap.size()));
}

int main() {
  boost::bimap<std::string, size_t> mymap;

  insertByOrder(mymap, "stack"s);
  insertByOrder(mymap, "overflow"s);

  // Iterate over right map view (integers) in sorted order
  for (const auto& rit : mymap.right) {
    std::cout << rit.first << " -> " << rit.second << std::endl;
  }
}

The funky type alias in insertByOrder() is needed to insert elements into a boost::bimap in the following line (see referenced documentation).

andreee
  • 4,459
  • 22
  • 42
-3

Yes, it's called a vector or list (or array). Just appends to the vector to add element to the set.

Lie Ryan
  • 62,238
  • 13
  • 100
  • 144
  • 1
    Yeah, but all of those allow for redundant elements, which is most likely what the OP is trying to avoid. – pg1989 Oct 18 '11 at 00:24
  • 1
    pg1989 is correct, I dont want duplicate elements and I dont want to linear search the array to check. – Seth Carnegie Oct 18 '11 at 00:28