Home > database >  Group the data into a Map<Long, List<Long>> where Lists need to be sorted
Group the data into a Map<Long, List<Long>> where Lists need to be sorted

Time:11-22

Assume I have the following domain object:

public class MyObj {
    private Long id;
    private Long relationId;
    private Long seq;
    
    // getters
}

There is a list List<MyObj> list. I want to create a Map by grouping the data by relationId (*key) and sort values (value is a list of id).

My code without sort values:

List<MyObj> list = getMyObjs();

// key: relationId, value: List<Long> ids (needs to be sorted)

Map<Long, List<Long>> map = list.stream()
    .collect(Collectors.groupingBy(
        MyObj::getRelationId, 
        Collectors.mapping(MyObj::getId, toList())
    ));
public class MyObjComparator{
    public static Comparator<MyObj> compare() {
        ...
    }
}

I have created compare method MyObjComparator::compare, my question is how to sort this map's values in the above stream.

CodePudding user response:

As @dan1st said you can use TreeMap if you want to sort keys

If you want to sort values you can only sort them before they are grouped and then they are grouped again

@Data
@AllArgsConstructor
public class MyObj {
    private Long relationId;
    private Long id;


    static int comparing(MyObj obj,MyObj obj2){
        return obj.getId().compareTo(obj2.getId());
    }
    public static void main(String[] args) {
        List<MyObj> list = new ArrayList<>();
        list.add(new MyObj(2L, 3L));
        list.add(new MyObj(2L, 1L));
        list.add(new MyObj(2L, 5L));

        list.add(new MyObj(1L, 1L));
        list.add(new MyObj(1L, 2L));
        list.add(new MyObj(1L, 3L));

        Map<Long, List<Long>> collect = list.stream()
//                value sort
//                .sorted(MyObj::comparing)
                .collect(groupingBy(MyObj::getRelationId,
//                        key sort
//                        (Supplier<Map<Long, List<Long>>>) () -> new TreeMap<>(Long::compareTo),
                        mapping(MyObj::getId, toList())));
        try {
//            collect = {"1":[1,2,3],"2":[1,3,5]}
            System.out.println("collect = "   new ObjectMapper().writeValueAsString(collect));
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }

    }
}

CodePudding user response:

It appears from the presented code that the resulting map should look like Map<Long, List<Long>> with the key - relationId and the value - list of id. Therefore custom comparator for List<Long> should be implemented like this:

Comparator<List<Long>> cmp = (a, b) -> {
    for (int i = 0, n = Math.min(a.size(), b.size()); i < n; i  ) {
        int res = Long.compare(a.get(i), b.get(i));
        if (res != 0) {
            return res;
        }
    }
    return Integer.compare(a.size(), b.size());
};

This comparator should be applied to the entry set of the map, however, either the map should be converted into a SortedSet of the map entries, or a LinkedHashMap needs to be recreated on the basis of the comparator:

Map<Long, List<Long>> map = list.stream()
    .collect(groupingBy(
        MyObj::getRelationId, Collectors.mapping(MyObj::getId, toList())
    ))
    .entrySet()
    .stream()
    .sorted(cmp)
    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));

CodePudding user response:

To obtain the Map having the sorted lists of id as Values, you can sort the stream elements by id before collecting them (as @shmosel has pointed out in the comment).

Collector groupingBy() will preserve the order of stream elements while storing them into Lists. In short, the only case when the order can break is while merging the partial results during parallel execution using an unordered collector (which has a leeway of combining results in arbitrary order). groupingBy() is not unordered, therefore the order of values in the list would reflect the initial order of elements in the stream. You can find detailed explanation in this answer by @Holger.

You don't need a TreeMap (or a LinkedHashMap), unless you want the Entries of the Map to be sorted as well.

List<MyObj> list = getMyObjs();
        
// Key: relationId, Value: List<Long> ids (needs to be sorted)

Map<Long, List<Long>> map = list.stream()
    .sorted(Comparator.comparing(MyObj::getId))
    .collect(Collectors.groupingBy(
        MyObj::getRelationId,
        Collectors.mapping(MyObj::getId, Collectors.toList())
    ));

CodePudding user response:

Not clear if you want to sort by ids or by elements and extract the ids.

In the first case you can use an ending operation after the collect to get a sorted list. As Collections.sort() can sort a list, you just have to call it at the appropriate place.

Map<Long, List<Long>> map = list.stream()
    .collect(Collectors.groupingBy(
        MyObj::getRelationId,
        Collectors.collectingAndThen(Collectors.mapping(MyObj::getId,toList()),
                                     l -> { Collections.sort(l, your_comparator); return l; })
    ));

In the second case you just need to sort the stream (if it is finite and not too big).

  • Related