Как преобразовать входные данные в следующий формат? - группа по

У меня есть следующие входные данные для функции в фрагменте кода scala, который я пишу:

List(
  (1,SubScriptionState(CNN,ONLINE,Seq(12))), 
  (1,SubScriptionState(SKY,ONLINE,Seq(12))), 
  (1,SubScriptionState(FOX,ONLINE,Seq(12))), 
  (2,SubScriptionState(CNN,ONLINE,Seq(12))), 
  (2,SubScriptionState(SKY,ONLINE,Seq(12))), 
  (2,SubScriptionState(FOX,ONLINE,Seq(12))), 
  (2,SubScriptionState(CNN,OFFLINE,Seq(13))), 
  (2,SubScriptionState(SKY,ONLINE,Seq(13))), 
  (2,SubScriptionState(FOX,ONLINE,Seq(13))), 
  (3,SubScriptionState(CNN,OFFLINE,Seq(13))), 
  (3,SubScriptionState(SKY,ONLINE,Seq(13))), 
  (3,SubScriptionState(FOX,ONLINE,Seq(13)))
)

SubscriptionState здесь просто класс case:

case class SubscriptionState(channel: Channel, state: ChannelState, subIds: Seq[Long])

Я хочу преобразовать его в это:


Map(
  1 -> Map(
        SubScriptionState(SKY,ONLINE,Seq(12)) -> 1, 
        SubScriptionState(CNN,ONLINE,Seq(12)) -> 1, 
        SubScriptionState(FOX,ONLINE,Seq(12)) -> 1),

  2 -> Map(
        SubScriptionState(SKY,ONLINE,Seq(12,13)) -> 2, 
        SubScriptionState(CNN,ONLINE,Seq(12)) -> 1, 
        SubScriptionState(FOX,ONLINE,Seq(12,13)) -> 2, 
        SubScriptionState(CNN,OFFLINE,Seq(13)) -> 1),  

  3 -> Map(
        SubScriptionState(SKY,ONLINE,Seq(13)) -> 1, 
        SubScriptionState(FOX,ONLINE,Seq(13)) -> 1, 
        SubScriptionState(CNN,OFFLINE,Seq(13)) -> 1)
)

Как мне это сделать в scala?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
55
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вот мой подход к проблеме. Я думаю, что это может быть не идеальное решение, но оно работает так, как вы ожидаете.

  val result: Map[Int, Map[SubscriptionState, Int]] = list
    .groupBy(_._1)
    .view
    .mapValues { statesById =>
      statesById
        .groupBy { case (_, subscriptionState) => (subscriptionState.channel, subscriptionState.state) }
        .map { case (_, groupedStatesById) =>
          val subscriptionState = groupedStatesById.head._2 // groupedStatesById should contain at least one element
          val allSubIds = groupedStatesById.flatMap(_._2.subIds)
          val updatedSubscriptionState = subscriptionState.copy(subIds = allSubIds)
          updatedSubscriptionState -> allSubIds.size
        }
    }.toMap

Это «простое» решение с использованием groupMap и groupMapReduce

list
  .groupMap(_._1)(_._2)
  .view
  .mapValues{ 
    _.groupMapReduce(ss => (ss.channel, ss.state))(_.subIds)(_ ++ _)
     .map{case (k,v) => SubScriptionState(k._1, k._2, v) -> v.length}
  }
  .toMap

groupMap преобразует данные в Map[Int, List[SubScriptionState]], а mapValues преобразует каждый List в соответствующий Map. (Обертки view и toMap делают mapValues более эффективным и безопасным.)

groupMapReduce преобразует List[SubScriptionState] в Map[(Channel, ChannelState), List[SubId]].

map на этом внутреннем Map жонглирует этими значениями, чтобы сделать Map[SubScriptionState, Int] по мере необходимости.

Я не понимаю, какова цель внутреннего Map. Значение представляет собой длину поля subIds, поэтому его можно получить непосредственно из ключа, а не искать его в Map

Попытка использования foldLeft:

list.foldLeft(Map.empty[Int, Map[SubscriptionState, Int]]) { (acc, next) =>
  val subMap = acc.getOrElse(next._1, Map.empty[SubscriptionState, Int])
  val channelSub = subMap.find { case (sub, _) => sub.channel == next._2.channel && sub.state == next._2.state }
  acc + (next._1 -> channelSub.fold(subMap + (next._2 -> next._2.subIds.length)) { case (sub, _) =>
    val subIds = sub.subIds ++ next._2.subIds
    (subMap - sub) + (sub.copy(subIds = subIds) -> subIds.length)
  })
}

Я заметил, что count не используется при сворачивании и может быть рассчитано с помощью storeId. Кроме того, поскольку storeIds может варьироваться, внутренний Map довольно бесполезен, так как вам придется использовать find вместо get для извлечения значений из Map. Итак, если у вас есть контроль над вашими ADT, вы можете использовать промежуточный ADT, например:

case class SubscriptionStateWithoutIds(channel: Channel, state: ChannelState)

то вы можете переписать свой foldLeft следующим образом:

list.foldLeft(Map.empty[Int, Map[SubscriptionStateWithoutIds, Seq[Long]]]) { (acc, next) =>
  val subMap = acc.getOrElse(next._1, Map.empty[SubscriptionStateWithoutIds, Seq[Long]])
  val withoutId = SubscriptionStateWithoutIds(next._2.channel, next._2.state)
  val channelSub = subMap.get(withoutId)
  acc + (next._1 -> (subMap + channelSub.fold(withoutId -> next._2.subIds) { seq => withoutId -> (seq ++ next._2.subIds) }))
}

Самым большим преимуществом промежуточного ADT является то, что вы можете получить более чистую groupMapReduce версию:

list.groupMap(_._1)(sub => SubscriptionStateWithoutIds(sub._2.channel, sub._2.state) -> sub._2.subIds)
  .map { case (key, value) => key -> value.groupMapReduce(_._1)(_._2)(_ ++ _) }

Другие вопросы по теме