Предотвращение случайного удаления дубликатов при отображении набора - PullRequest
30 голосов
/ 03 апреля 2012

Мне действительно нравятся концепции функционального программирования, но теперь я был укушен в двух разных случаях одной и той же ошибкой, когда отображал коллекцию, которая оказывается 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? Пристально ли вы проверяете конкретные классы всех коллекций в вашем приложении, прежде чем вызывать на них методы? И т.д.

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

Ответы [ 6 ]

19 голосов
/ 03 апреля 2012

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

11 голосов
/ 03 апреля 2012

Как не перетаскивать лишние зависимости на картинку:

(0 /: students) { case (sum, s) => sum + s.age }
3 голосов
/ 03 апреля 2012

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

scala> import collection.breakOut
import collection.breakOut

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

Тогда вы можете сложить, как ожидалось

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

2 голосов
/ 03 апреля 2012

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

2 голосов
/ 03 апреля 2012

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

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

1 голос
/ 03 апреля 2012

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

(students map (_.age))(collection.breakOut) : Seq[Int]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...