class CollectorUtil {

    private CollectorUtil() {}

    /**
     * Returns a new {@link MapStream} where the elements have been grouped together using
     * the specified function.
     * 
     * @param <T>      the stream element type
     * @param <C>      the type of the key to group by
     * @param grouper  the function to use for grouping
     * @return         a {@link MapStream} grouped by key
     */
    public static <T, C> Collector<T, ?, MapStream<C, List<T>>> groupBy(Function<T, C> grouper) {<FILL_FUNCTION_BODY>}

    protected static class GroupHolder<C, T> {

        private final Function<T, C> grouper;
        private final Map<C, List<T>> elements;

        private final Function<C, List<T>> createList = c -> new ArrayList<>();

        protected GroupHolder(Function<T, C> grouper) {
            this.grouper = grouper;
            this.elements = new HashMap<>();
        }

        public void add(T element) {
            final C key = grouper.apply(element);
            elements.computeIfAbsent(key, createList)
                .add(element);
        }

        public GroupHolder<C, T> merge(GroupHolder<C, T> holder) {
            holder.elements.entrySet().forEach(e
                -> elements.computeIfAbsent(e.getKey(), createList)
                .addAll(e.getValue())
            );

            return this;
        }

        public MapStream<C, List<T>> finisher() {
            return MapStream.of(elements);
        }
    }

}

        return Collector.of(
            () -> new GroupHolder<>(grouper),
            GroupHolder::add,
            GroupHolder::merge,
            GroupHolder::finisher
        );
    
<no_super_class>