подмножество по сравнению с полным содержит - PullRequest
3 голосов
/ 25 марта 2011

Считай, у меня есть:

case class X(...)
val xs: Seq[X] = ... // some method result
val ys: Seq[X] = ... // some other method result

Пока имеет место следующее:

xs.distinct.sameElements(xs) // true
ys.distinct.sameElements(ys) // true

Я сталкиваюсь:

xs forall(ys contains _)    // true
xs.toSet subsetOf ys.toSet  // false

Почему? Я имею в виду, что ясно, что Set из Seq выбирает случайные элементы в случае дубликатов, но дубликатов не существует из-за "(...).distinct.sameElements(...)".

Мне, безусловно, нужно более глубокое понимание вида проверки на равенство ...

EDIT:

После долгих поисков я нашел проблему и сжал ее до следующего:

Мои элементы не одинаковы, однако я должен поближе взглянуть, почему distinct.sameElements не жалуется. Но между тем возник новый вопрос:

Учтите это:

val rnd = scala.util.Random
def int2Label(i: Int) = "[%4s]".format(Seq.fill(rnd.nextInt(4))(i).mkString)
val s = Seq(1,2,3,4)

// as expected :
val m1: Map[Int,String] = s.map(i => (i,int2Label(i))).toMap
println(m1) // Map(5 -> [ 555], 1 -> [    ], 2 -> [  22], 3 -> [    ])
println(m1) // Map(5 -> [ 555], 1 -> [    ], 2 -> [  22], 3 -> [    ])

// but accessing m2 several times yields different results. Why?
val m2: Map[Int,String] = s.map(i => (i,i)).toMap.mapValues { int2Label(_) }
println(m2) // Map(5 -> [   5], 1 -> [  11], 2 -> [  22], 3 -> [ 333])
println(m2) // Map(5 -> [  55], 1 -> [  11], 2 -> [    ], 3 -> [    ])

Так что мои элементы в моих первых последовательностях не одинаковы, потому что они зависят от m2 -конструктуры, и поэтому каждый раз, обращаясь к ним, они различаются.

Мой новый вопрос: почему m2 ведет себя как функция в отличие от m1, хотя оба являются неизменяемыми картами. Это не интуитивно для меня.

1 Ответ

3 голосов
/ 26 марта 2011

Наиболее распространенные причины проблем в этой области - равенство тестовых наборов и т. Д. -

  1. hashCode не соответствует equals
  2. Ваши значенияне являются стабильными (поэтому предыдущий hashCode не согласуется с текущим equals)

Причина заключается в том, что distinct и toSet используют хеш-коды для построения множеств, тогда какcontains просто перебирает коллекцию с exists:

xs forall(ys contains _) == xs forall (x => ys exists (y => x==y) )

Это усложняется тем фактом, что многие наборы не начинают использовать хэш-коды, пока они не превышают некоторый минимальный размер(обычно 4), поэтому вы не всегда замечаете это при тестировании.Но давайте докажем это самим себе:

class Liar(s: String) {
  override def equals(o: Any) = o match {
    case l: Liar => s == l.s
    case _ => _
  }
  // No hashCode override!
}
val strings = List("Many","song","lyrics","go","na","na","na","na")
val lies = strings.map(s => new Liar(s))
val truly_distinct = lies.take(5)
lies.length          // 8
lies.distinct.length // 8!
lies.toSet.size      // 8!
lies forall( truly_distinct contains _ )   // True, because it's true
lies.toSet subsetOf truly_distinct.toSet   // False--not even the same size!

Хорошо, теперь мы знаем, что для большинства этих операций совпадение hashCode и equals - это хорошо.

Предупреждение: в Java несоответствия часто случаются даже с примитивами:

new java.lang.Float(1.0) == new java.lang.Integer(1)                       // True
(new java.lang.Float(1.0)).hashCode == (new java.lang.Integer(1)).hashCode // Uh-oh

, но Scala теперь, по крайней мере, ловит это (надеюсь каждый раз):

(new java.lang.Float(1.0)).## == (new java.lang.Integer(1)).##   // Whew

Классы Case также делают это правильно, поэтомуу нас осталось три варианта

  1. Вы переопределили equals, но не hashCode для соответствия
  2. Ваши значения нестабильны
  3. Существует ошибка иНесовпадение примитива hashCode в Java-оболочке возвращается, чтобы укусить вас

Первый достаточно прост.

Второй, похоже, ваша проблема, и это связано с тем, что mapValues фактически создает представление исходной коллекции, а не новую коллекцию.(filterKeys делает то же самое.) Лично я думаю, что это сомнительный выбор дизайна, поскольку обычно, когда у вас есть представление и вы хотите создать его конкретный экземпляр, вы .force его.Но карты по умолчанию не имеют .force, потому что они не понимают, что они могут быть представлениями.Поэтому вам нужно прибегнуть к таким вещам, как

myMap.map{ case (k,v) => (k, /* something that produces a new v */) }
myMap.mapValues(v => /* something that produces a new v */).view.force
Map() ++ myMap.mapValues(v => /* something that produces a new v */)

Это действительно важно, если вы делаете такие вещи, как файловый ввод-вывод, для сопоставления ваших значений (например, если ваши значения являются именами файлов и вы отображаете их содержимое)и вы не хотите читать файл снова и снова.

Но ваш случай - когда вы назначаете случайные значения - это другой случай, когда важно выбрать одну копию, а не воссоздатьзначения снова и снова.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...