4

我有一个这样的列表:

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")

(因为如果至少有一个 FAIL,我需要保留该条目)

代理 1 和代理 2 的条目保持不变,因为每个条目只有一个。

我找到的最接近的答案是 如何在 Scala 中找到 List 中的唯一项目, 但我不知道如何使用 FAIL 保留条目。

我希望问题很清楚,如果没有,我可以给你一个更好的例子。

谢谢

4

8 回答 8

9

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:

  1. In natural string ordering, "FAIL" < "PASS", so:

    List("PASS", "FAIL", "PASS").sorted.head = "FAIL"
    
  2. 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))
于 2010-12-15T10:20:04.223 回答
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))
于 2010-12-15T06:04:39.470 回答
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"))
}
于 2010-12-15T15:18:45.357 回答
2

这是我的看法。首先是功能解决方案:

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

首先,我们唯一地隔离名称 ( toSet),然后将每个名称映射到一个元组,其自身作为第一个元素,"FAIL"如果失败包含在 中,则作为第二个元素l,否则它显然必须是 a "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)。

请注意,这lvar命令式解决方案中的 a ,这是必要的,因为它被重新分配。

于 2010-12-15T07:03:40.090 回答
1

So as I understand it, you want to:

  1. Group the tuples by their first entry ("key")
  2. For each key, check all tuple second entries for the value "FAIL"
  3. Produce (key, "FAIL") if you find "FAIL" or (key, "PASS") otherwise

Since I still find foldLeft, reduceLeft, etc. hard to read, here's a direct translation of the steps above into for comprehensions:

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 there if you really want a List.

Edit: slightly modified to use the exists idiom suggested by Daniel C. Sobral.

于 2010-12-15T10:22:04.953 回答
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)

不知何故,我预计第二个会更短。

于 2010-12-15T08:09:19.180 回答
1

查看Scala 中的聚合列表值

在您的情况下,您将按代理分组并通过折叠 PASS+PASS=>PASS 和 ANY+FAIL=>FAIL 进行聚合。

于 2010-12-15T05:42:47.360 回答
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 }
}
于 2010-12-15T17:27:27.877 回答