Наиболее распространенные причины проблем в этой области - равенство тестовых наборов и т. Д. -
hashCode
не соответствует equals
- Ваши значенияне являются стабильными (поэтому предыдущий
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 также делают это правильно, поэтомуу нас осталось три варианта
- Вы переопределили
equals
, но не hashCode
для соответствия - Ваши значения нестабильны
- Существует ошибка иНесовпадение примитива 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 */)
Это действительно важно, если вы делаете такие вещи, как файловый ввод-вывод, для сопоставления ваших значений (например, если ваши значения являются именами файлов и вы отображаете их содержимое)и вы не хотите читать файл снова и снова.
Но ваш случай - когда вы назначаете случайные значения - это другой случай, когда важно выбрать одну копию, а не воссоздатьзначения снова и снова.