2

Say we have a text file that contains (product name, price) pairs. Each pair occupies two lines in the text file where the first line corresponds to the product name, and the second line corresponds to the price of that product. We may assume the text file is in the right format (and has an even amount of lines)

Example:

Ice Cream
$3.99
Chocolate
$5.00
Nice Shoes
$84.95
...

Now I have a simple class representing such pairs:

public class Product {
    private final String name;
    private final int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return this.name;
    }

    public int getPrice() {
        return this.price;
    }
}

We read the file containing the pairs and now have a string array containing all the individual lines. I need to use Streams to map each two lines to one object of type Product.

How can I group two lines each and then map them to a Product? If there is a simple approach, does it still work with parallel streams?

navix1337
  • 47
  • 4
  • 1
    Have you tried to start doing something with stream? – Frighi Jul 14 '21 at 14:05
  • 3
    Does this answer your question? [Collect successive pairs from a stream](https://stackoverflow.com/questions/20470010/collect-successive-pairs-from-a-stream) – Tim Hunter Jul 14 '21 at 14:10

2 Answers2

3

You can make your own Collector which temporarily stores the previous element/string. When the current element starts with a $, the name of the product is stored in prev. Now you can convert the price to a double and create the object.

private class ProductCollector {

    private final List<Product> list = new ArrayList<>();

    private String prev;

    public void accept(String str) {
        if (prev != null && str.startsWith("$")) {
            double price = Double.parseDouble(str.substring(1));
            list.add(new Product(prev, price));
        }
        prev = str;
    }

    public List<Product> finish() {
        return list;
    }

    public static Collector<String, ?, List<Product>> collector() {
        return Collector.of(ProductCollector::new, ProductCollector::accept, (a, b) -> a, ProductCollector::finish);
    }
}

Since you need to rely on the sequence (line with price follows line with name), the stream cannot be processed in parallel. Here is how you can use your custom collector:

String[] lines = new String[]{
        "Ice Cream", "$3.99",
        "Chocolate", "$5.00",
        "Nice Shoes", "$84.95"
};

List<Product> products = Stream.of(lines)
        .sequential()
        .collect(ProductCollector.collector());

Note that your prices are not integers which is why I used a double to represent them properly.

Matt
  • 12,848
  • 2
  • 31
  • 53
  • Is there a way to parallelize this, say, using a custom Spliterator? Why couldn't we just represent two lines as *one entity* and parallelize over these entities? Also would something like this work: `IntStream.range(0, length - 1).filter(i -> i % 2 == 0).mapToObj(i -> new Product(lines.get(i), lines.get(i + 1))).collect(Collectors.toList());` – navix1337 Jul 14 '21 at 15:18
  • Are there some book or tutorial to learn that tricks? – Marek Borecki Oct 27 '21 at 19:33
-1

if you have the items in an array you can use vanilla java with an intStream and filter on even values and then in the next map you can use index and index+1. Maybe have look here

jorne
  • 894
  • 2
  • 11
  • 23