简体   繁体   English

根据 scala 中的元素类型将列表拆分为子列表

[英]Split a list into sublist based on element types in scala

How to split a list into sublist based on element types?如何根据元素类型将列表拆分为子列表?

In short, given:简而言之,鉴于:

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

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

I want:我想:

List( List(Coke,Coke), List(Pepsi), List(Coke), List(Pepsi, Pepsi) )
    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
    }

If types are parametric you'll need TypeTags as mentioned in the comment... but at that point, it's really going to be easier to just add a method to the class itself... something like:如果类型是参数化的,您将需要注释中提到的TypeTags ......但在这一点上,向 class 本身添加一个方法真的会更容易......类似于:

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

Then you can just compare these type ids rather than actual classes.然后你可以只比较这些类型 id 而不是实际的类。

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
}

Somewhat verbose partly due to them being case objects.有点冗长,部分原因是它们是案例对象。

You can use a short tail-recursive function such as the following.您可以使用短尾递归 function,如下所示。

The idea is to use next to store the "next List of Drinks to append to the result" and acc to accumulate these Lists of Drinks, into a List of List of Drinks.想法是使用next将“下一个饮品列表到 append 到结果”并acc将这些饮品列表累积到饮品列表中。

The base case is an empty list, in which the results are returned.基本情况是一个空列表,其中返回结果。 Otherwise, either the next drink matches the next sublist (add it to this sublist), or it doesn't (add the sublist to the result and start a new sublist with the new drink).否则,下一个饮料与下一个子列表匹配(将其添加到此子列表),或者不匹配(将子列表添加到结果并使用新饮料开始一个新的子列表)。

Note that :+ is a List method which gives back a new List with the specified item appended.请注意:+是一个 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))

Result:结果:

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

Note, noticed that jwvh has a correct answer too, with proper pattern matching instead of these conditionals.注意,注意到 jwvh 也有一个正确的答案,使用正确的模式匹配而不是这些条件。 Using the head method on a List can be unsafe (or hard for the compiler to determine the safety), but this approach may be more concise especially if there are many kinds of Drink.在 List 上使用head方法可能是不安全的(或者编译器很难确定安全性),但这种方法可能更简洁,尤其是在存在多种 Drink 时。

If you want to avoid using head directly, you can write this, which I find more confusing:如果你想避免直接使用head ,你可以这样写,我觉得这更令人困惑:

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

why don't use just groupBy on your list?为什么不在您的列表中使用groupBy

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

Can test here可以在这里测试

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM