1
Map<String, Integer> nonOrderedData = // {b=1, c=2, d=3, e=4, a=0}
List<String> orderSequence = // a, b, c, d, e

I need to apply the order sequence to get correctly ordered data, how could I achieve this using (prefered) stream?

What I used is non stream way:

Map<String, Integer> orderedData = new HashMap<>();
for (Map.Entry<String, Integer> nod : nonOrderedData.entrySet()) {
    for (String os : orderSequence) {
        if (os == nod.getKey()) {
            // add nonOrderedData data
        } else {
            // add data by sequence
        }
    }
}

Would like to have more cleaner way of what I want.


I've noticed that in my method I could just return new TreeMap<>(nonOrderedData) and it would work just fine, but I don't want to stick to just applying asc order - I would like to read actual sequence values and then change nonOrderedData.

0

2 Answers 2

4

The HashMap cannot be used to store orderedData because it does not guarantee ordering of its keys, so LinkedHashMap should be used which maintains insertion order.

Ordering by the data presented in orderSequence using streams may be implemented in two modes:

  1. Keep only the values available in nonOrderedData:
Map<String, Integer> nonOrderedData = Map.of(
    "b", 1, "e", 4, "a", 0, "o", 5, "d", 7
);
List<String> orderSequence = Arrays.asList(
    "a", "e", "i", "o", "u", "b", "c", "d"
);

Map<String, Integer> reordered = orderSequence
    .stream()
    .filter(nonOrderedData::containsKey)
    .collect(Collectors.toMap(
        key -> key, nonOrderedData::get, 
        (v1, v2) -> v1, LinkedHashMap::new
    ));
    
System.out.println(reordered);

Output:

{a=0, e=4, o=5, b=1, d=7}

It is similar to INNER JOIN between orderSequence and nonOrderedData.

  1. Filling reorderedData with some default value if the key from orderSequence is missing in nonOrderedData:
Map<String, Integer> reorderedWithDefault = orderSequence
    .stream()
    .collect(Collectors.toMap(
        key -> key, nonOrderedData.getOrDefault(key, -1), 
        (v1, v2) -> v1, LinkedHashMap::new
    ));
    
System.out.println(reorderedWithDefault);

Output:

{a=0, e=4, i=-1, o=5, u=-1, b=1, c=-1, d=7}

It is similar to LEFT JOIN between orderSequence and nonOrderedData.


Update
In the above-mentioned implementations the key-value pairs in nonOrderedData that do not match to the keys in orderSequence are skipped altogether. Such keys may be tracked (and added later to the reordered result) using Map::remove (Object key) which returns a value of the key being removed.

However, the following two code examples modify the state of nonOrderedData outside the stream execution.

  1. Keep the keys and related values only from nonOrderedData, place the non-matched pairs to the end:
Map<String, Integer> nonOrderedData = new HashMap<>(Map.of(
    "b", 1, "e", 4, "z", 8, "a", 0, "q", 6,
    "f", 5, "d", 7
    ));
List<String> orderSequence = Arrays.asList("a", "e", "i", "o", "u", "b", "c", "d");

Map<String, Integer> reordered = orderSequence
    .stream()
    .filter(nonOrderedData::containsKey)
    .collect(Collectors.toMap(
        key -> key, nonOrderedData::remove, 
        (v1, v2) -> v1, LinkedHashMap::new
    ));

SortedMap<String, Integer> remainder = new TreeMap<>(nonOrderedData);
System.out.println("remained: " + remainder);
    
reordered.putAll(remainder);

System.out.println(reordered);

Output:

remained: {f=5, q=6, z=8}
{a=0, e=4, b=1, d=7, f=5, q=6, z=8}

It is similar to RIGHT JOIN between orderSequence and nonOrderedData.

  1. Keep all values from both orderSequence and nonOrderedData similar to FULL JOIN

Here default values will be provided for the non-mapped keys in orderSequence and non-matched keys from nonOrderedData will be added to the end.

Map<String, Integer> reorderedFull = orderSequence
    .stream()
    .peek(key -> nonOrderedData.computeIfAbsent(key, (k) -> -1)) // default value
    .collect(Collectors.toMap(
        key -> key, nonOrderedData::remove, 
        (v1, v2) -> v1, LinkedHashMap::new
    ));

SortedMap<String, Integer> remainderFull = new TreeMap<>(nonOrderedData);
System.out.println("remained: " + remainderFull);
    
reorderedFull.putAll(remainderFull);
System.out.println(reorderedFull);

Output:

remained: {f=5, q=6, z=8}
{a=0, e=4, i=-1, o=-1, u=-1, b=1, c=-1, d=7, f=5, q=6, z=8}
Sign up to request clarification or add additional context in comments.

Comments

1

If you have an ordered sequence, you can collect a new map with keys from that sequence and values from the unordered map using the remove method, which removes the mapping for a key from that map and returns the previous value.

After collecting a new map, if there is something left in the unordered map, you can add those entries using putAll method.

Map<String, Integer> nonOrderedData = new HashMap<>(
        Map.of("f", 5, "b", 1, "c", 2, "d", 3, "e", 4, "a", 0));

List<String> orderSequence = List.of("a", "b", "c", "d", "e");
Map<String, Integer> orderedMap = orderSequence.stream()
        .collect(LinkedHashMap::new,
                (col, e) -> col.put(e, nonOrderedData.remove(e)),
                HashMap::putAll);
System.out.println(orderedMap); // {a=0, b=1, c=2, d=3, e=4}
System.out.println(nonOrderedData); // {f=5}

orderedMap.putAll(nonOrderedData); // add what's left

System.out.println(orderedMap); // {a=0, b=1, c=2, d=3, e=4, f=5}

Similarly, instead of a map you can collect a list of map entries:

Map<String, Integer> nonOrderedData = new HashMap<>(
        Map.of("f", 5, "b", 1, "c", 2, "d", 3, "e", 4, "a", 0));

List<String> orderSequence = List.of("a", "b", "c", "d", "e");
List<Map.Entry<String, Integer>> orderedList = orderSequence.stream()
        .map(e -> Map.entry(e, nonOrderedData.remove(e)))
        .collect(Collectors.toList());
System.out.println(orderedList); // [a=0, b=1, c=2, d=3, e=4]
System.out.println(nonOrderedData); // {f=5}

orderedList.addAll(nonOrderedData.entrySet()); // add what's left

System.out.println(orderedList); // [a=0, b=1, c=2, d=3, e=4, f=5]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.