Tengo una lista de objetos, digamos de clase Documento:
class Document { private final String id; private final int length; public Document(String id, int length) { this.id = id; this.length = length; } public int getLength() { return length; } }La tarea en cuestión es agruparlos en Sobres para que el número de páginas (Documento.longitud) no exceda cierto número.
class Envelope { private final List<Document> documents = new ArrayList<>(); }Entonces, por ejemplo, si tuviera la siguiente lista de documentos:
Document doc0 = new Document("doc0", 2); Document doc1 = new Document("doc1", 5); Document doc2 = new Document("doc2", 5); Document doc3 = new Document("doc3", 5);y el recuento máximo de páginas en el sobre es digamos 7 , de lo que espero 3 sobres con los siguientes documentos:
Assert.assertEquals(3, envelopeList.size()); Assert.assertEquals(2, envelopeList.get(0).getDocuments().size()); // doc0, doc1 Assert.assertEquals(1, envelopeList.get(1).getDocuments().size()); // doc2 Assert.assertEquals(1, envelopeList.get(2).getDocuments().size()); // doc3He implementado esto con el bucle for tradicional y un montón de if, pero la pregunta es: ¿es posible hacerlo de esta manera más elegante con flujos y recopiladores?
Gracias y un saludo
dalibor
Para agrupar los documentos en lotes según la longitud, necesitamos mantener el estado de las longitudes acumuladas. Streams no son la mejor opción cuando es necesario mantener el estado externo y el bucle personalizado debe ser una opción más simple y eficiente.
Si forzamos el ajuste, las secuencias para este escenario, el DocumentSpliterator cambiaría de la siguiente manera:
public static List<Couvert> splitDocuments(List<Document> docs) { IntUnaryOperator helper = new IntUnaryOperator() { private int bucketIndex = 0; private int accumulated = 0; public synchronized int applyAsInt(int length) { if (length + accumulated > MAX) { bucketIndex++; accumulated = 0; } accumulated += length; return bucketIndex; } }; return new ArrayList<>(docs.stream() .map(d -> new AbstractMap.SimpleEntry<>(helper.applyAsInt(d.getLength()), d)) .collect(Collectors.groupingBy(AbstractMap.SimpleEntry::getKey, Collector.of(Couvert::new, (c, e) -> c.getDocuments().add(e.getValue()), (c1, c2) -> {c1.getDocuments().addAll(c2.getDocuments());return c1;}))) .values()); }Explicación:
helper mantiene la longitud acumulada y proporciona un nuevo índice de cubo cuando supera el máximo. He usado la interfaz IntUnaryOperator aquí. Alternativamente, podemos usar cualquier interfaz que tome un parámetro int y devuelva un int .Document se asigna a una entrada simple de SimpleEntry y Document.SimpleEntry se agrupa primero según el bucketIndex. Otro Collector transforma el flujo de Document para un bucketIndex en particular en un Couvert . La salida de collect() es Map<Integer,Couvert>Collection de Couvert se convierte en una lista y se devuelve. Nota: Para esta implementación, eliminé el parámetro front y lo incluí como parte de la lista de docs .