Scala - получить уникальные значения из списка с поворотом - PullRequest
4 голосов
/ 15 декабря 2010

У меня есть список, подобный этому:

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 найти уникальные элементы в списке , но я не могу сказать, как сохранить записи с ошибкой.

Надеюсь, вопрос ясен, если нет, я могу привести лучший пример.

Спасибо

Ответы [ 8 ]

9 голосов
/ 15 декабря 2010

Преамбула

Мне пришло в голову, что состояние может рассматриваться как имеющее приоритет, и если задана последовательность пар (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)

ОБНОВЛЕННЫЙ ОТВЕТ

val solution = l.sorted.reverse.toMap

При преобразовании Seq[(A,B)] до Map[A,B] с помощью метода .toMap, каждый «ключ» в исходной последовательности кортежей может появиться в результирующей карте только один раз.Как это происходит, преобразование использует последний такой случай.

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))
6 голосов
/ 15 декабря 2010

Это то, что вы хотите?

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))
4 голосов
/ 15 декабря 2010

Множество хороших решений, но все равно мое.: -)

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"))
}
2 голосов
/ 15 декабря 2010

Вот мой дубль. Первое функциональное решение:

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

Сначала мы выделяем имена однозначно (toSet), затем мы сопоставляем каждое имя с кортежем, в котором первый элемент указан сам, и либо "FAIL" как второй элемент, если ошибка содержится в l, либо иным это, очевидно, должно быть "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 в императивном решении, которое необходимо, поскольку оно переназначается.

1 голос
/ 15 декабря 2010

Итак, насколько я понимаю, вы хотите:

  1. Группировать кортежи по их первой записи («ключу»)
  2. Для каждого ключа проверить все записи второго кортежа назначение "FAIL"
  3. Произведите (ключ, "FAIL"), если вы найдете "FAIL" или (ключ, "PASS"), иначе

Так как я все еще нахожу 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 идиома , предложенная Даниэлем С. Собралом .

1 голос
/ 15 декабря 2010

Возможно, более эффективно сначала сгруппировать, а затем найти дизассудцию 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)

Каким-то образом я ожидал, что второй будет короче.

1 голос
/ 15 декабря 2010

Посмотрите на Сводные значения списка в Scala

В вашем случае вы бы группировали по агенту и агрегировали, складывая PASS + PASS => PASS и ANY + FAIL => FAIL.

0 голосов
/ 15 декабря 2010

Вам нужно сохранить оригинальный заказ?Если нет, самое короткое решение, которое я знаю (также довольно простое):

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