0

I am writing a Java string formatter and I think I am making it more complicated than it is maybe? I have a file that gives a list a stores:

"100|Pete's Pizza Palace|George Smith|200"
"400|Pete's Pizza Palace|George Smith|30"
"320|Pete's Pizza Palace|George Smith|-13"
"310|Pete's Pizza Palace|John Smith|2"

The output should look: "Pete's Pizza Palace|George Smith|217,Pete's Pizza Palaca|John Smith|2"

So, there's the storenumber removed in the first section then the added profits for the same stores. I do not seem to put in the map to get the sum for the same key string.

static String fileRecords(String[] records) {
    int len = records.length;
    Map<String, Integer> map = new HashMap<String, Integer>();
    Map<String, Integer> profitTotals = new HashMap<String, Integer>();
    String[] record = new String[len];
    int index = 0;
    int[] sums = new int[len];
    StringBuilder sb = new StringBuilder();
    StringBuilder tempStringBuilder = new StringBuilder();
    int totalSum = 0;
    for(int i = 0;i<len;i++) {
        record = records[i].split("\\|");
        String recEntryNameString = tempStringBuilder.append(record[1]).append("|").append(record[2]).append("|").toString();
        map.put(recEntryNameString, Integer.parseInt(record[3]));   
        profitTotals.put(recEntryNameString, Integer.parseInt(record[3])); 
        Iterator iter = map.entrySet().iterator();
        for (Map.Entry<String, Integer> entries : map.entrySet()) {
            for(Map.Entry<String, Integer> sumNum : profitTotals.entrySet()) {
                if(!entries.getKey().equals(sumNum.getKey())) {
                    totalSum = entries.getKey() + sumNum.getKey();
                    map.replace(recEntryNameString, entries.getKey(), totalSum);    
                    profitTotals.remove(recEntryNameString);
                }
            }
        }
    }
    Iterator<String, Integer> iter = map.entrySet().iterator();
    while(iter.hasNext()) {
        Map.Entry<String, Integer> entry = iter.next();
        if(iter.hasNext()==true)
            sb.append(entry.getKey()).append(entry.getValue()).append(",");
        else
            sb.append(entry.getKey()).append(entry.getValue());
    }
    return sb.toString();
}

I get outputs that are really close but the again looking for the correct format

Pete's Pizza Palace|George Smith|Pete's Pizza Palace|George Smith|Pete's Pizza Palace|George 
Smith|87,Pete's Pizza Palace|George Smith|100,Pete's Pizza Palace|George Smith|Pete's Pizza 
 Palace|George Smith|123,
2
  • You should consider adding spacing and comments to your code. It can do wonders for readability. Commented Feb 27, 2020 at 5:23
  • There could be much easier approach, will try soon. Commented Feb 27, 2020 at 6:12

4 Answers 4

3

First represent that data in a class:

public class Transaction {
    private int id;
    private String place;
    private String customer;
    private double amount;

    Transaction(String tokenizedString) {
        String[] tokens = tokenizedString.split("\\|");
        id = Integer.parseInt(tokens[0]);
        place = tokens[1];
        customer = tokens[2];
        amount = Double.parseDouble(tokens[3]);
    }

    //getters/setters
}

then you can use it like:

public static void main(String[] args) {
    List<String> list = List.of(
            "100|Pete's Pizza Palace|George Smith|200",
            "400|Pete's Pizza Palace|George Smith|30",
            "320|Pete's Pizza Palace|George Smith|-13",
            "310|Pete's Pizza Palace|John Smith|2"
    );
    Map<String, Double> map = list.stream()
            .map(Transaction::new)
            .collect(Collectors.groupingBy(
                    o -> String.join("|", o.getPlace(), o.getCustomer()),
                    Collectors.summingDouble(Transaction::getAmount)));
    List<String> result = map.entrySet().stream()
            .map(e -> e.getKey() + "|" + e.getValue())
            .collect(Collectors.toList());
    System.out.println(result);
}

Output:

[Pete's Pizza Palace|George Smith|217.0, Pete's Pizza Palace|John Smith|2.0]

Explanation:

.map(Transaction::new)
converts each item in the string list to a Transaction, thanks to the parameterized constructor.

Then I grouped transactions by the combined string of place and customer, separated by your separator |.
This gave me a map having entries like "Pete's Pizza Palace|George Smith" -> 217.0

Finally I mapped each entry to a string, which is a result of combining the key and value, separated by |, and collected them to a list.

I tried doing this last step of collecting to a list within a downstream collector of groupingBy but couldn't find a way.

Sign up to request clarification or add additional context in comments.

2 Comments

I can't get this to find the Collectors using 'import java.util.*;import java.lang.*:'?
@April_Nara Your IDE should suggest imports, I would recommend to learn some IDE shortcuts. You could also easily google it - import java.util.stream.Collectors;.
0

I really like Kartik's answer for the use of newer features in the Java language.

Slightly easier to understand might be:

public class PizzaStore {

static String[] records = new String[]{"100|Pete's Pizza Palace|George Smith|200",
    "400|Pete's Pizza Palace|George Smith|30",
    "320|Pete's Pizza Palace|George Smith|-13",
    "310|Pete's Pizza Palace|John Smith|2"};

public static void main(String[] args) {
    fileRecords(records);
}

static void fileRecords(String[] records) {

    int len = records.length;

    HashMap<String, Integer> runningTotals = new HashMap<>();

    for (int i = 0; i < len; i++) {
        String rec = records[i];
        String[] fields = rec.split("\\|");
        String storeName = fields[1] + "|" + fields[2];
        Integer value = new Integer(fields[3]);
        if (!runningTotals.containsKey(storeName)) {
            runningTotals.put(storeName, 0);            
        }
        runningTotals.put(storeName, runningTotals.get(storeName) + value);
    }

    for (String s : runningTotals.keySet()) {
        System.out.println(s + "|" + runningTotals.get(s));
    }

}
}

Here we're using the insight (credit to Kartik) that the two middle strings can be joined to form a key.

Output looks like this:

Pete's Pizza Palace|George Smith|217

Pete's Pizza Palace|John Smith|2

e.g. separated on different lines, but this is easily adjustable (change the println to a print and insert "|" between them.

Comments

0

I would create an intermediate object as a data holder to contain the records

class PizzaStore {

    int storeID;
    String franchiseName;
    String managerName;
    // if this is supposed to be a currency value use BigDecimal instead
    int profitability;

 }

Most IDEs can add setter and getter methods if you want to make them protected or private.

Then you need a constructor to turn your record into a pizza store

     public PizzaStore(String record) {
         if (record == null) {
             // initialize to default values and return
             // don't want to throw exceptions in constructors
             // ...
             return;
         }

         String[] fields = record.split("\\|");

         if (fields.length < 4) {
             // initialize to default values and return
             // don't want to throw exceptions in constructors
             // ...
             return;
         }

         this.franchiseName = fields[1];
         this.managerName = fields[2];


         try {
             Integer id = new Integer(fields[0]);
             Integer profit = new Integer(fields[3]);
         } catch (Exception e) {
             System.err.println("failed to parse fields: " + fields[0] + " - " + fields[3]);    
             // initialise them to something
         }
     }

And then you need a data structure to store them. You can use an array, but if you're going to pull them out of the data structure by storeID (for instance) and storeID is unique, then you can make a HashMap and put the store number as the key and the pizza store as the value.

1 Comment

Now the problem is when you are aggregating them you're not using the ids, you're using the name of the store (what I called franchiseName). So what you do is you create a second HashMap, this one with Strings as the key and the value is a running total. You then iterate across your list of stores, and each time you go to the second HashMap and pull out its total for that store-name, and add the profitability of the store you're currently looking at to that running total. What happens the first time you iterate over a particular store? Well it won't be in there yet, so check for nulls.
0

As mentioned in other answers it's not a bad idea to create a separate class to hold the information about sales. However, it's possible to achieve the result without such class.

  static String fileRecords(String[] records) {
    return Arrays.stream(records)
        .map(record -> record.split("\\|"))
        .collect(groupingBy(fields -> fields[1] + "|" + fields[2], summingInt(fields -> Integer.parseInt(fields[3]))))
        .entrySet().stream()
        .map(entry -> entry.getKey() + "|" + entry.getValue())
        .collect(joining(","));
  }

This stream is quite amenable to being parallelized as well. To process large arrays of records in parallel you can just add .parallel() after Arrays.stream(records) and entrySet().stream()

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.