15

I have the following vector:

std::vector< std::pair<int, int> > vectorOfPairs

with the following items:

0, 1
0, 2
1, 4
2, 3
3, 4
4, 5
5, 6

I would like to sort them in a way that the second component of every pair is equal to the first component of the nearest pair in the vector, something like this:

0, 1
1, 4
4, 5
5, 6
0, 2
2, 3
3, 4

I don't know if this is clearly enough, I'll append an image that show what I'm trying to do:

enter image description here

I feel that I should use sort with some kind of comparator but I'm lost right here:

std::sort(vectorOfPairs.begin(), vectorOfPairs.end(), MyComparator);

bool MyComparator(pair<int, int> a, pair<int, int> b) {
    if(some_kind_of_comparison){
        return true;
    } 
    return false;
}

I'm newbie with c++ and if someone could help me with a pseudo-code of how to do this, I'll be very grateful.

Alex S. Diaz
  • 2,637
  • 2
  • 23
  • 35
  • why is `0,2` coming after `5,6` in your sorted list? The first element of the sorted array is the first element of the original? I think it is easier not to do it with the `std::sort`. Actually I am not sure if you can even do it with `std::sort`, because your ordering does not behave like a normal `<`. – 463035818_is_not_an_ai Nov 23 '15 at 20:08
  • what I mean is this: to use `std::sort` you need to know whether e.g. `0,5` is before or after `3,4`, but in your logic this is not defined – 463035818_is_not_an_ai Nov 23 '15 at 20:10
  • Sorting is done by comparing two items. There's no concept of taking into account other items as far as I know. – Anon Mail Nov 23 '15 at 20:10
  • 2
    This definitely doesn't sound like a strict weak ordering. – T.C. Nov 23 '15 at 20:11
  • I don't think this is doable either. I can think of no comparison that would indicate that (0,2) should *not* come before (1,4). That is, if those are the two items being compared, I see no relationship between them (A sorts first, B sorts first, or Are Equal) that results in the list being sorted in the manner you wish. You will likely have to write your own sort method. – Draco18s no longer trusts SE Nov 23 '15 at 20:11
  • @T.C. this was the phrase I was looking for – 463035818_is_not_an_ai Nov 23 '15 at 20:12
  • 2
    But you can always **arrange** your items any way you see fit, it's just that you will have to iterate through them and figure out how to arrange them based on your criteria. – Anon Mail Nov 23 '15 at 20:12
  • @tobi303 `0, 2` is after `5, 6` because there is no other value that start with `6` so I use the first of the items remaining that hasn't being sorted yet, in this case, `0, 2` – Alex S. Diaz Nov 23 '15 at 20:13
  • How will you resolve ties? Can there be duplicates in the list of pairs? – vsnyc Nov 23 '15 at 21:41
  • @AlexandroSifuentesDíaz Do you have an example when you'd use this sorting? – vsnyc Nov 23 '15 at 22:53
  • @vsnyc in my problem, there are no duplicates, also there are no inverted ones (if `1, 2` exist, `2, 1` will not exist) – Alex S. Diaz Nov 24 '15 at 04:12
  • @vsnyc I use this to order a set of images in a simple 2d stitching module to know the neighbors images of the current one... – Alex S. Diaz Nov 24 '15 at 04:19
  • @AlexandroSifuentesDíaz I see, sounds interesting. Although I've labeled it as scheduling my algorithm will work for your example. I did some quick Java code to test it, will refine it tomorrow and add a link to it in case it interests you. – vsnyc Nov 24 '15 at 05:41
  • "Too broad" unless you're interested in the algorithm - the possible implementations are endless. – ivan_pozdeev Dec 04 '15 at 13:00

7 Answers7

8

You can think of this problem as a graph problem. Each of your pairs represents an edge in a directed graph. For example, the pair (0, 2) means "there's an edge from node 0 to node 2," and the pair (2, 5) means "there's an edge from node 2 to node 5."

If you think of things this way, a series of edges where the second element of each pair matches the first element of the next pair corresponds to a path in the graph. For example, the sorted ordering you've given has two paths in it: 0 -> 1 -> 4 -> 5 -> 6 and 0 -> 2 -> 3 -> 4. Consequently, the problem you're trying to solve is the following: how do you break the edges in the graph apart into the smallest number of edge-disjoint paths? Once you've solved that, you can then output those paths in any order you'd like to form a sorted ordering along the lines of what you're trying to do.

You can't solve this problem with std::sort. As an example, suppose that you have the edges (0, 1), (0, 2), (2, 3), and (1, 3). In that case, both of these orderings are valid:

