#scala #collections
#scala #Коллекции
Вопрос:
Я думаю, что это может быть обычная операция. Так что, возможно, это внутри API, но я не могу его найти. Также меня интересует эффективное функциональное / простое решение, если нет.
Учитывая последовательность кортежей ("a" -> 1, "b" ->2, "c" -> 3)
, я хочу превратить это в карту. Это простое использование TraversableOnce.toMap
. Но я хочу потерпеть неудачу в этой конструкции, если результирующая карта «будет содержать противоречие», то есть разные значения, присвоенные одному и тому же ключу. Как в последовательности ("a" -> 1, "a" -> 2)
. Но дубликаты должны быть разрешены.
В настоящее время у меня есть этот (очень важный) код:
def buildMap[A,B](in: TraversableOnce[(A,B)]): Option[Map[A,B]] = {
val map = new HashMap[A,B]
val it = in.toIterator
var fail = false
while(it.hasNext){
val next = it.next()
val old = map.put(next._1, next._2)
fail = old.isDefined amp;amp; old.get != next._2
}
if(fail) None else Some(map.toMap)
}
Побочный вопрос
Действительно ли необходим final toMap
? Я получаю ошибку типа при ее пропуске, но я думаю, что это должно сработать. Реализация toMap
создает новую карту, которой я хочу избежать.
Комментарии:
1. Как вы хотите, чтобы выглядел сбой? (например, вы хотите, чтобы была возвращена опция [Map]?)
Ответ №1:
Как всегда при работе с Seq[A]
оптимальным решением, производительность зависит от конкретного типа коллекции. Общим, но не очень эффективным решением было бы свернуть Option[Map[A,B]]
:
def optMap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] =
in.iterator.foldLeft(Option(Map[A,B]())) {
case (Some(m),e @ (k,v)) if m.getOrElse(k, v) == v => Some(m e)
case _ => None
}
Если вы ограничитесь использованием List[A,B]
s, оптимизированная версия будет:
@tailrec
def rmap[A,B](in: List[(A,B)], out: Map[A,B] = Map[A,B]()): Option[Map[A,B]] = in match {
case (e @ (k,v)) :: tail if out.getOrElse(k,v) == v =>
rmap(tail, out e)
case Nil =>
Some(out)
case _ => None
}
Кроме того, менее идиоматичная версия, использующая изменяемые карты, может быть реализована следующим образом:
def mmap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] = {
val dest = collection.mutable.Map[A,B]()
for (e @ (k,v) <- in) {
if (dest.getOrElse(k, v) != v) return None
dest = e
}
Some(dest.toMap)
}
Комментарии:
1. Я думаю, использование getOrElseUpdate может сделать ваш третий код немного короче.
2. Ваш «менее идиоматичный» вариант кажется мне значительно более понятным. Мне было бы интересно услышать от читателей с большим опытом функционального программирования, чем у меня, найдут ли они, что разобраться с тем, что делает ваш первый, проще, чем делать то же самое для последнего…
3. @ziggystar, короче, но побочный эффект в условии означает менее понятный (для меня, в любом случае)
Ответ №2:
Вот решение, которое не приводит к сбоям (если создание всей карты, а затем удаление ее допустимо):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val m = s.toMap
if (m.size == s.length) Some(s) else None
}
Вот изменяемое решение без сбоев (отключается при обнаружении ошибки):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val h = new collection.mutable.HashMap[A,B]
val i = s.iterator.takeWhile(x => !(h contains x._1)).foreach(h = _)
if (h.size == s.length) Some(h) else None
}
И вот неизменяемое безотказное решение:
def uniqueMap[A,B](s: Seq[(A,B)]) = {
def mapUniquely(i: Iterator[(A,B)], m: Map[A,B]): Option[Map[A,B]] = {
if (i.hasNext) {
val j = i.next
if (m contains j._1) None
else mapUniquely(i, m j)
}
else Some(m)
}
mapUniquely(s.iterator, Map[A,B]())
}
Редактировать: и вот решение, использующее put
для ускорения (надеюсь):
def uniqueMap[A,B](s: Seq[(A,B)]) = {
val h = new collection.mutable.HashMap[A,B]
val okay = s.iterator.forall(x => {
val y = (h put (x._1,x._2))
y.isEmpty || y.get == x._2
})
if (okay) Some(h) else None
}
Редактировать: теперь протестировано, и это работает в ~ 2 раза быстрее при вводе (возвращает true), чем Мориц или мое простое решение.
Комментарии:
1. Я хочу потерпеть неудачу только при противоречивых дубликатах, а не при точных дубликатах. Тем не менее, очень хороший ответ, и я могу легко адаптировать его к моему случаю. Мне нравится, как вы используете takeWhile и foreach в нелогичной последовательности. 🙂 Я буду придерживаться второго предложения по соображениям производительности.
2. @ziggystar — Моя ошибка. Замените
h contains x._1
наh.get(x._1).map(_ == x._2).getOrElse(true)
или что-то подобное, и все будет готово.3. Адаптация не так проста. Проблема в том, что я, вероятно, захочу использовать HashMap.put, который возвращает старый элемент, который я затем могу сравнить с новым. Но при использовании вашего решения это невозможно. Мне пришлось бы добавить элемент, а затем проверить условие, выполнив второй запрос к карте.
4. @ziggystar — Придирчивый, придирчивый! Хорошо, я обновлю ответ, который работает там.
Ответ №3:
Scala 2.9 уже близко, так почему бы не воспользоваться методом комбинаций (вдохновленным ответом Морица):
def optMap[A,B](in: List[(A,B)]) = {
if (in.combinations(2).exists {
case List((a,b),(c,d)) => a == c amp;amp; b != d
case _ => false
}) None else Some(in.toMap)
}
scala> val in = List(1->1,2->3,3->4,4->5,2->3)
in: List[(Int, Int)] = List((1,1), (2,3), (3,4), (4,5), (2,3))
scala> optMap(in)
res29: Option[scala.collection.immutable.Map[Int,Int]] = Some(Map(1 -> 1, 2 -> 3, 3 -> 4, 4 -> 5))
scala> val in = List(1->1,2->3,3->4,4->5,2->3,1->2)
in: List[(Int, Int)] = List((1,1), (2,3), (3,4), (4,5), (2,3), (1,2))
scala> optMap(in)
res30: Option[scala.collection.immutable.Map[Int,Int]] = None
Комментарии:
1. Вау, это удивительно неэффективно.
2. Добро пожаловать в Scala, язык, наиболее простой для написания УДИВИТЕЛЬНО неэффективного кода с минимально возможным объемом
Ответ №4:
Вы также можете использовать gourpBy следующим образом:
val pList = List(1 -> "a", 1 -> "b", 2 -> "c", 3 -> "d")
def optMap[A,B](in: Iterable[(A,B)]): Option[Map[A,B]] = {
Option(in.groupBy(_._1).map{case(_, list) => if(list.size > 1) return None else list.head})
}
println(optMap(pList))
По эффективности это не уступает вышеупомянутым решениям.
На самом деле, если вы изучите реализацию gourpBy, вы увидите, что она очень похожа на некоторые из предложенных решений.