Оптимизировать вставку из ArrayList в HashMap - PullRequest
0 голосов
/ 05 августа 2020

Я пытаюсь вставить данные из ArrayList в HashMap оптимально.

Многие элементы могут иметь одно и то же имя_языка (код ниже), поэтому мне нужно сгруппировать элементы, имеющие один и тот же язык, в классе Language и сохранить языки в HashMap с именем языка в качестве ключа.

Элемент

String name;
String language_name;

Язык

String language_name;
int numberItems; 
LinkedList<String> Items;

Я решил это следующим образом:

        ArrayList<Item> items; // given array of items
        HashMap<String, Language> languages = new HashMap<String, Language>();

        items.forEach(item -> {
            /** case 1: language isn't specified */
            if (item.getLanguageName() == null) {
                item.setLanguageName("unknown");
            }
            /** case 2: language already added */
            if (languages.containsKey(item.getLanguageName())) {
                languages.get(item.getLanguageName()).getItems().add(item.getName());
                languages.get(item.getLanguageName())
                        .setNumberItems(languages.get(item.getLanguageName()).getNumberItems() + 1);
            } else {
                /** case 3: language isn't added yet */
                LinkedList<String> languageItems = new LinkedList<String>();
                languageItems.add(item.getName());
                Language language = new Language(item.getLanguageName(), 1, languageItems);
                languages.put(item.getLanguageName(), language);
            }
        });

Любая помощь приветствуется!

Ответы [ 2 ]

1 голос
/ 05 августа 2020

Предполагая, что вы используете Java 8 или новее, это может быть прекрасно выполнено с помощью встроенных потоковых функций.

HashMap<String, List<Items>> itemsGroupedByLanguage =
 items.stream().collect(Collectors.groupingBy(Items::getLanguage));
0 голосов
/ 09 августа 2020

tl; dr

Невозможно достичь желаемого с помощью встроенного сборщика Java (8+), но вы можете написать свой собственный сборщик и написать код, как показано ниже для сбора на карту как -

Map<String, Language> languages = items.stream().collect(LanguageCollector.toLanguage());

Давайте сначала посмотрим на Collector<T, A, R> interface

public interface Collector<T, A, R> {
    /**
     * A function that creates and returns a new mutable result container.
     */
    Supplier<A> supplier();

    /**
     * A function that folds a value into a mutable result container.
     */
    BiConsumer<A, T> accumulator();

    /**
     * A function that accepts two partial results and merges them.  The
     * combiner function may fold state from one argument into the other and
     * return that, or may return a new result container.
     */
    BinaryOperator<A> combiner();

    /**
     * Perform the final transformation from the intermediate accumulation type
     */
    Function<A, R> finisher();

    /**
     * Returns a  Set of Collector.Characteristics indicating
     * the characteristics of this Collector.  This set should be immutable.
     */
    Set<Characteristics> characteristics();
}

Где T - это общий c тип элементов в потоке, которые нужно собрать. A - это тип аккумулятора, объекта, на котором будет накапливаться частичный результат в процессе сбора. R - это тип объекта (обычно, но не всегда, коллекция), полученного в результате операции сбора

Теперь давайте посмотрим на пользовательский LanguageCollector

  public class LanguageCollector
      implements Collector<Item, Map<String, Language>, Map<String, Language>> {

    /**
     * The supplier method has to return a Supplier of an empty accumulator - a parameterless
     * function that when invoked creates an instance of an empty accumulator used during the
     * collection process.
     */
    @Override
    public Supplier<Map<String, Language>> supplier() {

      return HashMap::new;
    }

    /**
     * The accumulator method returns the function that performs the reduction operation. When
     * traversing the nth element in the stream, this function is applied with two arguments, the
     * accumulator being the result of the reduction (after having collected the first n–1 items of
     * the stream) and the nth element itself. The function returns void because the accumulator is
     * modified in place, meaning that its internal state is changed by the function application to
     * reflect the effect of the traversed element
     */
    @Override
    public BiConsumer<Map<String, Language>, Item> accumulator() {

      return (map, item) -> {
        if (item.getLanguageName() == null) {
          item.setLanguageName("unknown");
        } else if (map.containsKey(item.getLanguageName())) {
          map.get(item.getLanguageName()).getItems().add(item.getName());
          map.get(item.getLanguageName())
              .setNumberItems(map.get(item.getLanguageName()).getNumberItems() + 1);
        } else {
          Language language = new Language(item.getLanguageName(), 1);
          language.add(item.getName());
          map.put(item.getLanguageName(), language);
        }
      };
    }

    /**
     * The combiner method, return a function used by the reduction operation, defines how the
     * accumulators resulting from the reduction of different subparts of the stream are combined
     * when the subparts are processed in parallel
     */
    @Override
    public BinaryOperator<Map<String, Language>> combiner() {
      return (map1, map2) -> {
          map1.putAll(map2);
          return map1;
       };
    }

    /**
     * The finisher() method needs to return a function which transforms the accumulator to the
     * final result. In this case, the accumulator is the final result as well. Therefore it is
     * possible to return the identity function
     */
    @Override
    public Function<Map<String, Language>, Map<String, Language>> finisher() {
      return Function.identity();
    }

    /**
     * The characteristics, returns an immutable set of Characteristics, defining the behavior of
     * the collector—in particular providing hints about whether the stream can be reduced in
     * parallel and which optimizations are valid when doing so
     */
    @Override
    public Set<Characteristics> characteristics() {
      return Collections.unmodifiableSet(
          EnumSet.of(Characteristics.IDENTITY_FINISH));
    }

    /**
     * Static method to create LanguageCollector
     */
    public static LanguageCollector toLanguage() {
      return new LanguageCollector();
    }
  }

У меня есть немного изменил ваши классы до (чтобы следовать соглашению об именах и более для работы с читаемым аккумулятором).

Class Item

public class Item {
    private String name;
    private String languageName;

    public Item(String name, String languageName) {
      this.name = name;
      this.languageName = languageName;
    }
    //Getter and Setter
  }

Class Language

public class Language {
    private String languageName;
    private int numberItems;
    private LinkedList<String> items;

    public Language(String languageName, int numberItems) {
      this.languageName = languageName;
      this.numberItems = numberItems;
      items = new LinkedList<>();
    }

    public void add(String item) {
      items.add(item);
    }

    // Getter and Setter

    public String toString() {
      return "Language(languageName=" + this.getLanguageName() + ", numberItems=" + this.getNumberItems() + ", items=" + this.getItems() + ")";
    }
  }

Выполняемый код

public static void main(String[] args) {
    List<Item> items =
        Arrays.asList(
            new Item("ItemA", "Java"),
            new Item("ItemB", "Python"),
            new Item("ItemC", "Java"),
            new Item("ItemD", "Ruby"),
            new Item("ItemE", "Python"));

    Map<String, Language> languages = items.stream().collect(LanguageCollector.toLanguage());

    System.out.println(languages);
  }

печатает

{Java=Language(languageName=Java, numberItems=2, items=[ItemA, ItemC]), Ruby=Language(languageName=Ruby, numberItems=1, items=[ItemD]), Python=Language(languageName=Python, numberItems=2, items=[ItemB, ItemE])}

Для получения дополнительной информации прочтите книгу «Современные Java в действии: лямбда-выражения, потоки, функциональное и реактивное программирование», глава 6.5 или проверьте по этой ссылке

...