【问题标题】:Parsing a String using HashMap for formatting使用 HashMap 解析字符串进行格式化
【发布时间】:2020-06-11 01:43:07
【问题描述】:

我正在编写一个 Java 字符串格式化程序,我认为我让它变得比它可能更复杂? 我有一个文件列出了一个商店:

"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"

输出应该是:“Pete's Pizza Palace|George Smith|217,Pete's Pizza Palaca|John Smith|2”

因此,在第一部分中删除了商店编号,然后是相同商店的附加利润。我似乎没有放入地图以获取相同密钥字符串的总和。

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();
}

我得到的输出非常接近,但又在寻找正确的格式

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,

【问题讨论】:

  • 您应该考虑在代码中添加间距和 cmets。它可以为可读性创造奇迹。
  • 可能有更简单的方法,很快就会尝试。

标签: java string parsing hashmap format


【解决方案1】:

首先在一个类中表示该数据:

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
}

那么你可以像这样使用它:

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);
}

输出

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

解释

.map(Transaction::new)
借助参数化构造函数,将字符串列表中的每个项目转换为 Transaction

然后我按placecustomer 的组合字符串对事务进行分组,并用分隔符| 分隔。
这给了我一张地图,其中包含 "Pete's Pizza Palace|George Smith" -&gt; 217.0

之类的条目

最后我将每个条目映射到一个字符串,这是组合键和值的结果,用|分隔,并将它们收集到一个列表中。

我尝试在groupingBy 的下游收集器中执行收集到列表的最后一步,但找不到方法。

【讨论】:

  • 我无法使用 'import java.util.*;import java.lang.*:' 找到收集器?
  • @April_Nara 您的 IDE 应该建议导入,我建议您学习一些 IDE 快捷方式。你也可以很容易地用谷歌搜索它 - import java.util.stream.Collectors;.
【解决方案2】:

我真的很喜欢 Kartik 关于在 Java 语言中使用新功能的回答。

比较容易理解的可能是:

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));
    }

}
}

在这里,我们使用了两个中间字符串可以连接起来形成一个键的洞察力(归功于 Kartik)。

输出如下所示:

皮特比萨宫|乔治·史密斯|217

皮特比萨宫|约翰史密斯|2

例如分隔在不同的行上,但这很容易调整(将 println 更改为 print 并在它们之间插入“|”。

【讨论】:

    【解决方案3】:

    我会创建一个中间对象作为数据持有者来包含记录

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

    大多数 IDE 都可以添加 setter 和 getter 方法,如果您想让它们受到保护或私有化。

    那么你需要一个构造函数来把你的记录变成比萨店

         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
             }
         }
    

    然后你需要一个数据结构来存储它们。您可以使用数组,但如果您要通过 storeID(例如)将它们从数据结构中提取出来并且 storeID 是唯一的,那么您可以制作一个 HashMap 并将商店编号作为键,将披萨店作为价值。

    【讨论】:

    • 现在的问题是,当您聚合它们时,您并没有使用 id,而是使用了商店的名称(我称之为特许经营名称)。所以你要做的是创建第二个 HashMap,这个以字符串为键,值是运行总计。然后,您遍历您的商店列表,每次您转到第二个 HashMap 并提取该商店名称的总数,并将您当前正在查看的商店的盈利能力添加到该运行总数中。第一次迭代特定商店时会发生什么?好吧,它还没有,所以检查空值。
    【解决方案4】:

    正如其他答案中提到的,创建一个单独的类来保存有关销售的信息并不是一个坏主意。但是,没有此类也可以实现结果。

      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(","));
      }
    

    这个流也很适合并行化。要并行处理大量记录,您只需在 Arrays.stream(records)entrySet().stream() 之后添加 .parallel()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-03
      • 1970-01-01
      相关资源
      最近更新 更多