2016-05-26 2 views
1

Если элемент меньше, чем при условии, что перемещение влево, если оно больше, перейдите вправо. Простой код с Java 7 стиль:java 8 paralel stream реорганизовать список по заданному значению

private static <T extends Comparable> List<T> doAction(List<T> original, T state) { 
    List<T> left = new ArrayList<T>(); 
    List<T> fight = new ArrayList<T>(); 
    for (T e : original) { 
     if (e.compareTo(state) < 0) { 
      left.add(e); 
     } else { 
      fight.add(e); 
     } 
    } 
    left.addAll(fight); 
    return left; 
} 

Как переписать код выше Java 8 стиля потока с помощью параллельного потока?

+0

Почему вы считаете, что параллельный поток поможет в вашем случае? у вас есть большой список, чтобы сортировать вот так? –

+0

Каждая проблема не поддается распараллеливанию; это может быть один из них. – duffymo

+5

@Nicolas Filotto: упорядоченный параллельный поток будет поддерживать порядок встреч, даже если порядок обработки отличается, см. [Здесь] (http://stackoverflow.com/a/29218074/2711488) – Holger

ответ

4

Один подход, будучи эквивалентен исходному коду, будет:

private static <T extends Comparable<T>> List<T> doAction(List<T> original, T state) { 
    Map<Boolean, List<T>> collect = original.parallelStream() 
     .collect(Collectors.partitioningBy(e -> e.compareTo(state) < 0)); 
    List<T> left = collect.get(true); 
    List<T> right = collect.get(false); 
    return Stream.of(left, right).flatMap(List::stream).collect(Collectors.toList()); 
} 

Однако, так как вы только разделить данные, чтобы присоединиться к ним после этого, более простым решением было бы:

private static <T extends Comparable<T>> List<T> doAction(List<T> original, T state) { 
    return original.parallelStream() 
     .sorted(Comparator.comparingInt(e -> Integer.signum(e.compareTo(state))|1)) 
     .collect(Collectors.toList()); 
} 

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


Обратите внимание, что второй подход может быть даже выполнены на месте, в случае, если оригинал List изменчиво (и вам не нужно первоначальный порядок больше):

original.sort(Comparator.comparingInt(e -> Integer.signum(e.compareTo(state))|1)); 
+0

делает это действительно помогает создать новый поток с двумя списками для создания единого списка? почему бы не просто left.addAll (справа), вернуться влево ;? –

+1

@Nicolas Filotto: потому что 'left' не гарантированно является изменчивым списком. В текущей реализации это 'ArrayList', но это может измениться. Вам нужно будет использовать более сложный сборщик 'Collectors.partitioningBy (..., Collectors.toCollection (ArrayList :: new))', чтобы быть уверенным, что вы можете вызывать 'addAll' в списках результатов. Однако, если у вас нет очень маленького «правого» списка, маловероятно, что емкость 'left' достаточна для последующего' addAll', поэтому количество операций копирования, выполняемых под капотом, не изменяется. Поэтому я держал коллекционера просто. – Holger

+1

В качестве альтернативы вы можете сказать 'left = new ArrayList <> (слева); оставил.addAll (справа); 'или, если важна производительность,' result = new ArrayList <> (left.size() + right.size()); result.addAll (слева); result.addAll (справа); ', как сказано, я просто хотел, чтобы это было просто. В любом случае, я предпочел бы второй вариант. – Holger

2

Хотя Holger ответ что вы (или другие читатели) могут быть заинтересованы в написании собственного Collector или знать, как вы его напишете.

Во-первых, вам нужен способ, чтобы представить частичные результаты

static class PartialResult<T extends Comparable<T>> { 
    public List<T> left = new ArrayList<>(); 
    public List<T> right = new ArrayList<>(); 
} 

И теперь мы определяем наши собственные Collector,

static class PartitionCollector<T extends Comparable<T>> implements Collector<T,PartialResult<T>,List<T>>{ 

    private final T pivot; 

    public PartitionCollector(T pivot){ 
     this.pivot = pivot; 
    } 

    @Override 
    public Supplier<ResultPair<T>> supplier() { 
     return ResultPair::new; 
    } 

    @Override 
    public BiConsumer<PartialResult<T>, T> accumulator() { 
     return (result, e) -> { 
     if (e.compareTo(pivot) < 0) { 
       result.left.add(e); 
      }else { 
       result.right.add(e); 
      } 
     }; 
    } 

    @Override 
    public BinaryOperator<PartialResult<T>> combiner() { 
     BinaryOperator<PartialResult<T>> mergeOp = (r1,r2) ->{ 
      r1.left.addAll(r2.left); 
      r1.right.addAll(r2.right); 
      return r1; 
     }; 
     return mergeOp; 
    } 

    @Override 
    public Function<PartialResult<T>, List<T>> finisher() { 
     Function<PartialResult<T>,List<T>> finisher = r -> { 
      List<T> finalResult = new ArrayList<>(r.left.size() + r.right.size()); 
      finalResult.addAll(r.left); 
      finalResult.addAll(r.right); 
      return finalResult; 
     }; 
     return finisher; 
    } 

    @Override 
    public Set<Characteristics> characteristics() { 
     return Collections.unmodifiableSet(EnumSet.of(Characteristics.CONCURRENT)); 
    } 
} 
  1. supplier легко, мы просто должны сказать ему для создания нового экземпляра.
  2. accumulator, учитывая элемент, мы поместим его в свою часть соответственно.
  3. combiner, у нас есть два частичных результата (многопоточные вычисления, рассчитанные на две ветви нашего дерева решений), объединить их в один.
  4. finsiher, в конце, я хочу полный список, а не частичное решение.
+2

Обратите внимание, что объединителю разрешено изменять один из его аргументов и возвращать его, поэтому вместо создания нового 'ResultPair' вы можете просто сделать' (r1, r2) -> {r1.left. addAll (r2.left); r1.right.addAll (r2.right); return r1; } '. Кроме того, аккумулятор можно упростить до '(result, e) -> (e.compareTo (pivot) <0? Result.left: result.right) .add (e)' – Holger

+1

@ Хороший улов. Спасибо. –

Смежные вопросы