1

Imagine that you have n sets of elements in a tuple. For example the tuple could be

std::tuple<topBottomStr, topBottomStr, topBottomStr> or
std::tuple<fraction, fraction, fraction>

So maybe there is some template that represents "topbottomthings"

template<typename T>
class TopBottomThing
{
private:
    T top;
    T bottom;
};

The point is that what is in the tuple has a notion of a top and a bottom. My question is, I need a function that returns an

std::vector<std::tuple<TopBottomThing>> ArrangeTopBottomThingFunc(std::vector<std::tuple<TopBottomThing>> tup)

where the items in the return value

std::vector<std::tuple<TopBottomThing>> retVal;

are arranged so that each consecutive entry's TopBottomThing, the bottom of std::vector<n> matches the top of std::vector<n + 1>, matches the bottom of std::vector<n + 2> etc, or the top of std::vector<n> matches the top of std::vector<n + 1>, or the bottom of std::vector<n> matches the bottom of std::vector<n + 1>. Only one case is guaranteed to happen as a constraint to the function. If the length of the input vector is n, either 0 matches or n - 1 matches are guaranteed.

For example, if std::vector<std::tuple<TopBottomThing>> tup represents standard fractions:

2/3 4/5 3/2,

ArrangeTopBottomThingFunc with that std::vector<tup> as input would return a sorted by this criterion an std::vector like this:

[0] = 3/2 [1] = 2/3 [2] = 4/5 (two matches top/bottom)

of if std::tuple tup had these fractions:

4/7 3/2 1/2

[0] = 3/2 [1] = 1/2 [2] = 4/7 (two matches bottom/bottom)

or

4/5 7/8 4/6

[0] 4/5 [1] = 4/6 [2] = 7/8 (two matches top/top)

or the degenerate case

4/5 6/3 2/7 (no matches)

would return in the order given.

or say std::tuple tup represents strings like this

"Straberry/Banana" "Blueberry/Kiwi" "Kiwi/Banana"

[0] = "Kiwi/Banana" [1] = "Straberry/Banana" [2] = "Blueberry/Kiwi"

At least two of the three (n) top or bottom are guaranteed to match. It would be great if the algorithm worked for any length of

std::vector<std::tuple<TopBottomThing>> input. 

It simply sorts and returns an std::vector those things whose top/top or bottom/bottom or top/bottom match in ascending order. The rest that don't match in this way are simply added in any order.

Finally, it would be nice if the calling function understood how the thing was sorted by returning an enum like this:

enum class WHICHSORT { NO_MATCHES, BOTH_TOP, BOTH_BOTTOM, MIXED_TOP_BOTTOM };
Ivan
  • 7,448
  • 14
  • 69
  • 134
  • Shouldn't `ArrangeTopBottomThingFunc` return `std::vector`? – musiphil Mar 11 '15 at 17:49
  • sorry that was a typo. Corrected above. It takes and std::vector> and returns that same thing sorted by the criterion. – Ivan Mar 11 '15 at 17:57
  • In addition, shouldn't `ArrangeTopBottomThingFunc` take `std::tuple` (a;; assuming `TopBottomThing` is not a template but a type)? – musiphil Mar 11 '15 at 17:57
  • yes exactly. However in this case it would work for only three items. That is actually quite good for now. – Ivan Mar 11 '15 at 18:00
  • Can get rid of std::vector altogether if using std::tuple<...> with parameter packs but that might be overly complicated. Either case is fine. – Ivan Mar 11 '15 at 18:09

2 Answers2

1

If I am understanding your question correctly, I think you are saying that you want to determine all TopBottomThing's that have either a top that matches some other entry, or a bottom that matches some other entry. Then you want to sort those TopBottomThing's by the min(top, bottom).

This is the algorithm that I would use to do such a thing. Create a std::map that stores a mapping from type T (from the definition of TopBottomThing) to size_t. Iterate through your input, and for each TopBottomThing, add the top, as well as the bottom (but only if distinct from the top) items to the map. When adding a new entry, initialize the value to zero. Otherwise, increment the existing value.

