Scala - get unique values ​​from a swirl list

I have a list like this:

val l= List(("Agent", "PASS"), ("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent", "PASS"), ("Agent 2", "PASS") ) 

and I need the list to be as follows:

 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 save this entry)

the entries for agent 1 and agent 2 remain unchanged because there is only one entry for each of them.

The closest answer I found is How to find unique items in a list in Scala , but I cannot say how to save entries with FAIL.

I hope the question will be clear, if not, I can give you a better example.

thanks

+4
source share
8 answers

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 is this 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)) 
+6
source

Preamble

It occurred to me that status can be considered as having priority, and if a sequence of pairs (agent,status) , then the task is to select only the highest priority status for each agent. Unfortunately, the status is not strongly typed with explicit ordering defined in this way, but ... since it is a string with only two values, we can safely use string ordering as having a 1: 1 priority.

My answers use two useful facts:

  • In the natural ordering of strings is "FAIL" < "PASS" , therefore:

     List("PASS", "FAIL", "PASS").sorted.head = "FAIL" 
  • For two tuples (x,a) and (x,b) , (x,a) > (x, b) if (a > b)

UPDATED ANSWER

 val solution = l.sorted.reverse.toMap 

When converting a Seq[(A,B)] to Map[A,B] using the .toMap method .toMap each "key" in the original sequence of tuples can appear only once in the resulting map. Since this happens, the conversion uses the last such event.

 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 RESPONSE

Starting from the answer ...

 val oldSolution = (l groupBy (_._1)) mapValues {_.sorted.head._2} 

... and then showing my work :)

 //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 sort the agent -> status pairs directly without having 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 any case, feel free to convert back to the list of pairs if you want:

 l.sorted.reverse.toMap.toList = List( (Agent 2, PASS), (Agent 1, FAIL), (Agent, FAIL)) 
+9
source

Lots of good solutions, but here anyway. :-)

 l .groupBy(_._1) // group by key .map { case (key, list) => if (list.exists(_._2 == "FAIL")) (key, "FAIL") else (key, "PASS") } 

Here is another time when I suddenly regained sight:

 def booleanToString(b: Boolean) = if (b) "PASS" else "FAIL" l .groupBy(_._1) .map { case (key, list) => key -> booleanToString(list.forall(_._2 == "PASS")) } 
+4
source

Here is my welcome. First functional solution:

 l.map(_._1).toSet.map({n:String=>(n, if(l contains (n,"FAIL")) "FAIL" else "PASS")}) 

First, we isolate the names uniquely ( toSet ), then map each name to a tuple with itself as the first element and either "FAIL" as the second element if the failure is contained in l , otherwise it should obviously be "PASS" .

The result is many. Of course, you can do toList at the end of the call chain if you really need a list.

Here is an imperative solution:

 var l = List(("Agent", "PASS"), ("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent", "PASS"), ("Agent 2", "PASS")) l.foreach(t=>if(t._2=="FAIL") l=l.filterNot(_ == (t._1,"PASS"))) l=l.toSet.toList 

I do not like it because it is necessary, but hey. In a way, it better reflects what you actually do when you decide it manually. For each "FAIL" you see, you delete all the corresponding "PASS" es. After that, you provide uniqueness ( .toSet.toList ).

Note that l is var in the imperative solution, which is necessary because it is reassigned.

+2
source

See Aggregate List Values ​​in Scala

In your case, you group the agent and aggregate by adding PASS + PASS => PASS and ANY + FAIL => FAIL.

+1
source

It might be more efficient to group first, then find the PASS / FAIL omission:

 l.filter(_._2 == "PASS").toSet -- l.filter(_._2 == "FAIL").map(x => (x._1, "PASS")) 

It depends on your output ("Agent", "PASS") , but if you just want the agents:

 l.filter(_._2 == "PASS").map(x => x._1).toSet -- l.filter(_._2 == "FAIL").map(x => x._1) 

Somehow I expected the second to be shorter.

+1
source

So, as I understand it, you want:

  • Group tuples by their first entry ("key")
  • For each key, check all entries in the second set for the value β€œFAIL”
  • Produce (key, "FAIL") if you find "FAIL" or (key, "PASS") otherwise

Since I still find foldLeft , reduceLeft , etc. hard to read, here is a direct translation of the above steps into understanding:

 scala> for ((key, keyValues) <- l.groupBy{case (key, value) => key}) yield { | val hasFail = keyValues.exists{case (key, value) => value == "FAIL"} | (key, if (hasFail) "FAIL" else "PASS") | } res0: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map((Agent 2,PASS), (Agent 1,FAIL), (Agent,FAIL)) 

You can call .toList at the end if you really want a List .

Edit: slightly modified to use exists idiom proposed by Daniel S. Sobral .

+1
source

Do you need to keep the original order? If not, the shortest solution that I know of (also pretty simple):

 { val fail = l.filter(_._2 == "FAIL").toMap // Find all the fails l.filter(x => !fail.contains(x._1)) ::: fail.toList // All nonfails, plus the fails } 

but this will not remove additional passages. If you want this, you need an additional card:

 { val fail = l.filter(_._2 == "FAIL").toMap l.toMap.filter(x => !fail.contains(x._1)).toList ::: fail.toList } 

On the other hand, you may want to take the elements in the same order that you originally found. This is harder because you need to track when the first interesting item appeared:

 { val fail = l.filter(_._2 == "FAIL").toMap val taken = new scala.collection.mutable.HashMap[String,String] val good = (List[Boolean]() /: l)((b,x) => { val okay = (!taken.contains(x._1) && (!fail.contains(x._1) || x._2=="FAIL")) if (okay) taken += x okay :: b }).reverse (l zip good).collect{ case (x,true) => x } } 
0
source

Source: https://habr.com/ru/post/1332179/


All Articles