英文:
How to use Java Stream Map a List<Src> to an Object Tgt?
问题
首先,这个问题来自于我的实际工作。我需要解决它,并且我做了一些权衡来实现不完美的解决方案。此外,我将基本数据结构进行了抽象和简化,以避免敏感数据的泄露。
我源对象和目标对象分别为Src
和Tgt
,定义如下:
public class Src {
int id;
boolean flag;
int count;
// 构造方法、Getter和Setter方法......
}
public class Tgt {
int id;
int true_count;
int false_count;
// 构造方法、Getter和Setter方法......
}
现在我有一个名为srcList
的Src
对象列表。我想将这个列表合并为一个名为Tgt
的对象,操作类似于统计列表中有多少个Src
对象的flag
为true或false。
在Java 8的Stream中,我尝试使用flatMap
和map
操作符来将列表转换为一个对象,代码如下:
public class TestSrc2Tgt {
public List<Tgt> listSrc2ListTgt(List<Src> srcList){
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(tgt.getTrue_count + src.getCount());
}
else {
tgt.setFalse_count(tgt.getFalse_count + src.getCount());
}
}
// 这是我的问题,我不想使用列表
// 我想要的是将List<Src>转换为一种合并的Tgt对象
List<Tgt> tgtList = new ArrayList<>();
tgtList.add(tgt);
return tgtList;
}
public static void main(String[] args){
TestSrc2Tgt testSrc2Tgt = new TestSrc2Tgt();
List<Src> srcList = new ArrayList<>();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
// 1. 按id进行分组,生成id对应的Src列表
Map<Integer, List<Src>> srcMap = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()));
// 2. 通过listSrc2ListTgt方法统计true或false的Src数量
// 我不喜欢这里的处理方式
List<Tgt> tgtMaplist =
srcMap.entrySet().stream().map(e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
.map(item->item.get(0)).collect(Collectors.toList());
tgtMaplist.stream().forEach(t -> {
System.out.println(t.getId());
System.out.println(t.getFalse_count());
System.out.println(t.getTrue_count());
});
}
}
这段代码确实可以工作,但我不喜欢listSrc2ListTgt()
方法以及在获取tgtMaplist
时使用的.map(item->item.get(0))
。我的想法是从List<Src>
直接转换为Tgt
,而不是先从List<Src>
转换为List<Tgt>
(仅包含一个元素),然后再从List<Tgt>
获取Tgt
。这是一种浪费和不完美的实现方式。
在理想情况下,代码应该是这样的:
public Tgt listSrc2ListTgt(List<Src> srcList){
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(src.getCount());
}
else {
tgt.setFalse_count(src.getCount());
}
}
return tgt;
}
在主方法中:
List<Tgt> tgtMaplist = srcMap.entrySet().stream()
.map(e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
.collect(Collectors.toList());
这样更舒适。因为在获取tgtMaplist
时没有使用.get(0)
方法的中间过程,似乎更加简洁。然而,理想的代码无法通过编译。
英文:
First of all, this question is from my real work. I need to solve it and I do some trade off working to realize it unperfect. What's more, I abstract and simplify the basic data structure to avoid sensitive data.
My source and target Object which is Src
and Tgt
is as followed:
public class Src {
int id;
boolean flag;
int count;
// Constructor Getter and Setter methods......
}
public class Tgt {
int id;
int true_count;
int false_count;
// Constructor Getter and Setter methods......
}
Now I have a list of Src Object named srcList
. I want to combine the list to one Object named Tgt
which operation is like count how many flag
in Src
Object is true or false in the list.
In java8 Stream, I haved tried to use flatmap
and map
operator to convert List to one Object like followed code:
public class TestSrc2Tgt {
public List<Tgt> listSrc2ListTgt(List<Src> srcList){
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(tgt.getTrue_count + src.getCount());
}
else {
tgt.setFalse_count(tgt.getFalse_count + src.getCount());
}
}
/* it is my question and I do not want to use List
What I want is convert List<Src> to Tgt which is a kind of combine srcList
*/
List<Tgt> tgtList = new ArrayList<>();
tgtList.add(tgt);
return tgtList;
}
public static void main(String[] args){
TestSrc2Tgt testSrc2Tgt = new TestSrc2Tgt();
List<Src> srcList = new ArrayList<>();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
// 1. Cluster the id and generate list by id
Map<Integer, List<Src>> srcMap = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()));
// 2. Count how many Src flag is true or false by listSrc2ListTgt method
// Where I hate is here
List<Tgt> tgtMaplist =
srcMap.entrySet().stream().map( e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
.map(item->item.get(0)).collect(Collectors.toList());
tgtMaplist.stream().forEach(t -> {
System.out.println(t.getId());
System.out.println(t.getFalse_count());
System.out.println(t.getTrue_count());
});
}
}
The code could really work but I hate the listSrc2ListTgt()
method and .map(item->item.get(0))
in getting List<Tgt> tgtMaplist
. My idea is a one way converting from List<Src>
to Tgt
but not convert from List<Src>
to List<Tgt>
(only one element) and then get Tgt
from List<Tgt>
. It is a wasted and unperfect way of realizing.<br>
In ideal situation
public Tgt listSrc2ListTgt(List<Src> srcList){
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(src.getCount());
}
else {
tgt.setFalse_count(src.getCount());
}
}
return tgt;
}
And in main method:
List<Tgt> tgtMaplist = srcMap.entrySet().stream()
.map( e -> {return testSrc2Tgt.listSrc2ListTgt(e.getValue());})
.collect(Collectors.toList());
It is more comfortable. Because there is no intermediate process taken from the tgtlist by using .get(0)
method, which seems less redundant. However the ideal code is unable to pass the compiler.
答案1
得分: 2
你的理想解决方案应该在 ListSrc2ListTgt
方法内部返回 tgt
而不是 tgtList
:
public Tgt ListSrc2ListTgt(List<Src> srcList) {
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(src.getCount());
} else {
tgt.setFalse_count(src.getCount());
}
}
return tgt;
}
为了提高 main
方法中流的可读性和可用性:
- 如果你不需要键,不要使用
entrySet()
,而是使用values()
。 - 然后可以使用方法引用来缩短 lambda 表达式。
List<Tgt> tgtMaplist = srcMap.values().stream()
.map(testSrc2Tgt::ListSrc2ListTgt)
.collect(Collectors.toList());
英文:
Your ideal solution should work, if you return tgt
instead of tgtList
inside ListSrc2ListTgt
:
public Tgt ListSrc2ListTgt(List<Src> srcList) {
Tgt tgt = new Tgt();
for(Src src : srcList) {
tgt.setId(src.getId());
if(src.isFlag()) {
tgt.setTrue_count(src.getCount());
} else {
tgt.setFalse_count(src.getCount());
}
}
return tgt;
}
And to improve the readability and usability of your stream in main
method:
- don't use
entrySet()
if you don't need the keys. usevalues()
instead. - the lambda can then be shortened with a method reference.
List<Tgt> tgtMaplist = srcMap.values().stream()
.map(testSrc2Tgt::ListSrc2ListTgt)
.collect(Collectors.toList());
答案2
得分: 2
你可以先将你的Src
映射到Tgt
。然后使用toMap
按id
进行映射,并使用合并函数将Tgt
的列表合并为一个。然后将映射值获取到一个ArrayList中。
Map<Integer, Tgt> tgtMap = srcList.stream()
.map(s -> new Tgt(s.getId(),
(s.isFlag() ? s.getCount() : 0),
(s.isFlag() ? 0 : s.getCount())))
.collect(Collectors.toMap(Tgt::getId, e -> e,
(a, b) -> new Tgt(a.getId(),
a.getTrue_count() + b.getTrue_count(),
a.getFalse_count() + b.getFalse_count())));
List<Tgt> tgtList = new ArrayList(tgtMap.values()); // 从映射值创建列表
在这里,使用map()
将Src
对象转换为Tgt
对象:
s -> new Tgt(s.getId(), (s.isFlag() ? s.getCount() : 0), (s.isFlag() ? 0 :s.getCount()))
然后使用Collectors.toMap
按Tgt::getId
进行映射,这是toMap
的第一个参数。接下来的参数是作为整个Tgt
对象的键的映射值,最后合并函数中的最后一个参数用于将两个Tgt
对象合并为一个Tgt
对象,从而合并相同键的所有值:
Collectors.toMap(Tgt::getId,
e -> e,
(a, b) -> new Tgt(a.getId(),
a.getTrue_count() + b.getTrue_count(),
a.getFalse_count() + b.getFalse_count()))
为了简化代码,带有解释的示例:
Map<Integer, Tgt> tgtMap = srcList.stream()
.map(s -> /*将其转换为Tgt*/ )
.collect(Collectors.toMap(Tgt::getId, e -> e, /*合并函数*/));
英文:
You can map your Src
into Tgt
first. Then using toMap
map by id
and merge the list of Tgt
into one using the merge function. Then get the map values in an ArrayList.
Map<Integer, Tgt> tgtMap = srcList.stream()
.map(s -> new Tgt(s.getId(),
(s.isFlag() ? s.getCount() : 0),
(s.isFlag() ? 0 : s.getCount())))
.collect(Collectors.toMap(Tgt::getId, e -> e,
(a, b) -> new Tgt(a.getId(),
a.getTrue_count() + b.getTrue_count(),
a.getFalse_count() + b.getFalse_count())));
List<Tgt> tgtList = new ArrayList(tgtMap.values()); // Create list from map values
Here using map()
transform Src
object into Tgt
object
s -> new Tgt(s.getId(), (s.isFlag() ? s.getCount() : 0),(s.isFlag() ? 0 :s.getCount()))
And then using Collectors.toMap
map by Tgt::getId
which is first parameter of toMap
.The next one is the key of the map e -> e
for the whole Tgt
obj as key. And finally last parameter in the merge function will used for merging two Tgt
objects into one Tgt
object so that all values of the same key merged.
Collectors.toMap(Tgt::getId,
e -> e,
(a, b) -> new Tgt(a.getId(),
a.getTrue_count() + b.getTrue_count(),
a.getFalse_count() + b.getFalse_count()))
To simplify the code with explanations
Map<Integer, Tgt> tgtMap = srcList.stream()
.map(s -> /*transform into Tgt*/)
.collect(Collectors.toMap(Tgt::getId, e -> e, /*Merge function*/));
答案3
得分: 1
不是严格的对您确切问题的回答,而是一个更简单的方法来找到所要求的3件事情:
int id = srcList.get(srcList.size() - 1).getId();
int true_count = srcList.stream().filter(Src::getFlag).count();
int false_count = srcList.size() - true_count;
英文:
Not strictly an answer to your exact question, but a simpler approach to find the 3 things asked for:
int id = srcList.get(srcList.size() - 1).getId();
int true_count = srcList.stream().filter(Src::getFlag).count();
int false_count = srcList.size() - true_count;
答案4
得分: 1
这里是一个使用Java流的简单方法:
List<Src> srcList = new ArrayList<Src>();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
List<Tgt> tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()))
.entrySet().stream()
.map(e ->
new Tgt(e.getKey(),
e.getValue().stream().mapToInt(s -> s.getFlag() ? s.getCount() : 0).sum(),
e.getValue().stream().mapToInt(s -> !s.getFlag() ? s.getCount() : 0).sum())
).collect(Collectors.toList());
System.out.println(tgtList);
这是输出结果:
[Tgt [id=1, true_count=15, false_count=130], Tgt [id=2, true_count=460, false_count=250]]
编辑:
我更倾向于不使用流,这样我们就不会多次遍历数据。
Map<Integer, Tgt> tgtMap = new HashMap<Integer, Tgt>();
for (Src src : srcList) {
Tgt tgt = new Tgt(src.getId(), src.getFlag() ? src.getCount() : 0, !src.getFlag() ? src.getCount() : 0);
tgtMap.compute(src.getId(), (k, v) ->
v == null ? tgt
: new Tgt(src.getId(),
tgt.getTrue_count() + v.getTrue_count(),
tgt.getFalse_count() + v.getFalse_count()));
}
英文:
Here is a pretty simply way using Java streams:
List<Src> srcList = new ArrayList<Src>();
srcList.add(new Src(1, true, 15));
srcList.add(new Src(1, false, 20));
srcList.add(new Src(1, false, 110));
srcList.add(new Src(2, true, 40));
srcList.add(new Src(2, false, 250));
srcList.add(new Src(2, true, 420));
List<Tgt> tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collectors.toList()))
.entrySet().stream()
.map(e ->
new Tgt(e.getKey(),
e.getValue().stream().mapToInt(s -> s.getFlag() ? s.getCount() : 0).sum(),
e.getValue().stream().mapToInt(s -> !s.getFlag() ? s.getCount() : 0).sum())
).collect(Collectors.toList());
System.out.println(tgtList);
Here is the output:
[Tgt [id=1, true_count=15, false_count=130], Tgt [id=2, true_count=460, false_count=250]]
EDIT:
I would prefer to not use streams so we don't loop over the data so many times.
Map<Integer, Tgt> tgtMap = new HashMap<Integer, Tgt>();
for (Src src: srcList) {
Tgt tgt = new Tgt(src.getId(), src.getFlag() ? src.getCount() : 0, !src.getFlag() ? src.getCount() : 0);
tgtMap.compute(src.getId(), (k, v) ->
v == null ? tgt
: new Tgt(src.getId(),
tgt.getTrue_count() + v.getTrue_count(),
tgt.getFalse_count() + v.getFalse_count()));
}
答案5
得分: 0
这段代码需要一个 Pair
类 - 可以从库中获取,或者您可以自己创建。您可以使用 Collectors.partitioningBy
替代自定义的收集器,不过这种方式更高效。
List<Tgt> tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collector.of(
() -> new Pair<>(0L, 0L),
(pair, src) -> {
if (src.isFlag())
pair.setKey(pair.getKey() + src.getCount());
else
pair.setValue(pair.getValue() + src.getCount());
},
(pairA, pairB) -> new Pair<>(pairA.getKey() + pairB.getKey(), pairA.getValue() + pairB.getValue())
)))
.entrySet()
.stream()
.map(entry -> new Tgt(entry.getKey(), (int) (long) entry.getValue().getKey(), (int) (long) entry.getValue().getValue()))
.collect(Collectors.toList());
英文:
For this code you need a Pair
class - either from a library, or you can create it yourself. Instead of custom collector you could use Collectors.partitioningBy
, but this way it's more efficient.
List<Tgt> tgtList = srcList.stream()
.collect(Collectors.groupingBy(Src::getId, Collector.of(
() -> new Pair<>(0L, 0L),
(pair, src) -> {
if (src.isFlag())
pair.setKey(pair.getKey() + src.getCount());
else
pair.setValue(pair.getValue() + src.getCount());
},
(pairA, pairB) -> new Pair<>(pairA.getKey() + pairB.getKey(), pairA.getValue() + pairB.getValue())
)))
.entrySet()
.stream()
.map(entry -> new Tgt(entry.getKey(), (int) (long) entry.getValue().getKey(), (int) (long) entry.getValue().getValue()))
.collect(Collectors.toList());
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论