Объединение элементов списка классов дел - PullRequest
1 голос
/ 30 октября 2019

У меня есть следующий класс дел:

case class GHUser(login:String, contributions:Option[Int])

И список таких элементов:

val list = List(
    List(GHUser("a", Some(10)), GHUser("b", Some(10))), List(GHUser("b", Some(300)))
  ).flatten

А теперь я хотел бы объединить все элементы так, чтобы все вклады были добавленывместе для одного и того же пользователя. Сначала я подумал, что могу применить Monoid к своему классу дел, например:

trait Semigroup[A] {
  def combine(x: A, y: A): A
}

trait Monoid[A] extends Semigroup[A] {
  def empty: A
}

case class GHUser(login: String, contributions: Option[Int])

object Main extends App {
  val ghMonoid: Monoid[GHUser] = new Monoid[GHUser] {
    def empty: GHUser = GHUser("", None)

    def combine(x: GHUser, y: GHUser): GHUser = {
      x match {
        case GHUser(_, None) => GHUser(y.login, y.contributions)
        case GHUser(_, Some(xv)) =>
          y match {
            case GHUser(_, None) => GHUser(x.login, x.contributions)
            case GHUser(_, Some(yv)) => GHUser(x.login, Some(xv + yv))
          }
      }
    }
  }


  val list = List(
    List(GHUser("a", Some(10)), GHUser("b", Some(10))), List(GHUser("b", Some(300)))
  ).flatten

  val b = list.groupBy(_.login)
  val c = b.mapValues(_.foldLeft(ghMonoid.empty)(ghMonoid.combine))

  println(c.valuesIterator mkString("\n"))
  // GHUser(a,Some(10))
  // GHUser(b,Some(310))
}

И это работает, но я чувствую, что не следую Законам Monoid, так как требуется, чтобы все пользователи имелитот же login (По этой причине я сделал groupBy звонок.

Есть ли более чистое решение?

Обновление

Перечитывая мой вопрос, кажется, что я делаюне хочу Monoid а Semigroup, я прав?

Ответы [ 2 ]

2 голосов
/ 30 октября 2019

Вот простое решение:

list.groupBy(_.login).map{
  case (k, v) =>
    GHUser(k, Some(v.flatMap(_.contributions).sum))
}

Это даст Some(0) для пользователей без участия. Если вы хотите None, то в этом случае это выглядит более уродливо:

list.groupBy(_.login).map{
  case (k, v) =>
    val c = v.flatMap(_.contributions)
    GHUser(k, c.headOption.map(_ => c.sum))
}
2 голосов
/ 30 октября 2019

groupMapReduce() (Scala 2.13) обрабатывает большую часть того, что вам нужно.

list.groupMapReduce(_.login)(_.contributions){case (a,b) => a.fold(b)(n => Some(n+b.getOrElse(0)))}
    .map(GHUser.tupled)
//res0 = List(GHUser(a,Some(10)), GHUser(b,Some(310)))

Часть Reduce немного запутанная, но она выполняет свою работу.

...