2015-12-04 6 views
15

のは、我々はこのストリームJavaストリーム:2つの要素を1つではなく1つずつ取り出す方法はありますか?

Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j"); 

を持っていると私は最初の1で始まるした隣接する文字列のカップルが「誤る」マップに保存したいとしましょう。

私は何を考えると、この

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

Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j") 
.reduce((acc, next) -> { 
    if (acc.startsWith("err")) 
     map.put(acc,next); 
    if (next.startsWith("err")) 
     return next; 
    else 
     return ""; 
}); 

のようなものです。しかし、私はreduce機能を「悪用」しています2つの主な理由

  1. のためのそれとは全く満足していません。 Stream APIでは、すべての関数に明確な明確な目的があります。maxは最大値を計算することになっています。filterは条件に基づいてフィルタリングすることになっています。reduceは徐々に累積された値を生成すると考えられます。
  2. このようにすると、Streamsの強力なメカニズムを使用できなくなります。検索結果を最初の2つの検索結果に限定したい場合、どうすればよいでしょうか?

ここで、それはあなたが、何らかの形で、バック「現在値」と「次の値」に似た何かにつながることができますが、値のカップルを比較することができますのみの機能です(私の知る限りでは)ので、私はreduceを使用概念。

もっと簡単な方法はありますか?それぞれの反復ごとに複数の値を考慮してストリームを反復できるものがありますか?

EDIT

は私が考えていることは、現在の要素を考えると、あなたは反復ごとに、検討する「要素の窓」を定義することができ、いくつかのメカニズムです。現在のAPIへの強力な "アップグレード" になり

<R> Stream<R> mapMoreThanOne(
    int elementsBeforeCurrent, 
    int elementsAfterCurrent, 
    Function<List<? super T>, ? extends R> mapper); 

代わりの

<R> Stream<R> map(Function<? super T, ? extends R> mapper); 

よう

何か。

EDIT2

私は彼らのソリューションを提案し、人々の努力に感謝しますが、問題は、アルゴリズムそのものではありません。ストリーム、インデックス、以前の値を格納するための一時変数をまとめることで私の目標を達成する方法はいくつかありますが、現在の要素以外の要素を扱うために設計されたStream APIには、 "ストリームのパラダイム"を壊すことなく。答えを考えると、この

List<String> list = 
     Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j") 
     .filterFunctionImWonderingIfExist(/*filters couples of elements*/) 
     .limit(2) 
     .collect(Collectors.toList()); 

ような何か、私は「明確かつ迅速な」解決策はありませんだと思う、StreamExライブラリ

+1

コレクタが必要なように見えます – Bohemian

+0

私はそれについて考えましたが、(すてきで清潔な)解決策を見つけることができませんでした –

答えて

9

このタスクのカスタムコレクタを構築できます。

Map<String, String> map = 
    Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j") 
      .collect(MappingErrors.collector()); 

:このコレクタで

private static final class MappingErrors { 

    private Map<String, String> map = new HashMap<>(); 

    private String first, second; 

    public void accept(String str) { 
     first = second; 
     second = str; 
     if (first != null && first.startsWith("err")) { 
      map.put(first, second); 
     } 
    } 

    public MappingErrors combine(MappingErrors other) { 
     throw new UnsupportedOperationException("Parallel Stream not supported"); 
    } 

    public Map<String, String> finish() { 
     return map; 
    } 

    public static Collector<String, ?, Map<String, String>> collector() { 
     return Collector.of(MappingErrors::new, MappingErrors::accept, MappingErrors::combine, MappingErrors::finish); 
    } 

} 

は二つの走行要素が保持されます。 Stringが承認されるたびに更新され、最初に"err"で始まる場合、2つの要素がマップに追加されます。


別の解決策は、このストリームの要素のすべての隣接する対に与えられた関数を適用pairMap方法を提供StreamExライブラリを使用することです。次のコードでは、最初の要素が"err"、それ以外の場合はnullで始まる場合は、ペアの最初と2番目の要素で構成される文字列配列が返されます。 nullの要素が除外され、ストリームがマップに収集されます。

Map<String, String> map = 
    StreamEx.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j") 
      .pairMap((s1, s2) -> s1.startsWith("err") ? new String[] { s1, s2 } : null) 
      .nonNull() 
      .toMap(a -> a[0], a -> a[1]); 

System.out.println(map); 
+0

それでは、もっと簡単な方法はありません。 +1 StreamExライブラリ、非常に興味深いプロジェクト! 'PairMap'は私のニーズを完全に解決します。 –

+1

