Сглаживание коллекции

Скажи у меня есть Map<? extends Object, List<String>>

Я могу достаточно легко получить значения карты и перебрать ее, чтобы получить List<String>,

   for (List<String> list : someMap.values()) {
        someList.addAll(list);
    }

Есть ли способ сгладить его за один выстрел?

  List<String> someList = SomeMap.values().flatten();

8 ответов

Решение

Если вы используете Java 8, вы можете сделать что-то вроде этого:

someMap.values().forEach(someList::addAll);

Использование Java 8, и если вы предпочитаете не создавать экземпляр List пример самостоятельно, как в предложенном (и принятом) решении

someMap.values().forEach(someList::addAll);

Вы можете сделать все это с помощью этого оператора:

List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());

Кстати, должно быть интересно узнать, что на Java 8 принятая версия действительно самая быстрая. Он имеет примерно то же время, что и

for (List<String> item : someMap.values()) ...

и это намного быстрее, чем чистое потоковое решение. Вот мой маленький тест-код. Я явно не называю это эталоном, чтобы избежать возникающего в результате обсуждения недостатков эталона.;) Я делаю каждый тест дважды, чтобы, надеюсь, получить полную скомпилированную версию.

    Map<String, List<String>> map = new HashMap<>();
    long millis;

    map.put("test", Arrays.asList("1", "2", "3", "4"));
    map.put("test2", Arrays.asList("10", "20", "30", "40"));
    map.put("test3", Arrays.asList("100", "200", "300", "400"));

    int maxcounter = 1000000;

    System.out.println("1 stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);

    System.out.println("1 parallel stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);

    System.out.println("1 foreach");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        map.values().forEach(mylist::addAll);
    }
    System.out.println(System.currentTimeMillis() - millis);        

    System.out.println("1 for");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        for (List<String> item : map.values()) {
            mylist.addAll(item);
        }
    }
    System.out.println(System.currentTimeMillis() - millis);


    System.out.println("2 stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().stream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);

    System.out.println("2 parallel stream flatmap");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> someList = map.values().parallelStream().flatMap(c -> c.stream()).collect(Collectors.toList());
    }
    System.out.println(System.currentTimeMillis() - millis);

    System.out.println("2 foreach");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        map.values().forEach(mylist::addAll);
    }
    System.out.println(System.currentTimeMillis() - millis);        

    System.out.println("2 for");
    millis = System.currentTimeMillis();
    for (int i = 0; i < maxcounter; i++) {
        List<String> mylist = new ArrayList<String>();
        for (List<String> item : map.values()) {
            mylist.addAll(item);
        }
    }
    System.out.println(System.currentTimeMillis() - millis);

И вот результаты:

1 stream flatmap
468
1 parallel stream flatmap
1529
1 foreach
140
1 for
172
2 stream flatmap
296
2 parallel stream flatmap
1482
2 foreach
156
2 for
141

Изменить 2016-05-24 (два года спустя):

Выполнение того же теста с использованием фактической версии Java 8 (U92) на том же компьютере:

1 stream flatmap
313
1 parallel stream flatmap
3257
1 foreach
109
1 for
141
2 stream flatmap
219
2 parallel stream flatmap
3830
2 foreach
125
2 for
140

Кажется, что есть ускорение для последовательной обработки потоков и еще большие издержки для параллельных потоков.

Изменить 2018-10-18 (два года спустя):

Используя сейчас версию Java 10 (10.0.2) на той же машине:

1 stream flatmap
393
1 parallel stream flatmap
3683
1 foreach
157
1 for
175
2 stream flatmap
243
2 parallel stream flatmap
5945
2 foreach
128
2 for
187

Накладные расходы для параллельной потоковой передачи, кажется, больше.

При поиске "java 8 flatten" это единственное упоминание. И дело не в сглаживающем потоке. Так что для хорошего блага я просто оставляю это здесь

.flatMap(Collection::stream)

Я также удивлен, что никто не дал одновременный ответ Java 8 на оригинальный вопрос, который

.collect(ArrayList::new, ArrayList::addAll, ArrayList::addAll);

Предложено коллегой:

listOfLists.stream().flatMap(e -> e.stream()).collect(Lists.toList())

Мне нравится это лучше, чем forEach().

Если вы используете Eclipse Collections, вы можете использовать Iterate.flatten ().

MutableMap<String, MutableList<String>> map = Maps.mutable.empty();
map.put("Even", Lists.mutable.with("0", "2", "4"));
map.put("Odd", Lists.mutable.with("1", "3", "5"));
MutableList<String> flattened = Iterate.flatten(map, Lists.mutable.empty());
Assert.assertEquals(
    Lists.immutable.with("0", "1", "2", "3", "4", "5"),
    flattened.toSortedList());

flatten() является частным случаем более общего RichIterable.flatCollect ().

MutableList<String> flattened = 
    map.flatCollect(x -> x, Lists.mutable.empty());

Примечание: я являюсь коммиттером для Eclipse Collections.

Нет, нет более короткого метода. Вы должны использовать цикл.

Обновление апрель 2014: Java 8 наконец вышла. В новой версии вы можете использовать Iterable.forEach метод обхода коллекции без использования явного цикла.

Обновление ноябрь 2017: случайно нашел этот вопрос, когда искал современное решение. Закончилось идти с reduce:

someMap.values().stream().reduce(new ArrayList(), (accum, list) -> {
    accum.addAll(list);
    return accum;
}):

Это позволяет избежать зависимости от изменяемого внешнего состояния forEach(someList::addAll) накладные расходы flatMap(List::stream),

Если вы просто хотите перебрать значения, вы можете избежать всех этих методов addAll.

Все, что вам нужно сделать, это написать класс, который инкапсулирует вашу карту, и который реализует итератор:

public class ListMap<K,V> implements Iterator<V>
{
  private final Map<K,List<V>> _map;
  private Iterator<Map.Entry<K,List<V>>> _it1 = null;
  private Iterator<V> _it2 = null;

  public ListMap(Map<K,List<V>> map)
  {
    _map = map;
    _it1 = map.entrySet().iterator(); 
    nextList();
  }

  public boolean hasNext()
  {
    return _it2!=null && _it2.hasNext();
  }

  public V next()
  {
    if(_it2!=null && _it2.hasNext())
    {
      return _it2.next();
    }
    else
    {
      throw new NoSuchElementException();
    }
    nextList();
  } 

  public void remove()
  {
    throw new NotImplementedException();
  }

  private void nextList()
  {
    while(_it1.hasNext() && !_it2.hasNext())
    {
      _it2 = _it1.next().value();
    }
  }
}

Свести на функцию:

    private <A, T> List<T> flatten(List<A> list, Function<A, List<T>> flattenFn) {
        return list
                .stream()
                .map(flattenFn)
                .flatMap(Collection::stream)
                .collect(Collectors.toUnmodifiableList());
    }

Хорошим решением для подслоя Карты является хранение, если возможно, данных в Guava. Table,

https://github.com/google/guava/wiki/NewCollectionTypesExplained

Так, например, Map<String,Map<String,String>> заменяется Table<String,String,String> который уже плоский. На самом деле, документы говорят, что HashBasedTable, TableРеализация хеша, по сути, поддерживается HashMap<R, HashMap<C, V>>

Другие вопросы по тегам