(0, 1)          (0, 2)
(1, 3)          (2, 3)
(0, 2)          (0, 1)
(2, 3)          (1, 3)

This is a problem. Because (0, 1) precedes (0, 2) in the first ordering and (0, 2) precedes (0, 1) in the second ordering, the only way the comparator could be a strict weak ordering is if (0, 1) and (0, 2) are incomparable. That means that in any sorted ordering, all the elements between (0, 1) and (0, 2) (inclusive) must also be incomparable because of transitivity of incomparability. In other words, we should be able to take any ordering, permute the elements between (0, 1) and (0, 2) (inclusive), and get back a new ordering. This would mean that this should be a valid ordering, even though it isn't because there's a vastly better solution:

(0, 1)          (0, 1)
(1, 3)   -->    (0, 2)
(0, 2)          (1, 3)
(2, 3)          (2, 3)

So there's no way to solve this using std::sort.

What I'm not sure about is what the best way to solve this is. This seems related to a flow problem, but I'm not sure how to set it up. If I think of anything, I'll update this answer. Thanks for posting something so interesting!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • This is basically a depth-first search. – T.C. Nov 23 '15 at 20:41
  • @T.C. That's an interesting line of reasoning, but I'm not sure it works. The shape of a DFS tree depends on where you start the DFS and while it will give a few long paths, the tree structure will also give back a bunch of short paths as well. – templatetypedef Nov 23 '15 at 20:49
3

I would NOT use std::sort for this. Let me explain why.

1) Your sort depends on information about ALL the members to be sorted, not a pairwise comparison. In your example, the reason [0,1] comes before [4,5] is the presence of [1,4] in the list. If you had instead had [5,0] in the list, it would have implied [0,1] comes AFTER [4,5]. Worse, if both are in the list, you have no clear basis for selecting which should come first.

2) Your sorting method is not well defined. You haven't explained, for example, why [0,1] should appear before [0,2] and not after. Similarly, if you have [[0,1],[1,2],[1,3]], there is no way to know whether [1,2] or [1,3] should be second.

One other important consideration. It feels like you may be doing some sort of pathfinding/chaining problem. It's possible your data structure is not well suited to your problem, overall. That's just an observation, but perhaps worth considering.

3

@templatetypedef's suggestions are great. Having given it some thought, this sounds more like a scheduling algorithm than a sorting algorithm. Particularly, it resembles of a Elevator like offline scheduling algorithm (i.e. all the ordered arrivals are known at the time scheduling is run) with the constraint that only one task can be taken up any time. In other words the elevator will go only in one direction until it reason the top requested floor. Once there it will descend to the lowest requested floor and go to the next requested top.

I am assuming that the order of elements in the list correspond to arrival of requests.

This is illustrated in the figure below. enter image description here

If the above assumptions are true, a pseudo code for this would be as below:

