Как улучшить группировку по заголовкам

#scala #collections

#scala #Коллекции

Вопрос:

Предположим, у меня есть фрагмент кода, подобный этому:

 case class A(xs: Seq[Int])

def groupByHead(as: Seq[A]): Map[Int, Seq[A]] = 
  as.filter(_.xs.nonEmpty).groupBy(_.xs.head)
  

groupByHead работает нормально, но мне не нравится, groupBy(_.xs.head) потому head что это небезопасно. Как бы вы это улучшили?

Комментарии:

1. .head не является небезопасным, поскольку вы знаете, что _.xs непустой (мне не нравится довольно распространенное утверждение о том, что x.head небезопасен. Это небезопасно, только если x может быть пустым, и часто можно легко показать, что этого не может быть)

2. @TheArchetypalPaul Возможно, вы лично знаете, что это безопасно, но компилятор этого не делает, и связь между notEmpty и head может быть не очевидна для всех. Кроме того, утверждение о том, что что-то небезопасно при любых обстоятельствах, безусловно, является самим определением «небезопасного».

3. ‘Кроме того, утверждение о том, что что-то небезопасно при любых обстоятельствах, безусловно, является самим определением «небезопасного»‘ Не с моей точки зрения, нет. Выходить на прогулку небезопасно при любых обстоятельствах (например, на активном vocano), но утверждать, что по определению это делает ходьбу «небезопасной», немного странно. Контекст имеет значение.

4. «связь между NotEmpty и head может быть не очевидна для всех». Я не хочу, чтобы в моей команде был кто-то, кто не считает, что непустая последовательность подразумевает, что у нее есть очевидный первый элемент. Но YMMV..

5. Так получилось, что я достаточно осведомлен о том, что может сделать формальная проверка (я читал книги по денотационной семантике для развлечения). Это действительно, как вы говорите, вопрос перспективы, и я думаю, я мог бы в равной степени утверждать, что вы не цените мою. Но, в конце концов, давайте поспорим за пивом, встретим ли мы когда-нибудь f2f.

Ответ №1:

В принципе, вы могли бы collect использовать только те A , где последовательность непустая, и с этого момента преобразовывать ее в значение, которое одновременно является конструктивным доказательством непустоты последовательности:

 def groupByHead(as: Seq[A]) = 
  as.collect { case a @ A(h  : t) => (h, a) }.groupBy(_._1).mapValues(_.map(_._2))
  

но это выглядит немного искусственно. В данном конкретном случае «небезопасный» .head кажется меньшим злом, между filter и groupBy мало что может пойти не так.

Комментарии:

1. Спасибо. Я согласен, это выглядит немного сложнее, чем «небезопасное» решение.

2. «В принципе, вы могли бы собирать только те, в которых последовательность не является пустой», OP уже сделал — это то, что делает фильтр?

3. @TheArchetypalPaul Предложение на этом не заканчивается. Это вторая часть, которая отличается от того, что OP делал изначально: преобразование последовательностей «в значение, которое одновременно является конструктивным доказательством непустоты последовательности» , в данном конкретном случае, кортеж, содержащий саму последовательность и отдельно сохраненные head . Разница в том, что брать head небезопасно, тогда как брать первую проекцию кортежа — небезопасно.

4. Конечно, но, похоже, у этого нет преимуществ по сравнению с оригиналом OP. Сначала удалите все элементы, которые имеют пустую последовательность, затем сгруппируйте по результату. Вам не нужно это доказательство во второй части — фильтр (или сбор) уже обеспечил, чтобы последовательности не были пустыми. Итак, (IMO) это не улучшение, оно (немного) медленнее и (опять же IMO) менее понятно, что происходит.

5. @TheArchetypalPaul Преимущество этого в том, что он, очевидно , не использует никаких операций, которые могли бы вызвать какие-либо исключения. Проекция на первый компонент кортежа никогда ничего не выдает.

Ответ №2:

Как насчет этого:

 def groupByHead(as: Seq[A]): Map[Int, Seq[A]] = 
  as.groupBy(_.xs.headOption).collect{ case (Some(key), x) => key -> x }
  

Комментарии:

1. Спасибо. Это приятно, но я бы предпочел «небезопасную» версию.

2. Как всегда, было бы неплохо понять, почему это получило отрицательный результат (а другой ответ — нет)

3. @Tim Первый ответ получил total -1 и был удален… Не уверен, почему этот ответ здесь получил бы отрицательный результат, хотя я все еще думаю, что это лучшее предложение.