The next step is to create two vectors: one to hold those TopBottomThing's that match something, and one for those that don't. Iterate through the list of TopBottomThing's a second time, and select max(map[top], map[bottom]) from the map. If that max value is at least 1, then the result matches at least one of the other entries, so add it to the match vector. Otherwise, add it to the non-match vector. After that, sort the match vector, then append the non-match vector, and return that result. The code is as follows:

#include <algorithm>
#include <iostream>
#include <map>
#include <random>
#include <vector>

using namespace std;

template<class T>
class TopBottom {
public:
    T top;
    T bottom;

    TopBottom(T _top, T _bottom) : top(_top), bottom(_bottom) {
    }
};

void print(vector<TopBottom<int>> myThings) {
    for (auto topBottom = myThings.begin(); topBottom != myThings.end(); ++topBottom) {
        cout << (topBottom == myThings.begin() ? "(" : "  ");
        cout << topBottom->top << '/' << topBottom->bottom;
    }
    cout << ')';
}

template<class T>
void Arrange(vector<TopBottom<T>> &topBottoms) {
    // Determine the number of TopBottom's with each value for either top or bottom
    map<T, size_t> countOfObjects;
    for (auto topBottom = topBottoms.cbegin(); topBottom != topBottoms.cend(); ++topBottom) {
        ++countOfObjects[topBottom->top];
        if (topBottom->top != topBottom->bottom) {
            ++countOfObjects[topBottom->bottom];
        }
    }

    // Split the input into two lists; one with things that match, and the remainder
    vector<TopBottom<T>> matches;
    vector<TopBottom<T>> nonMatches;
    for (auto topBottom = topBottoms.cbegin(); topBottom != topBottoms.cend(); ++topBottom) {
        auto matchingObjectCount = max(countOfObjects[topBottom->top],
            countOfObjects[topBottom->bottom]) - 1;
        (0 < matchingObjectCount ? matches : nonMatches).push_back(*topBottom);
    }

    // Here you can sort the matches however you want

    // Populate the result
    topBottoms.clear();
    topBottoms.insert(topBottoms.end(), matches.cbegin(), matches.cend());
    topBottoms.insert(topBottoms.end(), nonMatches.cbegin(), nonMatches.cend());
}

int main(int argc, char *argv[]) {
    vector<TopBottom<int>> myThings;
    mt19937 rng;

    for (auto i = 0; i < 20; ++i) {
        myThings.clear();
        myThings.push_back(TopBottom<int>(rng() % 10, rng() % 10));
        myThings.push_back(TopBottom<int>(rng() % 10, rng() % 10));
        myThings.push_back(TopBottom<int>(rng() % 10, rng() % 10));
        myThings.push_back(TopBottom<int>(rng() % 10, rng() % 10));

        print(myThings);
        cout << "  =>  ";

        Arrange(myThings);
        print(myThings);
        cout << endl;
    }

    cin.get();
    return 0;
}

Running this code generates the following output:

(2/2  5/4  1/4  5/9)  =>  (5/4  1/4  5/9  2/2)
(3/8  5/5  6/0  0/9)  =>  (6/0  0/9  3/8  5/5)
(4/9  6/7  9/9  9/3)  =>  (4/9  9/9  9/3  6/7)
(6/6  3/6  1/0  9/2)  =>  (6/6  3/6  1/0  9/2)
(8/9  5/6  3/3  8/7)  =>  (8/9  8/7  5/6  3/3)
(6/4  0/5  6/7  4/5)  =>  (6/4  0/5  6/7  4/5)
(2/7  3/8  6/0  6/2)  =>  (2/7  6/0  6/2  3/8)
(7/6  6/4  1/9  8/6)  =>  (7/6  6/4  8/6  1/9)
(0/2  2/2  3/1  2/5)  =>  (0/2  2/2  2/5  3/1)
(9/9  6/1  0/9  8/8)  =>  (9/9  0/9  6/1  8/8)
(3/8  0/9  3/6  3/2)  =>  (3/8  3/6  3/2  0/9)
(9/0  4/9  5/4  6/7)  =>  (9/0  4/9  5/4  6/7)
(1/2  4/0  9/1  5/8)  =>  (1/2  9/1  4/0  5/8)
(0/2  8/1  7/1  8/6)  =>  (8/1  7/1  8/6  0/2)
(8/7  4/6  9/2  0/8)  =>  (8/7  0/8  4/6  9/2)
(5/4  3/7  9/3  5/5)  =>  (5/4  3/7  9/3  5/5)
(5/2  0/1  6/0  1/8)  =>  (0/1  6/0  1/8  5/2)
(0/4  5/8  6/4  0/2)  =>  (0/4  6/4  0/2  5/8)
(0/3  6/9  5/5  8/5)  =>  (5/5  8/5  0/3  6/9)
(8/5  5/8  1/7  3/3)  =>  (8/5  5/8  1/7  3/3)
Jeff G
  • 4,470
  • 2
  • 41
  • 76
  • Thank you. I realized that since the vectors guarantee either n - 1 common "paths" or no common paths, that sorting magically works. Hence there is a simpler solution than your suggestion. – Ivan Mar 13 '15 at 15:38
  • Actually, you can't just use std::sort by itself to solve your problem. You indicated that you want to find all things in a list that match at least one other thing in the list. This is NOT possible using sort, as you are splitting the set of input based on set membership. If you post the code you think is working using only std::sort, I would be happy to provide you example input that breaks the solution. – Jeff G Mar 13 '15 at 17:15
0

Just use std::sort:

template<typename T>
bool MIXED_TOP_BOTTOM(const TopBottom<T>& v1,  const TopBottom<T>&v2)
{
    if( v1.bottom == v2.top )
            return true;
    return false;
}

...

std::vector<TopBottom<int> > data;

data.push_back(TopBottom<int>(2, 3));
data.push_back(TopBottom<int>(4, 5));
data.push_back(TopBottom<int>(3, 2));

std::sort(data.begin(), data.end(), MIXED_TOP_BOTTOM<int>);

produces

3/2 2/3 4/5
fukanchik
  • 2,811
  • 24
  • 29
  • Yep, I realized this too after thinking about it for a bit. – Ivan Mar 13 '15 at 15:38
  • 1
    Just to be clear, this doesn't do what you asked in your question. Concrete example: 7/8 2/4 2/5. You indicated in your question that you want these output in the order 2/4, 2/5, 7/8, but it would not be reordered by this solution at all. – Jeff G Mar 13 '15 at 17:11
  • 2
    Just so you are aware, I just copied this answer into a test app, and ran it, since I was surprised that this example worked on ANY input. I get a debug assertion error when running, because the ordering is not well defined. When you pass a comparison method to sort, the following must hold: ∀a, b ∈ data | a ≠ b, MIXED_TOP_BOTTOM(a, b) = ¬MIXED_TOP_BOTTOM(b, a). Otherwise, the result of sort is nondeterministic, which is why VS2013 is throwing the exception. – Jeff G Mar 13 '15 at 18:10
  • @Jeff, You are absolutely right, my answer is incorrect as MIXED_TOP_BOTTOM violates requirements for comparison function and i am not sure if such a function is possible. In [http://stackoverflow.com/questions/24286209/topological-sorting-using-stdsort](this) question ecatmur was able to find it for another ordering though. – fukanchik Mar 13 '15 at 22:24
  • Yes, it appears that a weak ordering requires both {∀a, b ∈ data | cond(a, b), ¬cond(b, a)} AND {∀a, b, c ∈ data | cond(a, b) ∧ cond(b, c), cond(a, c)} to hold. I only caught the first of the two requirements, which I think is the one that is failing when in debug mode. – Jeff G Mar 16 '15 at 22:29