1. Create two helper maps:
2. LeftKeyPairMap containing all tuples (leftValue, Pair) e.g. (0, (0,1)), (0,(0,2)) ...
3. PairIndexMap containing all tuples (Pair, Index) e.g. ((0,1),0), ((0,2),1) ...
4. Initialize an empty schedule
5. Add first input element to schedule and mark it as visited
6. Start input search at index = 1
7. Repeat while schedule size != input list {
8.   lastElementInSchedule = shedule.get(index - 1);
9.   Check if LeftKeyPairMap contains the an entry with key: lastElementInSchedule.rightElem
10.   if (a pair is present and it is not yet marked visited) {
11.      add pair to schedule
12.      mark pair as visited
13.      increment index
14.   } else {
15.     find min univisited index (identified as the non-consecutive gap in visited entries
16.     add the univisited pair to schedule
17.     increment index
18.   }
19. } // End Loop
vsnyc
  • 2,117
  • 22
  • 35
  • 1
    This is an interesting idea. The concern I have with this setup is that the pairs here don't necessarily represent intervals. For example, you could have a chain like (0, 2), (2, 1), (1, 4), (4, 3), etc. that alternates between going up and going down. – templatetypedef Nov 23 '15 at 22:42
  • That is how an idle elevator will work if requests arrive at t0: (0,2); t1: (2,1); t2:(1,4); t3:(4,3)... The OP did not give the use case being solved but it struck to me extremely similar, especially with the notion of ordering on the sequence, e.g. (0,2) was picked after (5,6). I don't know for sure if I'm thinking in the right direction – vsnyc Nov 23 '15 at 22:50
2

You cannot use std::sort, because your ordering cannot be phrased in terms of a strict weak ordering (as T.C.pointed out). However you can do it by some handmade sorting function. Something like this:

typedef std::vector<pair<int,int>> PVect
PVect mysort(const PVect& in){
    PVect result;
    // first element is the same ?
    result.push_back(in[0]);
    // add the next one
    for (int i=1;i<in.size();i++){
        if (in[i].second() == result[0].first()){
             result.push_back(in[i]);
        }
    }
    /* ... */
    return result;
}

This code is definitely not good, but it is just meant to guide you in the right direction.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • when you say *definitely not good*, why are you saying it?, and thanks! I feel this will help – Alex S. Diaz Nov 23 '15 at 20:24
  • This will give you back *an* ordering, but it's not necessarily a *good* ordering. – templatetypedef Nov 23 '15 at 20:25
  • @AlexandroSifuentesDíaz Its not good, because it only adds the first and then adding the second one is already broken. Maybe you will have to copy the input vector and remove elements from it once they are placed in the sorted one. – 463035818_is_not_an_ai Nov 23 '15 at 20:28
1

You cannot use std::sort() as it requires strict weak ordering. I would copy vector to std::multi_map and then put them back in sorted order:

std::multi_map<int,int> values( vectorOfPairs.begin(), vectorOfPairs.end() );
vectorOfPairs.clear();
for( auto iter = values.begin(); iter != values.end(); ) {
    vectorOfPairs.push_back( *iter );
    values.erase( iter );
    iter = values.find( vectorOfPairs.back().second );
    if( iter == values.end() ) iter = values.begin();
}
Slava
  • 43,454
  • 1
  • 47
  • 90
0

If you have access to the full complete vector from the start you might try a kind of selection sort:

  1. scan the full list and select the item you want at the start and swap it to position 1.

  2. While items remaining do: scan the remaining items and select what you want next and swap it into the next position

This will work but its time complexity is n squared so it may be slow if the vector is very large.

-1

Pretty late post. but might help somebody. This code is for a list of pairs to be ordered continuously.

It would give the below ordering

0, 1
1, 4
4, 5
5, 6

But not

0, 1
1, 4
4, 5
5, 6
0, 2
2, 3
3, 4

So,

  1. if we have (1,2) we should not have (2,1) (1 should not be seen again)
  2. list of pairs should have one starting point and one ending point

We need a starting point for this. I have followed the below steps.

  1. Creating a map called originalMap which stores each pairs’ first value as the key and the second value as the value

  2. Creating another map called reverseMap which stores each pairs’ second value as the key and the first value as the value

  3. Iterating over the original map: a. If any of the key value in originalMap is not a key in the reverseMap too, that’s the starting point, as the starting point would not be a second value of any of the pairs in the input b. If no starting point (a cycle is present) or more than one such values are found there is a disconnection in the path and we can conclude that a continuous sequence cannot be formed from the given input and would throw an exception

  4. If a valid starting point is found, the originalMap can be iterated from that point until all the pairs are found in order

public static List<Pair<Integer, Integer>> sortSegments (List<Pair<Integer, Integer>> segments){
        LinkedList<Pair<Integer, Integer>> resultList = new LinkedList<Pair<Integer,Integer>>();

        if(segments.size() == 0) return resultList;
        HashMap<Integer, Integer> originalMap = new HashMap<>();
        HashMap<Integer, Integer> reverseMap = new HashMap<>();

        for(Pair<Integer, Integer> segment : segments) originalMap.put(segment.getKey(), segment.getValue());  // original pairs 
        for(Pair<Integer, Integer> segment : segments) reverseMap.put(segment.getValue(), segment.getKey());   // reversed pairs

        System.out.println("The original input order: " + originalMap);
        System.out.println("The reverse order: " + reverseMap);

        List<Integer> startingPoints = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry: originalMap.entrySet()) { // if an element from original is not in reverse then thats 
            if(!reverseMap.containsKey(entry.getKey())) startingPoints.add(entry.getKey()); // the first to go into the output resultList
        }

        Integer pairFirst = null;
        if(startingPoints.size() != 1) {            // if there are multiple / NO starting points are identified then there is no
            throw new IllegalArgumentException("Invalid input segments"); // contiguous path for the segments to be arranged
        } else {
            pairFirst = startingPoints.get(0);
            Integer pairSecond = originalMap.get(pairFirst); 
            while(pairSecond != null) {
                resultList.add(new Pair<Integer, Integer>(pairFirst, pairSecond));
                pairFirst = pairSecond;
                pairSecond = originalMap.get(pairFirst);
            }
        }

        return resultList;
    }