【问题标题】:Split a list into sublist based on element types in scala根据scala中的元素类型将列表拆分为子列表
【发布时间】:2021-08-20 19:54:22
【问题描述】:

如何根据元素类型将列表拆分为子列表?

简而言之,给定:

trait Drink
final case object Coke extends Drink
final case object Pepsi extends Drink

val drinks = List(Coke,Coke,Pepsi,Coke,Pepsi,Pepsi)

我想要:

List( List(Coke,Coke), List(Pepsi), List(Coke), List(Pepsi, Pepsi) )

【问题讨论】:

  • 您想要通用的东西,还是对特定类型集的解决方案满意?另外,它们总是案例对象还是也可能有案例类? - 最后,你尝试了什么?
  • @LuisMiguelMejíaSuárez 这组特殊的类型是无关紧要的。真实世界将是案例类。
  • 这些案例类将是参数/通用的吗?喜欢final case class Foo[T](data: T) extends Bar 吗?如果是这样,你有List(Foo(1), Foo("3")) 这两个应该在同一个组还是在不同的组?
  • 是的,当然,我只是没有将它们参数化以简化问题
  • 那么,由于类型擦除,您的问题基本上无法解决。 - 您有四个选择:1. 简化数据类型,以避免类型擦除 - 2. 仅依赖类测试,这意味着 Foo(1)Foo("3") 将在同一组中 - 3. 使用TypeTags 但这需要创建 List 的人使用其 TypeTag 压缩每个元素。 - 4. 与上一个类似,创建自己的Type ADT 并在创建List时使用Type 压缩每个元素。

标签: list scala


【解决方案1】:
    drinks.foldRight[List[List[Drink]](List.empty) {
       case (next, (l@(last :: _) :: tail) if next.getClass == last.getClass => 
          (next :: l)::tail
       case (next, rest) => List(next) :: rest
    }

如果类型是参数化的,您将需要TypeTags,正如评论中提到的那样......但在这一点上,向类本身添加一个方法真的会更容易......就像:

    class Drink[T: ClassTag] {
       def typeId = s"Drink of ${classTag[T].runtimeClass.getName}"
    }

然后您可以只比较这些类型 ID 而不是实际的类。

【讨论】:

    【解决方案2】:
    drinks.foldRight(List.empty[List[Drink]]){
      case (c:Coke.type, ((hd:Coke.type)::tl)::acc)  => (c::hd::tl)::acc
      case (p:Pepsi.type,((hd:Pepsi.type)::tl)::acc) => (p::hd::tl)::acc
      case (d, acc) => List(d)::acc
    }
    

    有些冗长,部分原因是它们是案例对象。

    【讨论】:

      【解决方案3】:

      您可以使用如下的短尾递归函数。

      这个想法是使用next 来存储“下一个要附加到结果的饮品列表”,并使用acc 来将这些饮品列表累积到饮品列表中。

      基本情况是一个空列表,其中返回结果。否则,下一个饮品与下一个子列表匹配(将其添加到此子列表中),或者不匹配(将子列表添加到结果中并使用新饮品开始一个新的子列表)。

      请注意,:+ 是一个 List 方法,它返回一个附加了指定项的新 List。

      @tailrec
      def get(list:List[Drink],
              next:List[Drink]=List(),
              acc: List[List[Drink]]=List()): List[List[Drink]] =
        list match {
          case Nil => acc :+ next   // dump final results
          case head :: tail =>
            if (next.isEmpty || next.head.getClass == head.getClass) get(tail, next :+ head, acc)
            else get(tail, List(head), acc :+ next)
        }
      
      
      println(get(drinks))
      

      结果:

      List(List(Coke, Coke), List(Pepsi), List(Coke), List(Pepsi, Pepsi))
      

      注意,注意到 jwvh 也有一个正确的答案,用正确的模式匹配而不是这些条件。在 List 上使用 head 方法可能是不安全的(或者编译器难以确定安全性),但这种方法可能更简洁,尤其是在存在多种 Drink 时。

      如果你想避免直接使用head,你可以这样写,我觉得比较混乱:

      ...
            if (next.headOption.map(h => h.getClass == head.getClass).getOrElse(true)) get(tail, next :+ head, acc)
      ...
      

      【讨论】:

        【解决方案4】:

        为什么不在您的列表中只使用groupBy

        val rows = drinks.groupBy(x => x).values.toList
        

        可以测试here

        【讨论】:

        • 这不是 OP 要求的,它甚至只适用于 case object
        猜你喜欢
        • 1970-01-01
        • 2011-01-17
        • 2015-05-19
        • 1970-01-01
        • 2018-12-22
        • 2018-10-24
        • 2021-11-30
        • 1970-01-01
        • 2021-08-10
        相关资源
        最近更新 更多