Cortocircuitare un flusso infinito di Java 8

-3

Si supponga

  1. Hai un POJO% come% di co_de %codice%
  2. Hai un po 'di enum Animal public class Animal { // some fields }

  3. Hai un metodo che a un AnimalType restituisce un public enum AnimalType { // some animal types } con firma Animal

  4. Infine, hai un flusso infinito di animali AnimalType

Vuoi raccogliere in una struttura il primo di ogni occorrenza di ciascun tipo di animale. Dato che stai iterando su un flusso infinito, devi anche restituire il primo momento in cui hai soddisfatto i requisiti della struttura.

Come risolveresti questo / potresti risolvere questo senza usare lo stato al di fuori del flusso? Questa non è una domanda a casa. Mi sono imbattuto in qualcosa di simile e non sono riuscito a trovare una soluzione solo streaming (nessun flusso esterno).

    
posta geofflittle 18.04.2018 - 20:33
fonte

1 risposta

2

could you solve this without using state outside of the stream

No, non puoi. Ma non importa. Lascia che ti mostri perché:

Assume

  1. You have some POJO Animal like public class Animal { // some fields }

Poiché il requisito 3 insiste sulla possibilità di ottenere l'enum AnimalType da Animal diventa un campo tipo. Aggiunta di un campo tag per distinguere le istanze durante la visualizzazione.

class Animal {

    public Animal( String tag, AnimalType type ) { 
        this.tag = tag;
        this.type = type;
    }

    public AnimalType getAnimalType() { return type; }

    public String toString() { return tag + " " + type + " animal"; }

    @Override
    public int hashCode() { return Objects.hashCode( type ); }

    @Override
    public boolean equals( Object that ) {        
        return that != null
            && that.getClass() == this.getClass()     
            && ( (Animal) that ).type == this.type
        ;
    }

    private String tag;
    private AnimalType type;
}

Nota che un'identità di animali è legata al tipo non tag.

Vedi elenco completo .

  1. You have some enum AnimalType like public enum AnimalType { // some animal types }
enum AnimalType{ HUMAN, DOG, CAT }
  1. You have some method that given an Animal returns an AnimalType with signature public AnimalType getAnimalType(Animal animal);

Non ho davvero bisogno di questo, ma OK.

public AnimalType getAnimalType(Animal animal) { return animal.getAnimalType(); }
  1. Finally, you have some infinite stream of animals Stream<Animal>

Questo dà un flusso infinito dando questi quattro ancora e ancora. Il secondo animale umano dovrebbe essere rimosso poiché non è "il primo di ogni occorrenza di ciascun tipo di animale".

List<Animal> animals = Arrays
    .asList(
        new Animal( "first", AnimalType.HUMAN ), 
        new Animal( "first", AnimalType.DOG ), 
        new Animal( "second", AnimalType.HUMAN ), 
        new Animal( "first", AnimalType.CAT ) 
    )
;

Stream<Integer> infiniteStreamOfInts = Stream.iterate( 0, i->i+1 );

Stream<Animal> infiniteStreamOfAnimals = infiniteStreamOfInts
    .map( 
        i->animals.get( 
            i % animals.size() 
        ) 
    )
;

You want to collect into some structure the first of each occurrence of each animal type. Given that you're iterating over an infinite stream, you also want to return the first moment you've satisfied the structure requirements.

Ciò significa che devi essere in grado di testare la struttura per vedere quando hai ogni tipo di animale.

How would you solve this / could you solve this without using state outside of the stream? This is not a homework question. I came across something similar and wasn't able to come up with a stream-only (no outside stream state) solution.

Beh, non puoi. Il flusso non sa cosa è successo prima. Ma senza "stato di flusso esterno" non è possibile raccogliere comunque.

@CandiedOrange For example, when I map over a list, I can collect the results into a list via .collect(Collectors.toList()). This allows me to refrain from declaring a list outside of the scope of the stream and performing a forEach modifying that list – geofflittle

Questo non è "all'interno dello stato del flusso". Questo è solo fornire un generatore allo stream in modo che possa richiedere una nuova raccolta per raccogliere elementi in. Dichiarare una raccolta esterna e passarla qui ha solo un effetto: ti consente di fare riferimento alla raccolta prima che ti sia restituita. Questo è tutto. E questo è qualcosa di cui hai assolutamente bisogno se hai intenzione di fermarti in base allo stato della raccolta.

(Nota: takeWhile() è Java 9. Fammi sapere se è necessario attenersi a 8)

Set<Animal> setOfAnimals = new LinkedHashSet<>();

infiniteStreamOfAnimals
    .takeWhile( x->setOfAnimals.size() < AnimalType.values().length )
    .collect(
        Collectors.toCollection(
            ()->setOfAnimals
        )
    )
;

System.out.println( setOfAnimals );

Visualizza:

[first HUMAN animal, first DOG animal, first CAT animal]

Invece stai cercando di evitare di avere accesso codificando il generatore di raccolta in questo modo:

.collect(
    Collectors.toCollection(
        LinkedHashSet::new
    )
)

Fai in questo modo e quando provi a interrompere lo stream infinito non avrai accesso alla struttura (raccolta) che devi testare. Anche se si potesse fare ciò, il nuovo hashset è anche "fuori dallo stato del flusso". Al flusso non interessa davvero a cosa sta parlando, a patto che non si scherzi con esso. Tutto ciò che hai fatto è nascondere il riferimento alla raccolta da te stesso. Per testare hai bisogno di quel riferimento.

Forse hai qualche problema di concorrenza qui. Permettimi di assicurarti che sono infondati. Poiché questo flusso non può essere parallelizzato comunque, a causa del requisito sequenziale di memorizzazione della prima occorrenza di ciascun tipo.

Forse vuoi solo essere funzionale. Vuoi creare qualcosa di puro che sia deterministico e referenzialmente trasparente. Bene che possiamo fare. Basta inserirlo in una funzione:

class InfiniteStream {
    public static <T> Set<T> firstFrom( Stream<T> infiniteStream, int size ) {
        Set<T> set = new LinkedHashSet<>();

        infiniteStream
            .takeWhile( x->set.size() < size )
            .collect(
                Collectors.toCollection(
                    ()->set
                )
            )
        ;

        return set;
    }
}

quando lo chiami in questo modo:

Set<Animal> setOfAnimals = InfiniteStream
    .firstFrom( 
        infiniteStreamOfAnimals, 
        AnimalType.values().length 
    )
;

hey presto la chiamata è deterministica e referenzialmente trasparente. Nessuno stato "esterno" di cui preoccuparsi. Cosa si può volere di più?

Per l'analisi di questo codice e un approccio alternativo, consulta questa mia domanda sulla revisione del codice

    
risposta data 21.04.2018 - 13:42
fonte

Leggi altre domande sui tag