@LuigiCortese私はそうは考えていません。それは残念です。 Scalaには素晴らしい['sliding'](http://www.scala-lang.org/api/current/index.html#[email protected]%28size:Int,step:Int%29:Iterator [ Repr])メソッドを使用してください。これは、固定サイズブロ​​ックの要素をグループ化します。 – Tunaki

+0

私はそれについて聞いたことがあります。 IMHOは真ん中にある大きな、深い穴です。 –

5

を使用しない限り、あなたはカスタムコレクタを書く、または上のストリーミングの非常に単純なアプローチを使用することができますリストのインデックス:

Map<String, String> result = IntStream.range(0, data.size() - 1) 
     .filter(i -> data.get(i).startsWith("err")) 
     .boxed() 
     .collect(toMap(data::get, i -> data.get(i+1))); 

これは、データがランダムアクセスフレンドリリストにあるか、一時的に1つにダンプできることを前提としています。

あなたがランダムにデータにアクセスしたり、処理のために、リストや配列にロードすることができない場合は、ここではコレクターのためのソースです

Map<String, String> result = data.stream() 
     .collect(pairing(
       (a, b) -> a.startsWith("err"), 
       AbstractMap.SimpleImmutableEntry::new, 
       toMap(Map.Entry::getKey, Map.Entry::getValue) 
     )); 

を書くことができますので、あなたは常にカスタムpairingコレクタを作ることができます。これは並列に対応しており、他の状況では便利です:

public static <T, V, A, R> Collector<T, ?, R> pairing(BiPredicate<T, T> filter, BiFunction<T, T, V> map, Collector<? super V, A, R> downstream) { 

    class Pairing { 
     T left, right; 
     A middle = downstream.supplier().get(); 
     boolean empty = true; 

     void add(T t) { 
      if (empty) { 
       left = t; 
       empty = false; 
      } else if (filter.test(right, t)) { 
       downstream.accumulator().accept(middle, map.apply(right, t)); 
      } 
      right = t; 
     } 

     Pairing combine(Pairing other) { 
      if (!other.empty) { 
       this.add(other.left); 
       this.middle = downstream.combiner().apply(this.middle, other.middle); 
       this.right = other.right; 
      } 
      return this; 
     } 

     R finish() { 
      return downstream.finisher().apply(middle); 
     } 
    } 

    return Collector.of(Pairing::new, Pairing::add, Pairing::combine, Pairing::finish); 
} 
+0

元の問題のソースストリームはログファイルなので、インデックスへ –

4

あなたの入力がランダムアクセスリストにある場合は簡単でしょう。けれども

List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e", 
    "f", "g", "h", "err3", "i", "j"); 

Map<String, String> map = StreamEx.ofSubLists(list, 2, 1) 
    .mapToEntry(l -> l.get(0), l -> l.get(1)) 
    .filterKeys(key -> key.startsWith("err")) 
    .toMap(); 

:同じことが少し短いようにし(私が書いた)すでに述べたStreamExライブラリで行うことができる

List<String> list = Arrays.asList("a", "b", "err1", "c", "d", "err2", "e", 
    "f", "g", "h", "err3", "i", "j"); 

Map<String, String> map = IntStream.range(0, list.size()-1) 
    .mapToObj(i -> list.subList(i, i+2)) 
    .filter(l -> l.get(0).startsWith("err")) 
    .collect(Collectors.toMap(l -> l.get(0), l -> l.get(1))); 

:あなたはこのような古き良きList.subList方法を利用することができますこの方法サードパーティの依存関係が必要ない場合、不十分なStream APIソリューションもそれほど悪くないように見えます。

+1

私はStreamExの背後にあるアイデアが好きです。私はすぐに興味深いPRを出すことを望みます。) –

+0

@LuigiCortese、私は熱心にすべての強化を受け入れていません。あらゆる強化には、評価するために多くの賛否両論があります。たとえば、私は、パラレル・パフォーマンスが悪い新しい機能を拒否する可能性があります。おそらく、あなたのアイデアを[issue tracker](https://github.com/amaembo/streamex/issues)で議論することをお勧めします。 –

-1

はここで既製のコレクタを使用して、簡単な1つのライナーです:

Stream<String> stream = Stream.of("a", "b", "err1", "c", "d", "err2", "e", "f", "g", "h", "err3", "i", "j"); 

Map<String, String> map = Arrays.stream(stream 
     .collect(Collectors.joining(",")).split(",(?=(([^,]*,){2})*[^,]*$)")) 
    .filter(s -> s.startsWith("err")) 
    .map(s -> s.split(",")) 
    .collect(Collectors.toMap(a -> a[0], a -> a[1])); 

「トリック」ここで、最初に一緒に単一の文字列にすべての用語に参加することで、その後のペアの文字列に分割し、 "a,b""err1,c"などです。ペアのストリームが作成されると、処理は簡単です。

関連する問題