【问题标题】:Manipulating CSV file with a non standardized content使用非标准化内容处理 CSV 文件
【发布时间】:2015-06-14 14:07:07
【问题描述】:

我有一个非标准化内容的 CSV 文件,它是这样的:

John, 001
01/01/2015, hamburger
02/01/2015, pizza
03/01/2015, ice cream
Mary, 002
01/01/2015, hamburger
02/01/2015, pizza
John, 003
04/01/2015, chocolate

现在,我要做的是在 java 中编写一个逻辑来分隔它们。我希望“John,001”作为标题,并将所有行放在 John 下,在 Mary 之前是 John 的。

这可能吗?还是我应该手动完成?

编辑:
对于输入,即使它没有标准化,一个明显的模式是没有名称的行将始终以日期开头。
我的输出目标是一个 java 对象,我最终可以将它存储在数据库中,格式如下。

Name, hamburger, pizza, ice cream, chocolate
John, 01/01/2015, 02/01/2015, 03/01/2015, NA
Mary, 01/01/2015, 02/01/2015, NA, NA
John, NA, NA, NA, 04/01/2015

【问题讨论】:

  • 那么以名称开头的每一行都标志着一个新的 CSV“文件”的开始?
  • 确实有可能,但是如果您说输入文件的实际规格是什么而不是让我们猜测会更容易。示例很有用,但不是完整的规范。
  • @Tichodroma 是的,你是对的。
  • 除了向我们展示输入模式之外,您还需要根据输出定义成功的样子。您是否正在尝试构建新的输出文件?还是java对象?为什么数据首先是非结构化的?
  • @SergeBallesta 实际上这就是文件的样子,哦,我忘了提到没有名称的行将以日期开头。我将编辑我的问题。

标签: java csv


【解决方案1】:

您可以将文件读入列表

List<String> lines = Files.readAllLines(Paths.get(path), StandardCharsets.UTF_8);

然后遍历列表并将它们拆分为想要的分隔符(",")

现在您可以使用 if-else 或 switch 块来检查特定条目。

List<DataObject> objects = new ArrayList<>();
DataObject dataObject = null;
for(String s : lines) {
    String [] splitLine = s.split(",");
    if(splitLine[0].matches("(\d{2}\/){2}\d{4}")) {
        // We found a data
        if(dataObject != null && splitLine.length == 2) {
            String date = splitLine[0];
            String dish = splitLine[1];
            dataObject.add(date, dish);
        } else {
            // Handle error
        }
    } else if(splitLine.length == 2) {
        // We can create a new data object
        if(dataObject != null) {
            objects.add(dataObject);
        }
        String name = splitLine[0];
        String id = splitLine[1];
        dataObject = new DataObject(name, id);
    } else {
        // Handle error
    }
}

现在您可以将它们分类到您的特定类别中。

编辑:更改了循环并添加了一个正则表达式(这可能不是最佳的)来匹配日期字符串并使用它们来决定是否将它们添加到最后一个数据对象。

DataObject 类可以包含保存日期/菜肴的数据结构。解析 CSV 后,您可以遍历对象 List 并做任何您想做的事情。我希望这个答案有帮助:)

【讨论】:

  • 这不能回答 OP 问题 - 您必须修改它以查看 splitLine[0] 并检测它是否看起来像一个日期。如果是日期,则添加到前一个项目的数据中,否则是新项目的开始。
  • 嗯,我以为这和我的回答有牵连,所以做的东西cmets,他可以在这里检查它是否是一个日期,然后将它添加到上一个项目的数据中。
  • 您对代码中的数据项进行了一些硬编码。以“约翰”为例。我认为这不是我们所需要的。
  • 现在应该更通用了:)
  • @TimoH 谢谢!只是一些问题,行 }else if (splitLine.length == 2){ 用于什么?此外,我尝试在 csv 中使用其他条目运行代码,但不知何故,如果有 3 个名称,则对象中只会放入 2 个名称。好像最后一个数据对象不会被添加到对象列表中。
【解决方案2】:

如果我理解正确的话,规格是:

  • 输入为文本,每行一条记录(字段以逗号分隔)
  • 2种记录:
    • 标题由名称和数字组成(数字被忽略)
    • 由日期和用餐组成的实际记录
  • 输出应包含:
    • 一个包含常量 Name 的标题,以及按出现顺序排列的饭菜
    • 记录每个名称,包含与膳食对应的名称和日期 - 缺席字段将具有 NA 常量字符串
  • 我们假设我们永远不会为不同的输入记录获得相同日期的名称。

算法是伪代码:

Data structures :
 one list of struct< string name, hash< int meal index, date> > for the names : base
 one list of strings for the meals : meals

Code :

name = null
iname = -1
Loop per input lines {
  if first field is date {
    if name == null {
      throw Exception("incorrect structure");
    }
    meal = second field
    look for index of meal in meals
    if not found {
      index = len(meals);
      add meal at end of list meals
    }
    base[iname].hash[index] = date
  }
  else {
    name = first field
    iname += 1
    add a new struc { name, empty hash } at end of list base
  }
}
close input file
open output file
// headers
print "names"
for meal in meals {
  print ",", meal
}
print newline
for (i=0; i<=iname; i++) {
  print base[i].name
  for meal in meals {
    look for meal in base[i].hash.keys
    if found {
      print ",", base[i].hash[meal]
    }
    else {
      print ",NA"
    }
  }
  print newline
}
close output file

只需用正确的 Java 编写代码,如果您有任何问题,请返回这里。

【讨论】:

    【解决方案3】:

    使用uniVocity-parsers 为您处理此问题。它带有一个主从行处理器。

    // 1st, Create a RowProcessor to process all "detail" elements (dates/ingredients)
    ObjectRowListProcessor detailProcessor = new ObjectRowListProcessor();
    
    // 2nd, Create MasterDetailProcessor to identify whether or not a row is the master row (first value of the row is a name, second is an integer).
    MasterDetailListProcessor masterRowProcessor = new MasterDetailListProcessor(RowPlacement.TOP, detailProcessor) {
        @Override
        protected boolean isMasterRecord(String[] row, ParsingContext context) {
             try{
                 //tries to convert the second value of the row to an Integer.
                 Integer.parseInt(String.valueOf(row[1]));
                 return true;
             } catch(NumberFormatException ex){
                 return false;
             }
        }
    };
    
    CsvParserSettings parserSettings = new CsvParserSettings();
    
    // Set the RowProcessor to the masterRowProcessor.
    parserSettings.setRowProcessor(masterRowProcessor);
    
    CsvParser parser = new CsvParser(parserSettings);
    parser.parse(new FileReader(yourFile));
    
    // Here we get the MasterDetailRecord elements.
    List<MasterDetailRecord> rows = masterRowProcessor.getRecords();
    
    // Each master record has one master row and multiple detail rows.
    MasterDetailRecord masterRecord = rows.get(0);
    Object[] masterRow = masterRecord.getMasterRow();
    List<Object[]> detailRows = masterRecord.getDetailRows();
    

    披露:我是这个库的作者。它是开源免费的(Apache V2.0 许可)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-11-27
      • 1970-01-01
      • 1970-01-01
      • 2021-09-28
      • 2014-11-09
      • 2012-08-07
      • 1970-01-01
      • 2019-01-21
      相关资源
      最近更新 更多