英文:
Java stream elegant solution to avoid looping multiple times
问题
class Note{
private text;
..
private int score = 0;
}
class Project{
...
List<Note> notes;
private int score = 0;
}
项目得分由项目属性和所有笔记分数之和计算得出。
首先,我正在更新和替换项目中的所有笔记。然后再次迭代以计算笔记分数。
project.notes(project.notes()
.stream()
.map(this::updateNote)
.collect(Collectors.toList()));
project.score(project.notes()
.stream()
.mapToInt(n->n.score())
.sum());
private Note updateNote(Note note){
note.score(....);
return note;
}
不过,我觉得这样做不够优雅。有没有一种更好的解决方案,可以避免两次循环?
英文:
class Note{
private text;
..
private int score = 0;
}
class Project{
...
List<Note> notes;
private int score = 0;
}
Project score is derived by project properties + sum of note scores.
First I'm updating and replacing all notes in the project. Then iterating again to sum the note score.
project.notes(project.notes()
.stream()
.map(this::updateNote)
.collect(Collectors.toList()));
project.score(project.notes()
.stream()
.mapToInt(n->n.score())
.sum());
private Note updateNote(Note note){
note.score(....);
return note;
}
Somehow I feel this is not right. Is there an elegant solution to avoid looping twice?
答案1
得分: 1
在类似于映射操作中执行具有副作用的方法,无论如何切分这个谜题,都是可疑的,它总是会显得有些奇怪。
你在这里滥用了映射:你将对象映射到其自身,但在这样做的同时,引发了副作用。嗯,不管怎样,我想 - 这就是可疑的地方,但代码是可行的(只是不好的风格)。还要注意,将收集到的列表传递给 project.notes
没有任何作用,只需 project.notes().stream().map(this::updateNote).collect(Collectors.toList())
,让集合已经丢失到了其他地方。收集的唯一目的只是强制流实际迭代(映射不会导致迭代,它只是说:当您开始迭代时,逐步进行映射)。
所以:
project.notes()
.stream()
.map(this::updateNote) // 流中的无操作。只是让副作用发生
.mapToInt(this::score)
.sum();
这就是你所需要的一切 - 但还是有点可疑。如果 updateNote
而不是更改了 Note
对象,并且有一个 calculateScore
方法,你可以这样做:
project.notes()
.stream()
.mapToInt(this::calculateScore)
.sum();
在这里,calculateScore
不会改变 Note
对象的任何内容,它只是计算分数并返回,而不改变任何字段。
编辑:我忘记了一个流中的 'stream',并添加了一个澄清。
英文:
Performing a side-effect-ful method in a map operation like this is suspect no matter how you want to slice this puzzle, it's always going to look a bit weird.
You're abusing map here: You map objects to itself, but in so doing, cause a side effect. well, in for a penny, in for a pound, I guess - that is the suspect thing here, but the code works (it's just bad style). Note also that passing the collected list to project.notes
does nothing, just project.notes().stream().map(this::updateNote).collect(Collectors.toList())
and letting the collection be lost to the either already 'works'. The only point of collecting is merely to force the stream to actually iterate (map doesn't cause iteration, it merely says: When you start iterating, map as-you-go).
So:
project.notes()
.stream()
.map(this::updateNote)) // no-op streamwise. Just making the side effect happen
.mapToInt(this::score)
.sum();
is all you need - but it's.. still a bit stinky. If instead of updateNote
, note was immutable and there is a calculateScore
method, you could do:
project.notes()
.stream()
.mapToInt(this::calculateScore)
.sum()
Here, calculateScore
doesn't change anything about a Note
object, it merely.. calculates the score and returns it, without changing any fields.
EDIT: I forgot a 'stream' in stream, and added a clarification.
答案2
得分: 1
你可以通过在AtomicInteger
中累积总和来避免两次循环,但这将导致将方法引用this::updateNote
替换为lambda表达式:
AtomicInteger sum = new AtomicInteger(0);
project.notes(project.notes()
.stream()
.map(note -> {
Note updated = updateNote(note);
sum.addAndGet(updated.getScore());
return updated;
})
.collect(Collectors.toList()));
project.score(sum.intValue());
英文:
You may get rid of looping twice by accumulating the sum in an AtomicInteger
, but this would result in replacing the method reference this::updateNote
with a lambda:
AtomicInteger sum = new AtomicInteger(0);
project.notes(project.notes()
.stream()
.map(note -> {
Note updated = updateNote(note);
sum.addAndGet(updated.getScore());
return updated;
})
.collect(Collectors.toList()));
project.score(sum.intValue());
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论