Robert
Robert

Reputation: 23

Java Hash map / Array List Count distinct values

I am pretty new into programming and I have an assignment to make, but I got stuck.

I have to implement a program which will read a CSV file (1 million+ lines) and count how many clients ordered "x" distinct products on a specific day.

The CSV looks like this:

Product Name | Product ID | Client ID |  Date
Name              544           86       10/12/2017
Name              545           86       10/12/2017
Name              644           87       10/12/2017
Name              644           87       10/12/2017
Name              9857          801      10/12/2017
Name              3022          801      10/12/2017
Name              3021          801      10/12/2017

The result from my code is:

801: 2 - incorrect

86: 2 - correct

87: 2 - incorrect

Desired output is:

Client 1 (801): 3 distinct products

Client 2 (86): 2 distinct products

Client 3 (87): 1 distinct product

Additionally,

Total: 1 client ordered 2 distinct products

The maximum number of distinct products ordered is: 3

I tried to use a Hash Map and Multimap by Google Guava (my best guess here), but I couldn't wrap my head around it.

My code looks like this:

package Test;

import java.io.BufferedReader;
import java.io.FileReader;    
import java.io.IOException;

import java.util.ArrayList;   
import java.util.HashMap;    
import java.util.Map;  

import com.google.common.collect.ArrayListMultimap;  
import com.google.common.collect.HashMultimap;    

public class Test {

    public static void main(String[] args) {
        //HashMultimap<String, String> myMultimap = HashMultimap.create();
        Map<String, MutableInteger> map = new HashMap<String, MutableInteger>();
        ArrayList<String> linesList = new ArrayList<>();
        // Input of file which needs to be parsed
        String csvFile = "file.csv";
        BufferedReader csvReader;

        // Data split by 'TAB' in CSV file
        String csvSplitBy = "\t";
        try {
            // Read the CSV file into an ArrayList array for easy processing.
            String line;
            csvReader = new BufferedReader(new FileReader(csvFile));
            while ((line = csvReader.readLine()) !=null) {
                linesList.add(line);
            }
            csvReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        } 

        // Process each CSV file line which is now contained within
        // the linesList list Array
        for (int i = 0; i < linesList.size(); i++) {
            String[] data = linesList.get(i).split(csvSplitBy);
            String col2 = data[1];
            String col3 = data[2];
            String col4 = data[3];
            
            // Determine if Column 4 has the desired date
            // and count the values
            if (col4.contains("10/12/2017"))  {
                String key = col3;
                if (map.containsKey(key)) {
                      MutableInteger count = map.get(key);
                      count.set(count.get() + 1);
                } else {
                      map.put(key, new MutableInteger(1));
                }
            }
        }
        
        for (final String k : map.keySet()) {
            if (map.get(k).get() == 2) {
              System.out.println(k + ": " + map.get(k).get());
            }
        }
    }
}

Any advise or suggestion on how this can be implemented would be greatly appreciated.

Thank you in advance guys.

Upvotes: 1

Views: 1325

Answers (1)

Ward
Ward

Reputation: 2828

You could store a Setof productIds per clientId, and just take the size of that. As a Set does not allow duplicate values, this will effectively give you the distinct number of productIds.

Also, I recommend that you give your variables meaningful name instead of col2, k, map... This will make your code more readable.

Map<String, Set<String>> distinctProductsPerClient = new HashMap<String, Set<String>>();
// Process each CSV file line which is now contained within
// the linesList list Array
// Start from 1 to skip the first line
for (int i = 1; i < linesList.size(); i++) {
    String line = linesList.get(i);
    String[] data = line.split(csvSplitBy);
    String productId = data[1];
    String clientId = data[2];
    String date = data[3];

    // Determine if Column 4 has the desired date
    // and count the values
    if (date.contains("10/12/2017"))  {
        if (!distinctProductsPerClient.containsKey(clientId)) {
            distinctProductsPerClient.put(clientId, new HashSet<>());
        }
        distinctProductsPerClient.get(clientId).add(productId);

    }
}

for (final String clientId : distinctProductsPerClient.keySet()) {
    System.out.println(clientId + ": " + distinctProductsPerClient.get(clientId).size());
}

More advanced solution using Stream API (requires Java 9)

If you introduce the class OrderData(that represents a single line in the CSV) like this:

private static class OrderData {

    private final String productName;
    private final String productId;
    private final String clientId;
    private final String date;

    public OrderData(String csvLine) {

        String[] data = csvLine.split("\t");

        this.productName = data[0];
        this.productId = data[1];
        this.clientId = data[2];
        this.date = data[3];

    }

    public String getProductName() {
        return productName;
    }

    public String getProductId() {
        return productId;
    }

    public String getClientId() {
        return clientId;
    }

    public String getDate() {
        return date;
    }
}

you can replace the for loop with this:

Map<String, Set<String>> distinctProductsPerClient2 = linesList.stream()
        .skip(1)
        .map(OrderData::new)
        .collect(groupingBy(OrderData::getClientId, mapping(OrderData::getProductId, toSet())));

But I reckon this might be a little bit to complex if you're new into programming (although it might be a good exercise if you would try to understand what the above code does).

Upvotes: 1

Related Questions