2015-12-11 19 views
10

è possibile controllare se una matrice (o raccolta) contiene elemento 5 e elemento diverso da 5. In uno flusso di ritorno risultato booleano invece di utilizzare due flussi:multipli "match" assegni in un unico flusso

int[] ints = new int[]{1, 2, 3, 4, 5}; 

boolean hasFive = IntStream.of(ints).anyMatch(num -> num == 5); 
boolean hasNonFive = IntStream.of(ints).anyMatch(num -> num != 5); 

boolean result = hasFive && hasNonFive; 

risposta

5

Un modo che vedo come fare è creare un numero personalizzato IntPredicate da più IntPredicate s. Ogni volta che viene testato un valore, proviamo a trovare un predicato da questo array che lo abbini e, in caso affermativo, lo memorizziamo all'interno di un interno Set (per gestire correttamente i duplicati). Quando il set memorizzato ha le stesse dimensioni dell'array iniziale, significa che tutti i predicati sono stati abbinati e il nostro predicato personalizzato può restituire true.

La mia soluzione iniziale utilizzava uno Set<Integer> per memorizzare gli indici dei predicati corrispondenti. Come commentato da @Holger, potrebbe essere più efficiente usare uno BitSet e memorizzare gli indici dei predicati senza corrispondenza.

private static class MultipleIntPredicate implements IntPredicate { 

    private IntPredicate[] predicates; 
    private BitSet unmatchedPredicates; 

    public MultipleIntPredicate(IntPredicate... predicates) { 
     this.predicates = predicates; 
     unmatchedPredicates = new BitSet(predicates.length); 
     unmatchedPredicates.set(0, predicates.length, true); // initially, all predicates are unmatched 
    } 

    @Override 
    public boolean test(int value) { 
     unmatchedPredicates.stream() 
          .filter(i -> predicates[i].test(value)) 
          .findFirst() 
          .ifPresent(unmatchedPredicates::clear); // when a match is found, clear the BitSet 
     return unmatchedPredicates.isEmpty(); // return true if all the predicates were matched 
    } 

} 

Utilizzando in questo modo:

int[] ints = new int[] {1, 2, 3, 4, 5}; 
MultipleIntPredicate predicate = new MultipleIntPredicate(num -> num == 5, num -> num != 5); 
boolean hasFiveAndNonFive = IntStream.of(ints).anyMatch(predicate); 
System.out.println(hasFiveAndNonFive); 

Per il caso di un array, come nella sua interrogazione, questa soluzione è probabilmente più in alto di quanto l'iterazione di matrice due volte. Tuttavia, nel caso di un numero infinito di IntStream, questo predicato funzionerà ancora correttamente. Ha anche il vantaggio che i predicati ricercati non devono essere opposti a se stessi.

+2

Beh, questa soluzione utilizza una mutabile 'BitSet', sarebbe non meglio evitare che come l'intera API Stream è circa evitare stato mutevole? – user140547

2

Se non ti dispiace utilizzando un flusso in scatola e 2 predicati sono sufficienti, è possibile utilizzare Collectors.partitioningBy e proprio fare qualcosa di simile:

Map<Boolean, List<Integer>> collect = IntStream.of(ints).boxed().collect(Collectors.partitioningBy(x -> x == 5)); 

    boolean hasFive = !collect.get(true).isEmpty(); 
    boolean hasNonFive = !collect.get(false).isEmpty(); 

Un'altra soluzione (per molteplici predicati) che è forse non così performante come soluzione di Tunaki e probabilmente crea troppi array, ma non utilizza un mutevole BitSet ...

Boolean[] result = IntStream.of(ints).mapToObj(i -> 
        new Boolean[]{four.test(i), five.test(i), six.test(i)} 
    ).reduce(new Boolean[]{false, false, false}, Test::or); 
+3

Dato che sei interessato solo al fatto che ci siano elementi di entrambe le partizioni, puoi usare 'Collectors.partitioningBy (x -> x == 5, Collectors.counting()))' per evitare la memorizzazione non necessaria degli elementi in 'List' s e basta controllare tramite 'collect.get (true)! = 0' e' collect.get (false)! = 0'. Nota che se usi 'groupingBy' invece di' partitioningBy', i gruppi inesistenti sono semplicemente assenti nella 'Mappa' risultante e puoi semplicemente controllare tramite' collect.containsKey (true) 'resp. 'collect.containsKey (false)' (o 'collect.size() == 2' per entrambi) ... – Holger

6

In questo caso specifico, vale a dire che si desidera sapere se un flusso o array contiene sia una corrispondenza e una non corrispondenti elemen t (un elemento che corrisponde alla negazione del predicato), puoi farlo molto più semplice.

In primo luogo, verificare se il primo elemento corrisponde al predicato o la sua negazione, quindi, cercare se il flusso contiene una partita del contrario:

IntPredicate predicate=i -> i==5; 

if(ints.length>0 && predicate.test(ints[0])) 
    predicate=predicate.negate(); 
boolean result = IntStream.of(ints).anyMatch(predicate); 

Questo è tutto. Nel caso in cui non si dispone di una matrice o raccolta come fonte ruscello, ma un flusso arbitraria, testando il primo elemento è un po 'più complicato:

IntPredicate[] tmp={ null }; 
Spliterator.OfInt sp=intStream.spliterator(); 
boolean result = sp.tryAdvance(
    (int i) -> tmp[0]=predicate.test(i)? predicate.negate(): predicate) 
&& StreamSupport.intStream(sp, false).anyMatch(tmp[0]); 
6

Ecco due soluzioni che coinvolgono la mia libreria StreamEx. La caratteristica principale che sto usando qui è il concetto di collettori a corto circuito.La mia biblioteca esalta il concetto Collector per fornire la capacità di corto circuito (che funziona sia per i flussi sequenziali e paralleli)

Se predicati sono come nel campione (uno è l'opposto di un altro), è possibile utilizzare partitioningBy:

Map<Boolean, Optional<Integer>> map = IntStreamEx.of(ints).boxed() 
     .partitioningBy(num -> num == 5, MoreCollectors.first()); 

Ora si dovrebbe verificare se entrambe le mappature sono presenti:

System.out.println(map.values().stream().allMatch(Optional::isPresent)); 

O in un'unica istruzione:

System.out.println(IntStreamEx.of(ints).boxed() 
     .partitioningBy(num -> num == 5, MoreCollectors.first()) 
     .values().stream().allMatch(Optional::isPresent)); 

Qui stiamo usando il collettore di cortocircuito MoreCollectors.first(). Questa soluzione è simile a quella proposta da @ user140547, ma in realtà smetterà di processare non appena vengono trovati entrambi gli elementi.


Per due personalizzata predicati è possibile utilizzare pairing collettore che combina i risultati di due collettori (conservando l'cortocircuitando se collettori di ingresso sono corto circuito). Ma in primo luogo, abbiamo bisogno anyMatching collezionista (che è assente nella mia libreria):

import static one.util.streamex.MoreCollectors.*; 

static <T> Collector<T, ?, Boolean> anyMatching(Predicate<T> pred) { 
    return collectingAndThen(filtering(pred, first()), Optional::isPresent); 
} 

Collector<Integer, ?, Boolean> hasFive = anyMatching(num -> num == 5); 
Collector<Integer, ?, Boolean> hasNonFive = anyMatching(num -> num != 5); 
Collector<Integer, ?, Boolean> hasBoth = pairing(hasFive, hasNonFive, 
      (res1, res2) -> res1 && res2); 

System.out.println(IntStreamEx.of(ints).boxed().collect(hasBoth));