简体   繁体   中英

Java Comparator Sorting Problem Using Map Values

I have a scenario where I need to take the keys of a Map<String, Set<String>> , and add them into a new Set<String> that is sorted. The sort order is based on the Map values for each key. The value for each key of the map is a Set containing other keys that are related to that key.

I need the keys to be sorted in such a way that a related key must be BEFORE another key that contains it in its related Set. To use a programming paradigm, it's similar to requiring a variable to be declared on an earlier line, before it can be referenced on another line.

For example, the following represents the contents of the Map<String, Set<String>> :

abc=[def, ghi, jkl, mno]
def=[]
ghi=[def]
jkl=[ghi, stu]
mno=[]
pqr=[abc]
stu=[def]
vwx=[mno, ghi]
zy0=[jkl]

In this example, the key "jkl" has a relationship to keys, "ghi" and "stu", "def" does not have a relationship to any of the keys.

NOTE: The relationships will be ONE-WAY only. So, for example, if "ghi" is related to "def", "def" will NEVER be related to "ghi".

So, for the above Map, the sort order would be:

def=[]
mno=[]
ghi=[def]
stu=[def]
vwx=[mno, ghi]
jkl=[ghi, stu]
zy0=[jkl]
abc=[def, ghi, jkl, mno]
pqr=[abc]

Here's the Comparator that I wrote. It's inside of a runnable test class that uses the example above:

import java.util.*;

public class RelationshipComparator_Test {
    public static void main(String[] args) {
        String[] testMap = "abc=[def,ghi,jkl,mno]|def=[]|ghi=[def]|jkl=[ghi,stu]|mno=[]|pqr=[abc]|stu=[def]|vwx=[mno,ghi]|zy0=[jkl]".split("[|]");
        Map<String, Set<String>> relationshipMap = new HashMap<>();
        for (String entry : testMap) {
            String[] keyValue = entry.split("[=]");
            String replacement = keyValue[1].replaceAll("[^a-z0-9,]", "");
            Set<String> valueSet = new HashSet<>();
            String[] values = (!replacement.equals("") ? replacement.split("[,]") : new String[0]);
            Collections.addAll(valueSet, values);
            relationshipMap.put(keyValue[0], valueSet);
        }
        Set<String> sortedKeys = new TreeSet<>(new RelationshipComparator(relationshipMap));
        sortedKeys.addAll(relationshipMap.keySet());
        for (String key : sortedKeys) {
            System.out.println(key + "=" + relationshipMap.get(key));
        }
    }

    static class RelationshipComparator implements Comparator<String> {
        private Map<String, Set<String>> relationshipMap;

        RelationshipComparator(Map<String, Set<String>> relationshipMap) {
            this.relationshipMap = relationshipMap;
        }

        @Override
        public int compare(String o1, String o2) {
            Set<String> o1Set = relationshipMap.get(o1);
            Set<String> o2Set = relationshipMap.get(o2);
            if (o1Set != null && o2Set != null) {
                if (o1Set.size() == 0 && o2Set.size() > 0) {
                    printCompare(o1, o2, "o1Set.size() == 0: -1");
                    return -1;
                }
                if (o2Set.size() == 0 && o1Set.size() > 0) {
                    printCompare(o1, o2, "o2Set.size() == 0: 1");
                    return 1;
                }
                if (o1Set.contains(o2)) {
                    printCompare(o1, o2, "o1Set.contains(o2): 1");
                    return 1;
                }
                if (o2Set.contains(o1)) {
                    printCompare(o1, o2, "o2Set.contains(o1): -1");
                    return -1;
                }
            }
            printCompare(o1, o2, "default: " + o1.compareTo(o2));
            return o1.compareTo(o2);
        }

        private void printCompare(String o1, String o2, String result) {
            System.out.println("**********");
            System.out.println("o1: " + o1 + "=" + relationshipMap.get(o1));
            System.out.println("o2: " + o2 + "=" + relationshipMap.get(o2));
            System.out.println("result: " + result);
            System.out.println("**********");
            System.out.println();
        }
    }
}

If you run the code, you'll see the following output:

def=[]
mno=[]
ghi=[def]
jkl=[stu, ghi]
abc=[def, ghi, jkl, mno]
pqr=[abc]
stu=[def]
vwx=[ghi, mno]
zy0=[jkl]

It's incorrect because, "jkl" references "stu", but "stu" is sorted after "jkl".

Any help would be greatly appreciated.

You say that relationships are one-way, which rules out obvious cases such as:

a=[b]
b=[a]

for which no solution is possible. However, we also need to rule out cyclic relationships such as:

a=[b]
b=[c]
c=[a]

If this is the case then I believe you can achieve the required ordering by using a PriorityQueue to order keys by the size of the value set related to the key. As keys are removed from the queue they also have to be removed from any of the related value sets that contain them. Which value sets contain a given key can be recovered from a reverse Map<String, Set<String>> which holds the set of keys that refer to a given value key.

Hopefully some code will make things clearer:

static List<String> orderByRef(Map<String, Set<String>> relationshipMap)
{
  final Map<String, Set<String>> relationshipMapCopy = new HashMap<>();
  for(String key : relationshipMap.keySet())
    relationshipMapCopy.put(key, new HashSet<>(relationshipMap.get(key)));

  final Map<String, Set<String>> referencedBy = new HashMap<>();
  for(String key : relationshipMap.keySet())
    referencedBy.put(key, new HashSet<>());

  for (Entry<String,Set<String>> e : relationshipMapCopy.entrySet())
    for(String v : e.getValue())
      referencedBy.get(v).add(e.getKey());

  PriorityQueue<String> pq = new PriorityQueue<>(new Comparator<String>()
  {
    @Override
    public int compare(String k1, String k2)
    {
      return relationshipMapCopy.get(k1).size() - relationshipMapCopy.get(k2).size();
    }
  });
  pq.addAll(relationshipMap.keySet());

  List<String> orderedKeys = new ArrayList<>();
  while(!pq.isEmpty()) 
  {      
    String minKey = pq.poll();
    if(!relationshipMapCopy.get(minKey).isEmpty()) 
    {
      // cyclic relationship
      break;
    }
    orderedKeys.add(minKey);
    for(String refKey : referencedBy.get(minKey))
    {
      // remove minKey from value set of refKey 
      relationshipMapCopy.get(refKey).remove(minKey);
      // reorder refKey in pq
      pq.remove(refKey);
      pq.add(refKey);
    }
  }

  return orderedKeys;    
}

Note that since we're modifying the relationshipMap by removing keys from value sets we first need to create a deep copy. Also, we can detect the presence of a cyclic relationships by checking that the value set of the min key is empty.

Output:

def []
mno []
stu [def]
ghi [def]
vwx [ghi, mno]
jkl [stu, ghi]
zy0 [jkl]
abc [def, ghi, jkl, mno]
pqr [abc]

Which satisfies the constraint that no key is referenced before it appears in the list.

For input containing a cyclic relationship, eg (z=[y]|y=[]|a=[b]|b=[c]|c=[a]) , we get:

y []
z [y]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM