Alex S. Diaz
Alex S. Diaz

Reputation: 2667

Custom sort vector of pair based on their values

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.

Upvotes: 15

Views: 1692

Answers (7)

Sakthi Rajendran
Sakthi Rajendran

Reputation: 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;
    }

Upvotes: -1

Patrick Grace
Patrick Grace

Reputation: 21

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.

Upvotes: 0

vsnyc
vsnyc

Reputation: 2257

@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

Upvotes: 3

WingedPanther73
WingedPanther73

Reputation: 84

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.

Upvotes: 3

Slava
Slava

Reputation: 44238

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();
}

Upvotes: 1

templatetypedef
templatetypedef

Reputation: 372714

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!

Upvotes: 8

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122133

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.

Upvotes: 2

Related Questions