I have a list like this:
val l= List(("Agent", "PASS"), ("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent", "PASS"), ("Agent 2", "PASS") )
and I need to end up with a list like this:
val filteredList= List(("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent 2", "PASS") )
What happened?
("Agent", "PASS"), ("Agent", "FAIL")
becomes
("Agent", "FAIL")
(because if there is at least one FAIL, I need to keep that entry)
the entries for Agent 1 and Agent 2 stay the same because there are just one entry for each.
The closest answer I found is How in Scala to find unique items in List but I cannot tell how to keep the entries with FAIL.
I hope the question is clear, if not, I can give you a better example.
Thanks
Preamble
It occurred to me that the status could be seen as having a priority, and if given a sequence of (agent,status)
pairs then the task is to select only the highest priority status for each agent. Unfortunately, status isn't strongly typed with an explicit ordering so defined, but... as it's a string with only two values we can safely use string ordering as having a 1:1 correspondence to the priority.
Both my answers take advantage of two useful facts:
In natural string ordering, "FAIL" < "PASS"
, so:
List("PASS", "FAIL", "PASS").sorted.head = "FAIL"
For two tuples (x,a)
and (x,b)
, (x,a) > (x, b)
if (a > b)
UPDATED REPLY
val solution = l.sorted.reverse.toMap
When converting a Seq[(A,B)]
to a Map[A,B]
via the .toMap
method, each "key" in the original sequence of tuples can only appear in the resulting Map once. As it happens, the conversion uses the last such occurrence.
l.sorted.reverse = List(
(Agent 2,PASS), // <-- Last "Agent 2"
(Agent 1,FAIL), // <-- Last "Agent 1"
(Agent,PASS),
(Agent,PASS),
(Agent,FAIL)) // <-- Last "Agent"
l.sorted.reverse.toMap = Map(
Agent 2 -> PASS,
Agent 1 -> FAIL,
Agent -> FAIL)
ORIGINAL REPLY
Starting with the answer...
val oldSolution = (l groupBy (_._1)) mapValues {_.sorted.head._2}
...and then showing my working :)
//group
l groupBy (_._1) = Map(
Agent 2 -> List((Agent 2,PASS)),
Agent 1 -> List((Agent 1,FAIL)),
Agent -> List((Agent,PASS), (Agent,FAIL), (Agent,PASS))
)
//extract values
(l groupBy (_._1)) mapValues {_.map(_._2)} = Map(
Agent 2 -> List(PASS),
Agent 1 -> List(FAIL),
Agent -> List(PASS, FAIL, PASS))
//sort
(l groupBy (_._1)) mapValues {_.map(_._2).sorted} = Map(
Agent 2 -> List(PASS),
Agent 1 -> List(FAIL),
Agent -> List(FAIL, PASS, PASS))
//head
(l groupBy (_._1)) mapValues {_.map(_._2).sorted.head} = Map(
Agent 2 -> PASS,
Agent 1 -> FAIL,
Agent -> FAIL)
However, you can directly sort the agent -> status
pairs without needing to first extract _2
:
//group & sort
(l groupBy (_._1)) mapValues {_.sorted} = Map(
Agent 2 -> List((Agent 2,PASS)),
Agent 1 -> List((Agent 1,FAIL)),
Agent -> List((Agent,FAIL), (Agent,PASS), (Agent,PASS)))
//extract values
(l groupBy (_._1)) mapValues {_.sorted.head._2} = Map(
Agent 2 -> PASS,
Agent 1 -> FAIL,
Agent -> FAIL)
In either case, feel free to convert back to a List of Pairs if you wish:
l.sorted.reverse.toMap.toList = List(
(Agent 2, PASS),
(Agent 1, FAIL),
(Agent, FAIL))
Is this what you want?
jem@Respect:~$ scala
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val l= List(("Agent", "PASS"), ("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent", "PASS"), ("Agent 2", "PASS") )
l: List[(java.lang.String, java.lang.String)] = List((Agent,PASS), (Agent,FAIL), (Agent 1,FAIL), (Agent,PASS), (Agent 2,PASS))
scala> l.foldLeft(Map.empty[String, String]){(map,next) =>
| val (agent, result) = next
| if ("FAIL" == result) map.updated(agent, result)
| else {
| val maybeExistingResult = map.get(agent)
| if (maybeExistingResult.map(_ == "FAIL").getOrElse(false)) map
| else map.updated(agent, result)
| }
| }
res0: scala.collection.immutable.Map[String,String] = Map((Agent,FAIL), (Agent 1,FAIL), (Agent 2,PASS))
scala> res0.toList
res1: List[(String, String)] = List((Agent 2,PASS), (Agent 1,FAIL), (Agent,FAIL))
Or here is a shorter and more obscure solution:
scala> l.groupBy(_._1).map(pair => (pair._1, pair._2.reduceLeft((a,b) => if ("FAIL" == a._2 || "FAIL" == b._2) (a._1, "FAIL") else a))).map(_._2).toList
res2: List[(java.lang.String, java.lang.String)] = List((Agent 2,PASS), (Agent 1,FAIL), (Agent,FAIL))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With