我有一个这样的清单:
val l= List(("Agent", "PASS"), ("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent", "PASS"), ("Agent 2", "PASS") )
我需要得到一个这样的列表:
val filteredList= List(("Agent", "FAIL"), ("Agent 1", "FAIL"), ("Agent 2", "PASS") )
发生什么事了?
("Agent", "PASS"), ("Agent", "FAIL")
变为
("Agent", "FAIL")
(因为如果至少有一次失败,我需要保留该条目)
代理1和代理2的条目保持不变,因为每个条目只有一个条目。
我找到的最接近的答案是 How in Scala to find unique items in List但我无法告诉如何使用FAIL保留条目。
我希望问题很明确,如果没有,我可以给你一个更好的例子。
由于
答案 0 :(得分:9)
<强>序言强>
在我看来,状态可以被视为具有优先级,如果给出一系列(agent,status)
对,则任务是仅为每个代理选择最高优先级状态。不幸的是,状态并没有强烈地键入如此定义的显式排序,但是......因为它只是一个只有两个值的字符串,所以我们可以安全地使用字符串排序,与优先级保持1:1的对应关系。
我的答案都利用了两个有用的事实:
在自然字符串排序中,"FAIL" < "PASS"
,所以:
List("PASS", "FAIL", "PASS").sorted.head = "FAIL"
对于两个元组(x,a)
和(x,b)
,(x,a) > (x, b)
如果(a > b)
更新回复
val solution = l.sorted.reverse.toMap
通过Seq[(A,B)]
方法将Map[A,B]
转换为.toMap
时,原始元组序列中的每个“键”只能出现在生成的Map中一次。碰巧,转换使用最后一次这样的事件。
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)
原始回复
从答案开始......
val oldSolution = (l groupBy (_._1)) mapValues {_.sorted.head._2}
...然后展示我的工作:)
//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)
但是,您可以直接对agent -> status
对进行排序,而无需先提取_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)
在任何一种情况下,如果您愿意,可以随意转换回对象列表:
l.sorted.reverse.toMap.toList = List(
(Agent 2, PASS),
(Agent 1, FAIL),
(Agent, FAIL))
答案 1 :(得分:6)
这是你想要的吗?
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))
或者这是一个更短,更模糊的解决方案:
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))
答案 2 :(得分:4)
很多好的解决方案,但无论如何这里都是我的。 : - )
l
.groupBy(_._1) // group by key
.map {
case (key, list) =>
if (list.exists(_._2 == "FAIL")) (key, "FAIL")
else (key, "PASS")
}
这是我突然顿悟的另一个:
def booleanToString(b: Boolean) = if (b) "PASS" else "FAIL"
l
.groupBy(_._1)
.map {
case (key, list) => key -> booleanToString(list.forall(_._2 == "PASS"))
}
答案 3 :(得分:2)
这是我的看法。首先是功能性解决方案:
l.map(_._1).toSet.map({n:String=>(n, if(l contains (n,"FAIL")) "FAIL" else "PASS")})
首先我们隔离名称,唯一(toSet
),然后我们将每个名称映射到一个元组,其自身作为第一个元素,如果"FAIL"
中包含失败,则l
作为第二个元素1}},或者显然必须是"PASS"
。
结果是一组。当然,如果你真的需要一个列表,你可以在调用链的末尾做toList
。
这是一个必要的解决方案:
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
我不喜欢它,因为它是必要的,但是嘿。从某种意义上说,当你手动解决这个问题时,它会更好地反映出你实际上会做什么。对于您看到的每个"FAIL"
,您都会删除所有相应的"PASS"
个es。之后,您确保唯一性(.toSet.toList
)。
请注意,l
在命令式解决方案中是var
,这是必要的,因为它会被重新分配。
答案 4 :(得分:1)
查看Aggregate list values in Scala
在您的情况下,您按代理进行分组并通过折叠PASS + PASS =&gt; PASS和ANY + FAIL =&gt; FAIL进行聚合。
答案 5 :(得分:1)
首先分组可能更有效率,然后找到PASS / FAIL的消除:
l.filter(_._2 == "PASS").toSet -- l.filter(_._2 == "FAIL").map(x => (x._1, "PASS"))
这取决于您("Agent", "PASS")
的输出,但如果您只想要代理商:
l.filter(_._2 == "PASS").map(x => x._1).toSet -- l.filter(_._2 == "FAIL").map(x => x._1)
不知怎的,我预计第二个会更短。
答案 6 :(得分:1)
据我所知,你想:
由于我仍然发现foldLeft
,reduceLeft
等难以阅读,因此以上步骤直接转换为理解:
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))
如果您真的需要.toList
,可以在那里拨打List
。
修改略微修改后使用exists
成语suggested by Daniel C. Sobral。
答案 7 :(得分:0)
您需要保留原始订单吗?如果没有,我所知道的最短解决方案(也非常简单)是:
{
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
}
但这不会删除额外的通行证。如果你想要,那么你需要一个额外的地图:
{
val fail = l.filter(_._2 == "FAIL").toMap
l.toMap.filter(x => !fail.contains(x._1)).toList ::: fail.toList
}
另一方面,您可能希望以最初找到它们的顺序获取元素。这比较棘手,因为您需要跟踪第一个有趣项目何时出现:
{
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 }
}