Grouping elements of a list into sublists (maybe by using guava)

后端 未结 4 1893
南笙
南笙 2020-12-13 00:44

I want to group elements of a list. I\'m currently doing it this way:

public static  List> group(final List list, fina         


        
相关标签:
4条回答
  • 2020-12-13 00:47

    Sure it is possible, and even easier with Guava :) Use Multimaps.index(Iterable, Function):

    ImmutableListMultimap<E, E> indexed = Multimaps.index(list, groupFunction);
    

    If you give concrete use case it would be easier to show it in action.

    Example from docs:

    List<String> badGuys =
       Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
    Function<String, Integer> stringLengthFunction = ...;
    Multimap<Integer, String> index =
       Multimaps.index(badGuys, stringLengthFunction);
    System.out.println(index);
    

    prints

    {4=[Inky], 6=[Blinky], 5=[Pinky, Pinky, Clyde]}
    

    In your case if GroupFunction is defined as:

    GroupFunction<String> groupFunction = new GroupFunction<String>() {
      @Override public String sameGroup(final String s1, final String s2) {
        return s1.length().equals(s2.length());
      }
    }
    

    then it would translate to:

    Function<String, Integer> stringLengthFunction = new Function<String, Integer>() {
      @Override public Integer apply(final String s) {
        return s.length();
      }
    }
    

    which is possible stringLengthFunction implementation used in Guava's example.


    Finally, in Java 8, whole snippet could be even simpler, as lambas and method references are concise enough to be inlined:

    ImmutableListMultimap<E, E> indexed = Multimaps.index(list, String::length);
    

    For pure Java 8 (no Guava) example using Collector.groupingBy see Jeffrey Bosboom's answer, although there are few differences in that approach:

    • it doesn't return ImmutableListMultimap but rather Map with Collection values,
    • There are no guarantees on the type, mutability, serializability, or thread-safety of the Map returned (source),

    • it's a bit more verbose than Guava + method reference.

    EDIT: If you don't care about indexed keys you can fetch grouped values:

    List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), new Function<E, List<E>>() {
            @Override public List<E> apply(E key) {
                return indexed.get(key);
            }
    });
    
    // or the same view, but with Java 8 lambdas:
    List<List<E>> grouped = Lists.transform(indexed.keySet().asList(), indexed::get);
    

    what gives you Lists<List<E>> view which contents can be easily copied to ArrayList or just used as is, as you wanted in first place. Also note that indexed.get(key) is ImmutableList.

    // bonus: similar as above, but not a view, instead collecting to list using streams:
    List<List<E>> grouped = indexed.keySet().stream()
        .map(indexed::get)
        .collect(Collectors.toList());
    

    EDIT 2: As Petr Gladkikh mentions in comment below, if Collection<List<E>> is enough, above example could be simpler:

    Collection<List<E>> grouped = indexed.asMap().values();
    
    0 讨论(0)
  • 2020-12-13 00:56

    With Java 8, Guava and few helper functions you can implement grouping with custom Comparator

    public static <T> Map<T, List<T>> group(List<T> items, Comparator<T> comparator)
    {
        ListMultimap<T, T> blocks = LinkedListMultimap.create();
    
        if (!ArrayUtils.isNullOrEmpty(items))
        {
            T currentItem = null;
    
            for (T item : items)
            {
                if (currentItem == null || comparator.compare(currentItem, item) != 0)
                {
                    currentItem = item;
                }
    
                blocks.put(currentItem, ObjectUtils.clone(item));
            }
        }
    
        return Multimaps.asMap(blocks);
    }
    

    Example

    Comparator<SportExercise> comparator = Comparator.comparingInt(SportExercise::getEstimatedTime)
                    .thenComparingInt(SportExercise::getActiveTime).thenComparingInt(SportExercise::getIntervalCount)
                    .thenComparingLong(SportExercise::getExerciseId);
    
    Map<SportExercise, List<SportExercise>> blocks = group(sportWorkout.getTrainingExercises(), comparator);
    
    blocks.forEach((key, values) -> {
                System.out.println(key);
                System.out.println(values);
            });
    
    0 讨论(0)
  • 2020-12-13 01:03

    The easiest and simplest way would be using: Lamdaj grouping feature

    The above example can be re-written:

    List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
    Group group = group(badGuys, by(on(String.class).length)));
    System.out.println(group.keySet());
    
    0 讨论(0)
  • 2020-12-13 01:09

    Collector.groupingBy from the Java 8 streams library provides the same functionality as Guava's Multimaps.index. Here's the example in Xaerxess's answer, rewritten to use Java 8 streams:

    List<String> badGuys = Arrays.asList("Inky", "Blinky", "Pinky", "Pinky", "Clyde");
    Map<Integer, List<String>> index = badGuys.stream()
        .collect(Collectors.groupingBy(String::length));
    System.out.println(index);
    

    This will print

    {4=[Inky], 5=[Pinky, Pinky, Clyde], 6=[Blinky]}
    

    If you want to combine the values with the same key in some other way than creating a list, you can use the overload of groupingBy that takes another collector. This example concatenates the strings with a delimiter:

    Map<Integer, String> index = badGuys.stream()
        .collect(Collectors.groupingBy(String::length, Collectors.joining(" and ")));
    

    This will print

    {4=Inky, 5=Pinky and Pinky and Clyde, 6=Blinky}
    

    If you have a large list or your grouping function is expensive, you can go parallel using parallelStream and a concurrent collector.

    Map<Integer, List<String>> index = badGuys.parallelStream()
        .collect(Collectors.groupingByConcurrent(String::length));
    

    This may print (the order is no longer deterministic)

    {4=[Inky], 5=[Pinky, Clyde, Pinky], 6=[Blinky]}
    
    0 讨论(0)
提交回复
热议问题