Подтвердить что ты не робот

Избегание случайного удаления дубликатов при сопоставлении набора

Мне действительно нравятся функциональные концепции программирования, но я был укушен в двух отдельных случаях сейчас по той же самой игре, при сопоставлении с коллекцией, которая является Set (т.е. автоматически удаляет дубликаты). Проблема заключается в том, что после преобразования элементов такого набора выходной контейнер также является множеством, и поэтому удаляет любые дубликаты преобразованного вывода.

Очень короткий сеанс REPL, чтобы проиллюстрировать проблему:

scala> case class Person(name: String, age: Int)
defined class Person

scala> val students = Set(Person("Alice", 18), Person("Bob", 18), Person("Charles", 19))
students: scala.collection.immutable.Set[Person] = Set(Person(Alice,18), Person(Bob,18), Person(Charles,19))

scala> val totalAge = (students map (_.age)).sum
totalAge: Int = 37

Я бы, конечно, ожидал, что общий возраст будет 18 + 18 + 19 = 55, а потому, что ученики были сохранены в Set, так же как и их возраст после сопоставления, поэтому один из 18 исчез раньше возрасты были суммированы.

В реальном коде это часто более коварно и сложнее обнаружить, особенно если вы пишете код утилиты, который просто берет Traversable и/или использует вывод методов, объявленных для возврата Traversable (реализация который представляет собой набор). Мне кажется, что эти ситуации почти невозможно обнаружить надежно, пока/если они не проявятся как ошибка.

Итак, есть ли какие-либо рекомендации, которые уменьшат мою подверженность этой проблеме? Нехорошо думать о map -переходе над общим Traversable как концептуально трансформируя каждый элемент на место, а не добавляя преобразованные элементы в свою очередь к некоторой новой коллекции? Должен ли я называть .toStream на все перед сопоставлением, если я хочу сохранить эту ментальную модель?

Приветствуются любые советы/рекомендации.

Обновление. Большинство ответов до сих пор были сосредоточены на механике включения дубликатов в сумму. Меня больше интересует практика, связанная с написанием кода в общем случае - вы просверлили себя, чтобы всегда вызывать toList в каждой коллекции до вызова map? Вы старательно проверяете конкретные классы всех коллекций в своем приложении, прежде чем вызывать методы на них? Etc.

Фиксирование чего-то, что уже было идентифицировано как проблема, тривиально - трудная часть предотвращает проникновение этих ошибок в первую очередь.

4b9b3361

Ответ 1

Для этой цели вы можете использовать scalaz foldMap, поскольку она работает на что угодно для которого существует Foldable класс. Использование в вашем случае будет выглядеть так:

persons foldMap (_.age)

Подпись foldMap выглядит следующим образом:

trait MA[M[_], A] {
  val value: M[A]

  def foldMap[B](f: A => B)(implicit f: Foldable[M], m: Monoid[B])
}

Итак, до тех пор, пока у вас есть некоторая коллекция CC[A], где CC можно сложить (т.е. пройденную), функцию из A => B, где B - моноид, вы можете скопировать результат.

Ответ 2

Как не перетаскивать дополнительные изображения в изображение:

(0 /: students) { case (sum, s) => sum + s.age }

Ответ 3

Вы можете breakOut тип коллекции

scala> import collection.breakOut
import collection.breakOut

scala> val ages = students.map(_.age)(breakOut): List[Int]
ages: List[Int] = List(18, 18, 19)

Затем вы можете суммировать как ожидалось

Основываясь на обновлении вопроса, наилучшей практикой предотвращения этих типов ошибок является хорошее покрытие unit test с репрезентативными данными вместе с разумным API в сочетании со знанием того, как компилятор scala поддерживает типы источников через map/для генераторов и т.д. Если вы возвращаете набор чего-то, вы должны сделать это очевидным, так как возврат Collection/Traversable скрывает соответствующую деталь реализации.

Ответ 4

Вы можете использовать методы toIterable или toList, чтобы сначала преобразовать набор в другую структуру данных. http://www.scala-lang.org/api/current/scala/collection/immutable/Set.html

(Обратите внимание, что toIterable может возвращать любой Iterable, хотя ссылочная реализация не будет, согласно связанной документации. @Debilski сообщает мне в комментариях, что она тем не менее возвращает Set.)

Ответ 5

Если вы обнаружите, что несколько раз сталкиваетесь с одной и той же ошибкой, ваша первая проблема - не ошибка, а скорее повторение. map().sum является достаточно распространенным вариантом использования (особенно в контексте анализа данных), чтобы заслужить его собственный метод на Traversable. Из моего личного, никогда не идущего-никуда-без-меняющегося класса сутенеров.

  implicit def traversable2RichTraversable[A](t: Traversable[A]) = new {
///many many methods deleted

    def sumOf[C: Numeric](g: A => C): C = t.view.toList.map(g).sum

///many many more methods deleted

}

(То, что .view может не понадобиться, но не может повредить.)

Ответ 6

Неуклюжий, но возможно более быстрый способ его преобразования (по сравнению с явным toList/toSeq) будет использовать collection.breakOut (дополнительную информацию) с тип описи

(students map (_.age))(collection.breakOut) : Seq[Int]