使用 groovy 以纯函数方式解决排名问题
Solving a ranking problem in a purely functional way using groovy
我解决了一个水果按票数排序的问题。不幸的是,我想在不改变 rankPosition 变量的情况下以纯函数方式解决问题。这是我的解决方案:
def fruits=[
[name:'apple', votes:120 , ranking:null ],
[name:'banana', votes:200, ranking: null],
[name:'apricot', votes:66, ranking:null ],
[name:'pear', votes:84, ranking:null],
[name:'kiwi', votes:77, ranking:null],
[name:'plum', votes:66, ranking:null],
[name:'berry', votes:120, ranking:null],
[name:'pineapple', votes:50, ranking:null],
[name:'grapes', votes:200, ranking:null]
]
def rankPosition= 1
def groupedByVotes = fruits.groupBy {it.votes }
println "Ratings $groupedByVotes"
def finalResults=groupedByVotes.sort().each { votes, items ->
items.each { it.ranking = rankPosition }
rankPosition += items.size()
}
println "Final Results are $finalResults"
如何在不必在闭包外部声明 rankingPosition 变量并改变其状态的情况下解决此问题。请注意,此解决方案有效,但我后来了解到我不应该这样做。
我希望能够用正确的排名来填充排名。 inject 函数进行了累加,但我不知道如何以某种方式将它结合起来,以设置在 inject 中累加的值的排名。
我只是被卡住了,只是似乎无法推理这个。我在下面尝试使用注入,根本没有用。也许没有办法以纯粹的功能方式做到这一点,比我的尝试更好。
def res= groupedByVotes.collectEntries{votes, list1->
println "list class $list1"
def r= list1.inject(0){acc,l-> acc+l.size()}
list1.each{it.ranking=r}
println "$r"
[(votes): list1]
}
println "$res"
任何人都可以然后我会很感激你的解决方案,或者只是假设我的尝试是解决这个问题的最现实的方法。
你可以试试这个:
def results = groupedByVotes.sort()
.inject(new Tuple(1, []), { acc, entry ->
entry.value.each { it.ranking = acc[0] }
return new Tuple(acc[0] + entry.value.size(), acc[1] << entry.value)
})
finalResults = results[1]
在折叠的每一步(由 .inject(...)
完成)中,您都有一个 Tuple
包含下一个排名和到目前为止已计算的部分列表。作为最后一步,您从 Tuple
中提取结果列表。但这会将地图转换为列表。
这个解决方案更简单,因为如果你就地修改旧的,就不需要创建新的集合,而且它保留了地图:
def finalResults = groupedByVotes.sort()
finalResults.inject(1, { acc, entry ->
entry.value.each { it.ranking = acc }
return acc + entry.value.size()
})
但这两种解决方案都不是真正有效的。真正的功能代码将所有值视为不可变的。请参阅我的其他答案(即将发布)以获得真正的功能解决方案。
我可能没有很好地解释我想要实现的目标。计算排名后,我希望为列表中的各个元素插入值。这是我想出的:
def sortedFruits= fruits.sort{f1, f2 -> f1.votes <=> f2.votes}
(0..sortedFruits.size()-1)
.each{ i ->
if(i==0){
sortedFruits.get(i).ranking=1
}else if(sortedFruits.get(i-1).votes==sortedFruits.get(i).votes){
sortedFruits.get(i).ranking=i
}else{
sortedFruits.get(i).ranking=i+1
}
if(i<sortedFruits.size()){
def f= sortedFruits.get(i)
println "${f}"
}
}
println "Sorted Fruits are $sortedFruits"
最后的结果是这样的
分类的水果是 [[name:lemons, votes:20, ranking:1], [name:guava, votes:20, ranking:1],
[name:菠萝,votes:50,ranking:3],[name:杏,votes:66,ranking:4],[
name:plum、votes:66、ranking:4]等
这是一个纯功能性解决方案。它保留原始地图的地图不变并生成一个新地图:
def results = groupedByVotes.sort().inject(new Tuple(1, [:]), { acc, entry ->
def newRank = acc[0] + entry.value.size()
def newValue = entry.value.collect { [*:it, ranking:acc[0]] }
return new Tuple(newRank, [*:acc[1], (entry.key):newValue] )
})
finalResults = results[1]
我解决了一个水果按票数排序的问题。不幸的是,我想在不改变 rankPosition 变量的情况下以纯函数方式解决问题。这是我的解决方案:
def fruits=[
[name:'apple', votes:120 , ranking:null ],
[name:'banana', votes:200, ranking: null],
[name:'apricot', votes:66, ranking:null ],
[name:'pear', votes:84, ranking:null],
[name:'kiwi', votes:77, ranking:null],
[name:'plum', votes:66, ranking:null],
[name:'berry', votes:120, ranking:null],
[name:'pineapple', votes:50, ranking:null],
[name:'grapes', votes:200, ranking:null]
]
def rankPosition= 1
def groupedByVotes = fruits.groupBy {it.votes }
println "Ratings $groupedByVotes"
def finalResults=groupedByVotes.sort().each { votes, items ->
items.each { it.ranking = rankPosition }
rankPosition += items.size()
}
println "Final Results are $finalResults"
如何在不必在闭包外部声明 rankingPosition 变量并改变其状态的情况下解决此问题。请注意,此解决方案有效,但我后来了解到我不应该这样做。 我希望能够用正确的排名来填充排名。 inject 函数进行了累加,但我不知道如何以某种方式将它结合起来,以设置在 inject 中累加的值的排名。
我只是被卡住了,只是似乎无法推理这个。我在下面尝试使用注入,根本没有用。也许没有办法以纯粹的功能方式做到这一点,比我的尝试更好。
def res= groupedByVotes.collectEntries{votes, list1->
println "list class $list1"
def r= list1.inject(0){acc,l-> acc+l.size()}
list1.each{it.ranking=r}
println "$r"
[(votes): list1]
}
println "$res"
任何人都可以然后我会很感激你的解决方案,或者只是假设我的尝试是解决这个问题的最现实的方法。
你可以试试这个:
def results = groupedByVotes.sort()
.inject(new Tuple(1, []), { acc, entry ->
entry.value.each { it.ranking = acc[0] }
return new Tuple(acc[0] + entry.value.size(), acc[1] << entry.value)
})
finalResults = results[1]
在折叠的每一步(由 .inject(...)
完成)中,您都有一个 Tuple
包含下一个排名和到目前为止已计算的部分列表。作为最后一步,您从 Tuple
中提取结果列表。但这会将地图转换为列表。
这个解决方案更简单,因为如果你就地修改旧的,就不需要创建新的集合,而且它保留了地图:
def finalResults = groupedByVotes.sort()
finalResults.inject(1, { acc, entry ->
entry.value.each { it.ranking = acc }
return acc + entry.value.size()
})
但这两种解决方案都不是真正有效的。真正的功能代码将所有值视为不可变的。请参阅我的其他答案(即将发布)以获得真正的功能解决方案。
我可能没有很好地解释我想要实现的目标。计算排名后,我希望为列表中的各个元素插入值。这是我想出的:
def sortedFruits= fruits.sort{f1, f2 -> f1.votes <=> f2.votes}
(0..sortedFruits.size()-1)
.each{ i ->
if(i==0){
sortedFruits.get(i).ranking=1
}else if(sortedFruits.get(i-1).votes==sortedFruits.get(i).votes){
sortedFruits.get(i).ranking=i
}else{
sortedFruits.get(i).ranking=i+1
}
if(i<sortedFruits.size()){
def f= sortedFruits.get(i)
println "${f}"
}
}
println "Sorted Fruits are $sortedFruits"
最后的结果是这样的 分类的水果是 [[name:lemons, votes:20, ranking:1], [name:guava, votes:20, ranking:1], [name:菠萝,votes:50,ranking:3],[name:杏,votes:66,ranking:4],[ name:plum、votes:66、ranking:4]等
这是一个纯功能性解决方案。它保留原始地图的地图不变并生成一个新地图:
def results = groupedByVotes.sort().inject(new Tuple(1, [:]), { acc, entry ->
def newRank = acc[0] + entry.value.size()
def newValue = entry.value.collect { [*:it, ranking:acc[0]] }
return new Tuple(newRank, [*:acc[1], (entry.key):newValue] )
})
finalResults = results[1]