英文:
Java 8 api streams. Need to resolve duplicate keys issue
问题
我有一个看起来像这样的日志文件:
LSR2019-07-12_12:07:21.554
KMH2019-07-12_12:09:44.291
RGH2019-07-12_12:29:28.352
RGH2019-07-12_12:33:08.603
我有一个解析器,用于解析数据到缩写/日期/时间:
public Map<String, ?> parse() throws IOException {
try (Stream<String> lines = Files.lines(path)){
return lines.collect(Collectors.toMap(
string -> string.substring(0,3),
string -> new DateAndTimeInfo(LocalTime.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS").parse((string.substring(3).split("_")[1]))),
LocalDate.parse(string.substring(3).split("_")[0], DateTimeFormatter.ofPattern("yyyy-MM-dd"))),
(string1, string2) -> ??? )); //在这里遇到问题
解析后,它会创建一个包含缩写为键和 DateAndTimeInfo 类实例的映射。该类如下所示:
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
一切都很顺利,直到日志文件中出现重复的缩写。一旦出现重复的键,我希望数据存储在解析第一个重复项时创建的 DateAndTimeInfo 对象中。为此,我有 addAnotherLapTime()
方法。问题是我无法弄清楚如何在我的流中编写它。
英文:
I have a log file which looks like this:
LSR2019-07-12_12:07:21.554
KMH2019-07-12_12:09:44.291
RGH2019-07-12_12:29:28.352
RGH2019-07-12_12:33:08.603
I have a parser which parses data to abbreviation/date/time:
public Map <String, ?> parse() throws IOException {
try (Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.toMap(
string -> string.substring(0,3),
string -> new DateAndTimeInfo(LocalTime.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS").parse((string.substring(3).split("_")[1]))),
LocalDate.parse(string.substring(3).split("_")[0], DateTimeFormatter.ofPattern("yyyy-MM-dd"))),
(string1, string2)-> ??? )); //Struggle here
After parsing it creates a map that contains abbreviations as keys and instances of DateAndTimeInfo class. The class looks like this:
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
Everything works fine until the log file has a duplicate abbreviation. As soon as a duplicate key appears I want the data to be stored inside the DateAndTimeInfo object, which was created when the first duplicate was parsed. To do so I have the addAnotherLapTime()
method. The problem is I can't figure out how to write it in my stream.
答案1
得分: 4
由于数值的组合最终将形成 List
对象,因此这是 groupingBy
收集器的任务。
但首先,您必须修复 DateAndTimeInfo
类。它的唯一构造函数如下:
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
创建了固定大小的列表,因此方法
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
将会引发异常。
当您使用以下代码替换:
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo() {
localTime = new ArrayList<>();
localDate = new ArrayList<>();
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
您可以采用以下方式收集地图:
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String> lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collector.of(DateAndTimeInfo::new, (info,str) -> {
LocalDateTime ldt = LocalDateTime.parse(str.substring(3), f);
info.addAnotherLapTime(ldt.toLocalTime(), ldt.toLocalDate());
}, (info1,info2) -> {
info1.getLocalDate().addAll(info2.getLocalDate());
info1.getLocalTime().addAll(info2.getLocalTime());
return info1;
})));
}
}
groupingBy
允许为组指定一个收集器,此解决方案为 DateAndTimeInfo
对象创建了一个新的特定收集器。
您可以考虑是否真的希望将日期和时间保留在不同的列表中。
另一种选择是:
public class DateAndTimeInfo {
private List<LocalDateTime> localDateTimes;
public DateAndTimeInfo(List<LocalDateTime> list) {
localDateTimes = list;
}
public List<LocalDateTime> getLocalDateTimes() {
return localDateTimes;
}
// 如果真的需要的话
public List<LocalTime> getLocalTime() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalTime)
.collect(Collectors.toList());
}
public List<LocalDate> getLocalDate() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalDate)
.collect(Collectors.toList());
}
}
然后:
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String> lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collectors.collectingAndThen(
Collectors.mapping(s -> LocalDateTime.parse(s.substring(3), f),
Collectors.toList()),
DateAndTimeInfo::new)));
}
}
英文:
Since the combination of values will end up in List
objects, this is a task for the groupingBy
collector.
But first, you have to fix the DateAndTimeInfo
class. It’s only constructor
public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
this.localTime = Arrays.asList(localTime);
this.localDate = Arrays.asList(localDate);
}
creates fixed size lists, so the method
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
will fail with exceptions.
When you use
public class DateAndTimeInfo {
private List<LocalTime> localTime;
private List<LocalDate> localDate;
public DateAndTimeInfo() {
localTime = new ArrayList<>();
localDate = new ArrayList<>();
}
public List<LocalTime> getLocalTime() {
return this.localTime;
}
public List<LocalDate> getLocalDate() {
return this.localDate;
}
public void addAnotherLapTime(LocalTime localtime, LocalDate localDate) {
this.localTime.add(localtime);
this.localDate.add(localDate);
}
}
instead, you can collect the map like
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collector.of(DateAndTimeInfo::new, (info,str) -> {
LocalDateTime ldt = LocalDateTime.parse(str.substring(3), f);
info.addAnotherLapTime(ldt.toLocalTime(), ldt.toLocalDate());
}, (info1,info2) -> {
info1.getLocalDate().addAll(info2.getLocalDate());
info1.getLocalTime().addAll(info2.getLocalTime());
return info1;
})));
}
}
groupingBy
allows to specify a collector for the groups and this solution creates a new ad-hoc Collector
for the DateAndTimeInfo
objects.
You may consider whether you really want to keep the dates and times in different lists.
The alternative would be:
public class DateAndTimeInfo {
private List<LocalDateTime> localDateTimes;
public DateAndTimeInfo(List<LocalDateTime> list) {
localDateTimes = list;
}
public List<LocalDateTime> getLocalDateTimes() {
return localDateTimes;
}
// in case this is really needed
public List<LocalTime> getLocalTime() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalTime)
.collect(Collectors.toList());
}
public List<LocalDate> getLocalDate() {
return localDateTimes.stream()
.map(LocalDateTime::toLocalDate)
.collect(Collectors.toList());
}
}
and then
public Map<String, DateAndTimeInfo> parse() throws IOException {
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH:mm:ss.SSS");
try(Stream<String>lines = Files.lines(path)){
return lines.collect(Collectors.groupingBy(
string -> string.substring(0,3),
Collectors.collectingAndThen(
Collectors.mapping(s -> LocalDateTime.parse(s.substring(3), f),
Collectors.toList()),
DateAndTimeInfo::new)));
}
}
答案2
得分: 1
你可以尝试这样做。我添加了一些Lambda表达式,使代码看起来更加清晰。基本上,它使用toMap
的merge
函数将新创建类中的列表复制到已存在类中。以下是我对你的类所做的修改:
- 构造函数将值放入列表中。
- 添加了一个复制构造函数,在另一个
DateAndTimeInfo
实例中将一个列表复制到另一个列表。 - 添加了一个
toString
方法。
String[] lines = {
"LSR2019-07-12_12:07:21.554",
"KMH2019-07-12_12:09:44.291",
"KMH2019-07-12_12:09:44.292",
"RGH2019-07-12_12:29:28.352",
"RGH2019-07-12_12:33:08.603",
"RGH2019-07-12_12:33:08.604"
};
Function<String, LocalTime> toLT = (str) -> LocalTime
.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
.parse((str.substring(3).split("_")[1])));
Function<String, LocalDate> toLD = (str) -> LocalDate.parse(
str.substring(3).split("_")[0],
DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Map<String, DateAndTimeInfo> map = lines
.collect(Collectors.toMap(
string -> string.substring(0, 3),
string -> new DateAndTimeInfo(toLT.apply(string), toLD.apply(string)),
(dti1, dti2) -> dti1.copy(dti2)));
class DateAndTimeInfo {
private List<LocalTime> localTime = new ArrayList<>();
private List<LocalDate> localDate = new ArrayList<>();
public DateAndTimeInfo(LocalTime lt, LocalDate ld) {
localTime.add(lt);
localDate.add(ld);
}
public DateAndTimeInfo copy(DateAndTimeInfo dti) {
this.localTime.addAll(dti.localTime);
this.localDate.addAll(dti.localDate);
return this;
}
public String toString() {
return localTime.toString() + "\n " + localDate.toString();
}
}
对于给定的测试数据,它将打印出:
RGH=[12:29:28.352, 12:33:08.603, 12:33:08.604]
[2019-07-12, 2019-07-12, 2019-07-12]
KMH=[12:09:44.291, 12:09:44.292]
[2019-07-12, 2019-07-12]
LSR=[12:07:21.554]
[2019-07-12]
注意。你考虑过创建一个类似下面的映射吗?
Map<String, List<DateAndTimeInfo>>
并且只在每个类中存储日期和时间作为字段?你可以通过getter方法获取它们。这是非常简单的实现。因此,键的值将是DateAndTimeInfo
对象的列表。
Map<String, List<DateAndTimeInfo>> map = lines
.collect(Collectors.groupingBy(
str -> str.substring(0, 3),
Collectors.mapping(
str -> new DateAndTimeInfo(toLT.apply(str), toLD.apply(str)),
Collectors.toList()
)
));
英文:
You can try this. I added lambdas to make it a little cleaner to view. Basically it uses the merge function of toMap to copy list from the newly created class to the already existing class. Here are the mods I made to your class.
- the constructor puts the values in the lists.
- added a copy constructor to copy one list to the other in another instance of
DateAndTimeInfo
- Added a toString method.
String[] lines = {
"LSR2019-07-12_12:07:21.554",
"KMH2019-07-12_12:09:44.291",
"KMH2019-07-12_12:09:44.292",
"RGH2019-07-12_12:29:28.352",
"RGH2019-07-12_12:33:08.603",
"RGH2019-07-12_12:33:08.604"};
Function<String, LocalTime> toLT = (str) -> LocalTime
.from(DateTimeFormatter.ofPattern("HH:mm:ss.SSS")
.parse((str.substring(3).split("_")[1])));
Function<String, LocalDate> toLD = (str) -> LocalDate.parse(
str.substring(3).split("_")[0],
DateTimeFormatter.ofPattern("yyyy-MM-dd"));
Map<String, DateAndTimeInfo> map = lines
.collect(Collectors.toMap(
string -> string.substring(0,3),
string-> new DateAndTimeInfo(toLT.apply(string), toLD.apply(string)),
(dti1,dti2)-> dti1.copy(dti2)))
class DateAndTimeInfo {
private List<LocalTime> localTime = new ArrayList<>();
private List<LocalDate> localDate = new ArrayList<>();
public DateAndTimeInfo(LocalTime lt, LocalDate ld) {
localTime.add(lt);
localDate.add(ld);
}
public DateAndTimeInfo copy(DateAndTimeInfo dti) {
this.localTime.addAll(dti.localTime);
this.localDate.addAll(dti.localDate);
return this;
}
public String toString() {
return localTime.toString() + "\n " + localDate.toString();
}
}
For the given test data, it prints.
RGH=[12:29:28.352, 12:33:08.603, 12:33:08.604]
[2019-07-12, 2019-07-12, 2019-07-12]
KMH=[12:09:44.291, 12:09:44.292]
[2019-07-12, 2019-07-12]
LSR=[12:07:21.554]
[2019-07-12]
Note. Did you consider of creating a map like the following:
Map<String, List<DateAndTimeInfo>>
and storing just the date and time in each class as fields? You could get them with getters. It would be trivial to implement. So the value of the key would be a list of DateAndTimeInfo
objects.
Map<String, List<DateAndTimeInfo>> map = lines
.collect(Collectors.groupingBy(str->str.substring(0,3),
Collectors.mapping(str->new DateAndTimeInfo(toLT.apply(str),
toLD.apply(str)), Collectors.toList())));
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论