Search code examples
groovyfunctional-programmingranking

Solving a ranking problem in a purely functional way using groovy


I have solved a problem that ranks fruits by the number of votes. Unfortunately, I want to solve the problem in a purely functional way without mutating the rankPosition variable. Here is my solution:

  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"

How can I solve this problem without having to declare a rankingPosition variable external to the closure and mutating its state. Please notes that this solution works but I have since learned that I shouldn't be doing it this way. I want to be able to fill the rankings with the correct ranking. The inject function does an accumulation but I don't know how to combine it in a way to also set the ranking with the value accumulated in the inject.

I am simply stuck, just don't seem to be able to reason about this one. My attempt below to use inject, simply did not work. Maybe there isn't a way to do this in a purely functional way, better thsn my attempt.

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"

I anyone can then I would appreciate your solution or just assume my attempt is the most realistic way of solving this one.


Solution

  • This is a pure functional solution. It leaves the initial map of maps unchanged and produces a new one:

    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]