Java 8 API流。需要解决重复键问题。

huangapple go评论73阅读模式
英文:

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 &lt;String, ?&gt; parse() throws IOException {
		
		try (Stream&lt;String&gt;lines = Files.lines(path)){
			

		return lines.collect(Collectors.toMap(
				string -&gt; string.substring(0,3),
				string -&gt; new DateAndTimeInfo(LocalTime.from(DateTimeFormatter.ofPattern(&quot;HH:mm:ss.SSS&quot;).parse((string.substring(3).split(&quot;_&quot;)[1]))),
						LocalDate.parse(string.substring(3).split(&quot;_&quot;)[0], DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&quot;))),
				(string1, string2)-&gt; ??? )); //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&lt;LocalTime&gt; localTime;
	private List&lt;LocalDate&gt; localDate;
	
	public DateAndTimeInfo(LocalTime localTime, LocalDate localDate) {
		this.localTime = Arrays.asList(localTime);
		this.localDate = Arrays.asList(localDate);
	}
	public List&lt;LocalTime&gt; getLocalTime() {
		return this.localTime;
	}
	public List&lt;LocalDate&gt; 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&lt;LocalTime&gt; localTime;
    private List&lt;LocalDate&gt; localDate;

    public DateAndTimeInfo() {
        localTime = new ArrayList&lt;&gt;();
        localDate = new ArrayList&lt;&gt;();
    }
    public List&lt;LocalTime&gt; getLocalTime() {
        return this.localTime;
    }
    public List&lt;LocalDate&gt; 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&lt;String, DateAndTimeInfo&gt; parse() throws IOException {
    DateTimeFormatter f = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd_HH:mm:ss.SSS&quot;);
    try(Stream&lt;String&gt;lines = Files.lines(path)){
        return lines.collect(Collectors.groupingBy(
            string -&gt; string.substring(0,3),
            Collector.of(DateAndTimeInfo::new, (info,str) -&gt; {
                LocalDateTime ldt = LocalDateTime.parse(str.substring(3), f);
                info.addAnotherLapTime(ldt.toLocalTime(), ldt.toLocalDate());
            }, (info1,info2) -&gt; {
                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&lt;LocalDateTime&gt; localDateTimes;

    public DateAndTimeInfo(List&lt;LocalDateTime&gt; list) {
        localDateTimes = list;
    }
    public List&lt;LocalDateTime&gt; getLocalDateTimes() {
        return localDateTimes;
    }
    // in case this is really needed
    public List&lt;LocalTime&gt; getLocalTime() {
        return localDateTimes.stream()
            .map(LocalDateTime::toLocalTime)
            .collect(Collectors.toList());
    }
    public List&lt;LocalDate&gt; getLocalDate() {
        return localDateTimes.stream()
            .map(LocalDateTime::toLocalDate)
            .collect(Collectors.toList());
    }
}

and then

public Map&lt;String, DateAndTimeInfo&gt; parse() throws IOException {
    DateTimeFormatter f = DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd_HH:mm:ss.SSS&quot;);
    try(Stream&lt;String&gt;lines = Files.lines(path)){
        return lines.collect(Collectors.groupingBy(
            string -&gt; string.substring(0,3),
            Collectors.collectingAndThen(
                Collectors.mapping(s -&gt; LocalDateTime.parse(s.substring(3), f),
                    Collectors.toList()),
                DateAndTimeInfo::new)));
    }
}

答案2

得分: 1

你可以尝试这样做。我添加了一些Lambda表达式,使代码看起来更加清晰。基本上,它使用toMapmerge函数将新创建类中的列表复制到已存在类中。以下是我对你的类所做的修改:

  • 构造函数将值放入列表中。
  • 添加了一个复制构造函数,在另一个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 = {
&quot;LSR2019-07-12_12:07:21.554&quot;,
&quot;KMH2019-07-12_12:09:44.291&quot;,
&quot;KMH2019-07-12_12:09:44.292&quot;,
&quot;RGH2019-07-12_12:29:28.352&quot;,
&quot;RGH2019-07-12_12:33:08.603&quot;,
&quot;RGH2019-07-12_12:33:08.604&quot;};
Function&lt;String, LocalTime&gt; toLT = (str) -&gt; LocalTime
.from(DateTimeFormatter.ofPattern(&quot;HH:mm:ss.SSS&quot;)
.parse((str.substring(3).split(&quot;_&quot;)[1])));
Function&lt;String, LocalDate&gt; toLD = (str) -&gt; LocalDate.parse(
str.substring(3).split(&quot;_&quot;)[0],
DateTimeFormatter.ofPattern(&quot;yyyy-MM-dd&quot;));
Map&lt;String, DateAndTimeInfo&gt; map = lines
.collect(Collectors.toMap(
string -&gt; string.substring(0,3),
string-&gt; new DateAndTimeInfo(toLT.apply(string), toLD.apply(string)),
(dti1,dti2)-&gt; dti1.copy(dti2)))
class DateAndTimeInfo {
private List&lt;LocalTime&gt; localTime = new ArrayList&lt;&gt;();
private List&lt;LocalDate&gt; localDate = new ArrayList&lt;&gt;(); 
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() + &quot;\n    &quot; + 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&lt;String, List&lt;DateAndTimeInfo&gt;&gt; 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&lt;String, List&lt;DateAndTimeInfo&gt;&gt; map = lines
.collect(Collectors.groupingBy(str-&gt;str.substring(0,3),
Collectors.mapping(str-&gt;new DateAndTimeInfo(toLT.apply(str),
toLD.apply(str)), Collectors.toList())));
</details>

huangapple
  • 本文由 发表于 2020年10月16日 21:49:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/64390508.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定