1

I have a list of string arrays List<String[]> which looks like this:

List<String[]> dataLines = List.of(
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "84M", "-101.87", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-102.48", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-103.75", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-110.39", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "CZK", "12670012.4055", "60M", "-103.75", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4066", "20M", "-10.8", "0"}
);

I want to create a new list of arrays List<String[]> newDataLine by grouping arrays where the 0th, 1st, 3rd, and 5th elements are same and add up their 6th elements.

Expected Output:

["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","84M","-101.87","0"],
["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","120M","-124.08000000000001","0"],
["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4055","60M","-214.14","0"],
["2002","BRBTSS","BRSTNCNTF212","CZK","12670012.4055","60M","-103.75","0"], 
["2002","BRBTSS","BRSTNCNTF212","BRL","12670012.4066","20M","-10.8","0"]

I've tried the following:

Map<String, Map<String, Map<String, Map<String, Double>>>> map = 
    dataLines.stream()
    .collect(Collectors.groupingBy(
        s -> s[0],
        Collectors.groupingBy(s -> s[1],
            Collectors.groupingBy(s -> s[3],
                Collectors.groupingBy(s -> s[5],
                    Collectors.summingDouble(s -> Double.valueOf(s[6])))))
    ));

Which gives me the following output:

{2002={BRBTSS={BRL={84M=-101.87, 60M=-214.14, 120M=-124.08000000000001}}}}

How can group the data in a way described above?

Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46

1 Answers1

0

I want the new list List<String[]> newDataLine as if 0th,1st,3rd,5th are same then do the addition of 6th element

To group these arrays in the required way and merge their data, you can use a HashMap, non-nested one. Nested maps, like the one in your code, are cumbersome and difficult to deal with.

For that, we need an object which would serve as a Key. I'll use a Java 16 record, but it can be implemented a class as well.

public record Key(String s0, String s1, String s2, String s3, String s4, String s5, String s7) {
    public Key(String[] line) {
        this(line[0], line[1], line[2], line[3], line[4], line[5], line[7]);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Key key = (Key) o;
        return Objects.equals(s0, key.s0) && Objects.equals(s1, key.s1) && Objects.equals(s3, key.s3) && Objects.equals(s5, key.s5);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(s0, s1, s3, s5);
    }
    
    public String[] toArray(Double s6) {
        return new String[]{s0, s1, s2, s3, s4, s5, String.valueOf(s6), s7};
    }
}

If we translate it into a class here's how it would look like:

public class Key {
    private final String s0;
    private final String s1;
    private final String s2;
    private final String s3;
    private final String s4;
    private final String s5;
    private final String s7;
    
    public Key(String s0, String s1, String s2, String s3, String s4, String s5, String s7) {
        this.s0 = s0;
        this.s1 = s1;
        this.s2 = s2;
        this.s3 = s3;
        this.s4 = s4;
        this.s5 = s5;
        this.s7 = s7;
    }
    
    public Key(String[] line) {
        this(line[0], line[1], line[2], line[3], line[4], line[5], line[7]);
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Key key = (Key) o;
        return Objects.equals(s0, key.s0) && Objects.equals(s1, key.s1) && Objects.equals(s3, key.s3) && Objects.equals(s5, key.s5);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(s0, s1, s3, s5);
    }
    
    public String[] toArray(Double s6) {
        return new String[]{s0, s1, s2, s3, s4, s5, String.valueOf(s6), s7};
    }
}

Note:

  • List<String[]> - is not the most convenient type to dial with, and it's better to avoid mixing arrays and Collections.

  • These Arrays should be custom Objects, to begin with, because you're expecting them to contain a particular number of elements and each to have a certain shape. If you organize them into a class, your data would have a structure, each element would become a property with an appropriate data type, instead of being a nameless String.

  • It's highly advisable to use BigDecimal instead of Double for floating-point calculations when don't want to lose precision (e.g.). Although Collector summingDouble() is implemented to mitigate inaccuracies, double is not a good choice for calculating prices, tax rates, etc.

And here how the stream would look like:

List<String[]> dataLines = List.of(
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "84M", "-101.87", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-102.48", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-103.75", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "60M", "-110.39", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4055", "120M", "-10.8", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "CZK", "12670012.4055", "60M", "-103.75", "0"},
    new String[]{"2002", "BRBTSS", "BRSTNCNTF212", "BRL", "12670012.4066", "20M", "-10.8", "0"}
);
        
List<String[]> amountByKey = dataLines.stream()
    .collect(Collectors.groupingBy(
        Key::new,
        Collectors.summingDouble(s -> Double.parseDouble(s[6]))
    ))
    .entrySet().stream()
    .map(entry -> entry.getKey().toArray(entry.getValue()))
    .collect(Collectors.toList());

newDataLine.forEach(arr -> System.out.println(Arrays.toString(arr)));

Output:

[2002, BRBTSS, BRSTNCNTF212, BRL, 12670012.4055, 84M, -101.87, 0]
[2002, BRBTSS, BRSTNCNTF212, BRL, 12670012.4055, 60M, -214.14, 0]
[2002, BRBTSS, BRSTNCNTF212, BRL, 12670012.4055, 120M, -124.08000000000001, 0]
[2002, BRBTSS, BRSTNCNTF212, BRL, 12670012.4066, 20M, -10.8, 0]
[2002, BRBTSS, BRSTNCNTF212, CZK, 12670012.4055, 60M, -103.75, 0]
Alexander Ivanchenko
  • 25,667
  • 5
  • 22
  • 46
  • @prachiKadam Sorry, I'm not sure what you're trying to say. Can you explain this message. – Alexander Ivanchenko Nov 11 '22 at 14:33
  • Hi Alex, Could you check updated input and output. I wanted to groupby the data only for 0,1,3,5 index. Also record Key() is not supported by java 1.8 – prachi Kadam Nov 12 '22 at 10:26
  • @prachiKadam I've provided an implementation of `Key` as a plain `class` (*see the updated answer*). The code is fully compliant with Java 8 (just use class instead of the record), and the output matches the one you've expected. – Alexander Ivanchenko Nov 12 '22 at 14:12
  • Thanks Alex for the solution. What else required if I want the round of value for s6 filed. Example -1799.1599999999999 should be displyed as -1799.16 – prachi Kadam Nov 16 '22 at 06:25