4

If I have vector<int> foo and vector<int> bar both of which are sorted, and I want to merge them into foo such that the final result is sorted, does the standard provide me a method for doing this?

Obviously I can do:

foo.insert(foo.end(), bar.begin(), bar.end());
sort(foo.begin(), foo.end());

But I was hoping there was a one step algorithm to accomplish this.

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 6
    http://en.cppreference.com/w/cpp/algorithm/merge – Mat May 13 '15 at 14:11
  • @Mat: Does this make some sort of merge sort? – npinti May 13 '15 at 14:12
  • @Mat From http://en.cppreference.com/w/cpp/algorithm/merge : "The behavior is undefined if the destination range overlaps either of the input ranges (the input ranges may overlap each other)." – Jonathan Mee May 13 '15 at 14:13
  • 2
    Merge into a temp, then swap. – Mat May 13 '15 at 14:14
  • @JonathanMee: So merge them into a new vector. Then move it to `foo` if you want the result there. – Mike Seymour May 13 '15 at 14:14
  • @Mat That is a worse solution than what I already have suggested in the question because it requires the creation of another temporary `vector`. Furthermore it does not satisfy the request for a "one step algorithm to accomplish this." – Jonathan Mee May 13 '15 at 14:16
  • 1
    Worse in what sense? Time complexity - I don't think so. Memory overhead - yes, probably. swap is constant time and extremely quick. (If you're trying to save one line of code, ...) – Mat May 13 '15 at 14:18
  • 1
    @JonathanMee Is `std::list::merge` ([cppref](http://en.cppreference.com/w/cpp/container/list/merge)) a candidate for your one-liner? – TobiMcNamobi May 13 '15 at 14:22
  • Are you asking for in-place merge? – Lingxi May 13 '15 at 14:24
  • @Lingxi Yeah, one that I don't have to do two steps to acomplish – Jonathan Mee May 13 '15 at 15:16
  • @TobiMcNamobi Wow, I didn't know about `list::merge`. That's exactly what I want but it looks like it's only available for `list`s. – Jonathan Mee May 13 '15 at 18:14

4 Answers4

6

To elaborate on Mat's comment your code could look like this using std::merge:

std::vector<int> result;
std::merge(
    foo.begin(), foo.end(),
    bar.begin(), bar.end(),
    std::back_inserter(result));

foo = result; // if desired
TobiMcNamobi
  • 4,687
  • 3
  • 33
  • 52
  • That is a worse solution than what I already have suggested in the question because it requires the creation of another temporary `vector`. Furthermore it does not satisfy the request for a "one step algorithm to accomplish this." – Jonathan Mee May 13 '15 at 14:17
  • 1
    It could be made more efficient by pre-allocating storage for `result`. – Lingxi May 13 '15 at 14:19
  • 6
    it has lower complexity, albeit not in one step. the last part can be a move, probably making it the fastest algorithm you could devise for this. – sp2danny May 13 '15 at 14:19
  • @JonathanMee: Linear-time in-place merges [exist, but they're crazy](http://stackoverflow.com/questions/2126219/how-to-merge-two-sorted-integer-array-in-place-using-on-time-and-o1-space-co). So you can object to the temporary vector, but in the end you'll wind up either writing it this way yourself, or calling an existing function (like `std::inplace_merge()`) that does it for you anyway. – j_random_hacker May 13 '15 at 14:36
  • @TobiMcNamobi This doesn't work: `std::merge(foo.begin(), foo.end(), bar.begin(), bar.end(), std::back_inserter(result));` It inserts the merge of both containers at the end of the first container. – Jonathan Mee May 14 '15 at 13:19
  • 1
    @JonathanMee In my test program `std:merge` leaves `foo` and `bar` untouched and `result` is filled with the merged list just like the [documentation](http://en.cppreference.com/w/cpp/algorithm/merge) says. – TobiMcNamobi May 15 '15 at 07:17
  • 1
    @TobiMcNamobi You are correct, my mistake, I thought your output said `back_inserter(foo)`. – Jonathan Mee May 15 '15 at 13:26
6

It might be faster to use std::inplace_merge instead of std::sort. If there is additional memory available it has linear complexity otherwise it falls back to NlogN.

auto middle = foo.insert(foo.end(), bar.begin(), bar.end());
std::inplace_merge(foo.begin(), middle, foo.end());
Blastfurnace
  • 18,411
  • 56
  • 55
  • 70
  • @Lingxi: This algorithm attempts to allocate some temporary buffer memory to allow linear complexity, if it can't it uses a less efficient (NlogN) algorithm. – Blastfurnace May 13 '15 at 14:32
  • 3
    I upvoted, but let's be clear that under the hood, `std::inplace_merge()` does the exact same thing as TobiMcNamobi's code (assuming there's enough memory available), so if the OP is unhappy with that solution, he really ought to be equally unhappy with this one. – j_random_hacker May 13 '15 at 14:33
  • @j_random_hacker That's not true, TobiMcNamobi's solution doesn't work: http://stackoverflow.com/questions/30217383/is-there-an-alternative-to-insert-and-then-sort/30238403#comment48578328_30217523 – Jonathan Mee May 14 '15 at 13:20
  • I have accepted this because it's the right answer, but a better evaluation of this answer is here: http://stackoverflow.com/a/30238403/2642059 – Jonathan Mee May 14 '15 at 13:21
  • @Blastfurnace I've been racking my brain trying to come up with a way to make sure we don't invalidate `foo.begin()` so we could compress this into one line, but I can't do it without a reserve: `inplace_merge(foo.begin(), foo.insert(foo.end(), bar.begin(), bar.end()), foo.end());` – Jonathan Mee May 14 '15 at 14:45
  • @JonathanMee: To be honest, I would just write a function template that wraps these two steps. Since the order of evaluation of function arguments is unspecified I think it's the only way to be sure the arguments to `std::inplace_merge` remain valid. – Blastfurnace May 14 '15 at 14:59
  • @Blastfurnace Yeah that was the comment at the end of my answer cause I couldn't find a workaround. – Jonathan Mee May 14 '15 at 15:03
  • 1
    @JonathanMee: We know that `insert()` invalidates the `foo.end()` iterator regardless of the container's capacity. Combine that with the order of evaluation issue and it makes the idea of a one-liner very sketchy. – Blastfurnace May 14 '15 at 15:15
  • @JonathanMee: I read your comment claiming that TobiMcNamobi's code is broken, but you're mistaken. I'm not sure why you think anything gets written to the end of `foo`. The result gets written to `result`, and then that is copied to `foo` with `foo = result;`. – j_random_hacker May 15 '15 at 11:48
  • @j_random_hacker Yeah, I put a comment there too. I thought that he had used `back_inserter(foo)`. However even after reading it correctly I still believe that this answer is preferable. Because, it doesn't require me to create a temporary, and because `inplace_merge` takes advantage of the fact that the two ranges it's merging are already sorted. – Jonathan Mee May 15 '15 at 13:31
  • @JonathanMee: Both `merge` and `inplace_merge` take advantage of (in fact, *require*) both ranges to be already sorted -- that's not a point of difference! – j_random_hacker May 15 '15 at 14:47
  • @j_random_hacker Chalk up another for you sir. I am doing poorly today. I trust you will allow me to cling to the fact that I don't have to make a temporary when I use `inplace_merge`? – Jonathan Mee May 15 '15 at 14:57
  • 1
    @JonathanMee: No worries :) I think `inplace_merge` is indeed the more convenient choice here for that reason, and I've already harped on the fact that internally it will just do what TobiMcNamobi's code does, so cling away :) – j_random_hacker May 15 '15 at 15:06
1

If you need this kind of merge, why not make one yourself?

template <class Vector>
void insert_sorted(Vector& where, Vector& what)
{
    typename Container::iterator src = what.begin();
    typename Container::iterator src_end = what.end();

    size_t index = 0;

    while(src != src_end)
    {
        if(*src < where[index])
        {
            where.insert(where.begin() + index, *src);
            ++src;
        }

        ++index;
    }
}

Sample usage:

vector<int> foo{ 0, 5, 7, 9, 11, 14 };
vector<int> bar{ 1, 2, 4, 8, 10, 12 };

insert_sorted(foo, bar);

for(vector<int>::iterator i = foo.begin(); i != foo.end(); ++i)
    cout << *i << " ";

Output:

0 1 2 4 5 7 8 9 10 11 12 14

Live sample: link.

Mateusz Grzejek
  • 11,698
  • 3
  • 32
  • 49
  • 1
    Writing your own is generally risky, when it comes to std algorithms. Although this particular problem is easy so I might consider it, but I wouldn't admit doing it. :) – Kenny Ostrom May 13 '15 at 14:38
  • Your algorithm is flawed. What if the last element, if any, of `where` is less than the first element of `what`? – Lingxi May 13 '15 at 14:47
  • @MateuszGrzejek Assertion when `foo` is smaller than `bar`. I concur with writing my own but this doesn't work. – Jonathan Mee May 13 '15 at 15:05
  • @MateuszGrzejek This will probably work better if you can descramble it from the comment: `for (Container::iterator toIterator = where.begin(), fromIterator = what.begin(); fromIterator != what.end(); ++fromIterator){while (toIterator != where.end() && *toIterator < *fromIterator)++toIterator;toIterator = where.insert(toIterator, *fromIterator);}` – Jonathan Mee May 13 '15 at 15:10
  • That's a minimal solution, illustrating possible concept. Yes, it needs to be adjusted - note, however, that checking all possible situations (empty `where`, empty `what`, etc.) may not be necessary, if we can make some guarantees about content of parameters. Now, it's up to the OP to adjust this to his needs. – Mateusz Grzejek May 13 '15 at 16:42
0

So after looking through all the standard algorithms I can confirm that, there is no alternative to insert and sort. As I was searching the standard algorithms I did note that all the copying algorithms use input iterators and output iterators the only time an input-output iterators are used is when a single range is being operated on. (For example sort uses input-output iterators but any copy uses input iterators and an output iterator.)

I'd like to give an illustration of my point. So lets make an example of what an insertion merge algorithm with an input-output iterator would look like:

template <class BidirectionalIterator, class InputIterator>
void func(BidirectionalIterator first1, BidirectionalIterator last1, InputIterator first2, InputIterator last2){
    bool is1Empty = first1 == last1;
    bool is2Empty = first2 == last2;
    BidirectionalIterator end = next(last1, distance(first2, last2));

    if (!is1Empty){
        --last1;
    }

    if (!is2Empty){
        --last2;
    }

    while (!is1Empty || !is2Empty){
        --end;
        if (!is1Empty){
            if (!is2Empty && *last2 > *last1){
                *end = *last2;

                if (last2 == first2){
                    is2Empty = true;
                }else{
                    --last2;
                }
            }else{
                *end = *last1;

                if (last1 == first1){
                    is1Empty = true;
                }
                else{
                    --last1;
                }
            }
        }else{
            *end = *last2;

            if (last2 == first2){
                is2Empty = true;
            }
            else{
                --last2;
            }
        }
    }
}

Two things should be noted about this func algorithm:

  1. It doesn't respect last1 it is assumed that sufficient space is allocated beyond last1 to also contain all the elements in the input range
  2. func's input-output range cannot be called with a back_inserter like any other output only range in a standard algorithm

Because of this even func cannot be a "one step algorithm". It must be called like this:

 foo.resize(foo.size() + bar.size());
 func(foo.begin(), next(foo.begin(), foo.size() - bar.size()), bar.begin(), bar.end());

Note that Blastfurnace's answer takes advantage of the knowledge that it is merging two sorted ranges, and as such is of equivalent speed to func:

auto middle = foo.insert(foo.end(), bar.begin(), bar.end());
inplace_merge(foo.begin(), middle, foo.end());

The only actual "one step algorithm" is to roll this Blastfurnace's answer into a function that you could call by passing in the containers to be merged.

Community
  • 1
  • 1
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288