Scala - 从List中获取带有扭曲的唯一值

时间:2010-12-15 05:22:06

标签: scala scala-collections

我有一个这样的清单:

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保留条目。

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

由于

8 个答案:

答案 0 :(得分:9)

<强>序言

在我看来,状态可以被视为具有优先级,如果给出一系列(agent,status)对,则任务是仅为每个代理选择最高优先级状态。不幸的是,状态并没有强烈地键入如此定义的显式排序,但是......因为它只是一个只有两个值的字符串,所以我们可以安全地使用字符串排序,与优先级保持1:1的对应关系。

我的答案都利用了两个有用的事实:

  1. 在自然字符串排序中,"FAIL" < "PASS",所以:

    List("PASS", "FAIL", "PASS").sorted.head = "FAIL"
    
  2. 对于两个元组(x,a)(x,b)(x,a) > (x, b)如果(a > b)

  3. 更新回复

    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)

据我所知,你想:

  1. 按照第一个条目(“key”)
  2. 对元组进行分组
  3. 对于每个键,检查所有元组第二个条目的值“FAIL”
  4. 如果您发现“失败”或(密钥,“通过”),则生成(密钥,“失败”)
  5. 由于我仍然发现foldLeftreduceLeft等难以阅读,因此以上步骤直接转换为理解:

    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 }
}