按自定义逻辑对Scala列表进行分组

时间:2017-04-14 15:08:26

标签: scala list

我有一个自定义逻辑,可以按照第一个字母对名单进行分组,我可以通过以下方式实现:

val names = List("Adam", "Barbara", "Bob", "Charlie", "Damien", "Elaine", "Florence", "Gwen")
names.map{
  case x if x.startsWith("A") => (1, x)
  case x if x.startsWith("B") => (1, x)
  case x if x.startsWith("C") => (2, x)
  case x if x.startsWith("D") => (2, x)
  case x if x.startsWith("E") => (3, x)
  case x if x.startsWith("F") => (3, x)
  case default => (0, default)
}.groupBy(_._1)

逻辑可能会改变。例如,下次我可能想要将以A,F和G开头的所有名称分组到组1,或者我可以将所有字母添加到Z.更高级的逻辑是从Ad开始的前两个字母和组名称和Ba到第1组,在这种情况下,亚当和芭芭拉将属于同一组。

我想知道是否有更惯用的方法,我可以写startsWith次数较少。

Tanjin's answer开始,我进一步重构了groups

val groups = List(
      (List("A", "B"),1),
      (List("C", "D"),2),
      (List("E", "F"),3)
    ).flatMap{ case (l,i) => l.map((_, i)) }

4 个答案:

答案 0 :(得分:2)

我们可以尝试这一点 - 您需要做的就是改变您的groups

def categorize(groups: Seq[(String, Int)]) = (s: String) => {
  groups.find(category => category._1 == s.head.toString)
    .map(c => (c._2, s)).getOrElse((0, s))
}
val groups = Seq(("A", 1), ("B", 1), ("C", 2), ("D", 2), ("E", 3), ("F", 3))

names.map(categorize(groups)).groupBy(_._1)

答案 1 :(得分:1)

您只能使用<div> 1 <div> 2 <div>3</div> <div>4</div> <div>5 <div>6</div> <div> 7 <div>8</div> <div>9</div> </div> </div> </div> </div> <div> <span id="carot">|</span>10 </div>

执行此操作
groupBy

注意def groupBy[K](f: String => K): scala.collection.immutable.Map[K,List[String]] 将函数作为参数

Scala REPL

groupBy

答案 2 :(得分:1)

代码

    def categorize[A](names: List[String], prefixGroups: Seq[(A,Set[String])]): Map[Option[A],List[String]] = 
    names.groupBy(name => prefixGroups.find{case (group, prefixes) => prefixes.exists(name.startsWith)}.map(_._1))

    val result = categorize(List("Anna","Bob","Carla","Debbie","Eddie"), Seq(1 -> Set("A","B"), 2 -> Set("C","D")))

    println("all: " + result)
    println("only valid: " + result.filterKeys(_.isDefined).map{case (Some(k),v) => k -> v})

输出

all: Map(Some(1) -> List(Anna, Bob), None -> List(Eddie), Some(2) -> List(Carla, Debbie))
only valid: Map(1 -> List(Anna, Bob), 2 -> List(Carla, Debbie))

改变坦金的解决方案

  • 这些组以更简洁的格式Seq[(Key,Set[String])]给出,汇总了同一组的前缀。
  • 结果保留了无与伦比的名称。

答案 3 :(得分:1)

我参加派对有点晚了,但如果你愿意将你的群体存储略有不同,我想分享另一个好的解决方案。请注意,此版本仅在您检查第一个字母时才有效,但您也可以轻松更新它以便在其他情况下使用。

&#34;看看地图的力量&#34;

首先,我们将我们的群组作为Int的地图存储到一系列字符中,这些字符将成为我们的前缀。为清晰起见,我使用多行语法:

val groups = Map(
  1 -> Seq('A', 'B'),
  2 -> Seq('C', 'D'),
  3 -> Seq('E', 'F')
)

现在我们从字典中构建一个inverted index。这意味着我们可以使用地图中的值来交换键。它不完全交换,因为我们需要将每个前缀序列映射到多个元组:

val inverted = groups
  .flatMap { case (id, prefixes) => prefixes.map(_ -> id) } // Swap keys w/ values
  .withDefault(_ => 0)                                      // Set the default

最后,groupBy电话非常整洁:

names.groupBy(name => inverted(name(0)))

最后,我要补充说Map上的操作通常更快。但无论如何,如果你没有真正庞大的名单,你应该没办法。这种方法的另一个好处是你可以重复使用前两个地图:groupsinverted。因此,如果您正在检查多个名称列表,那么它反复重新计算的唯一内容是最后一次groupBy调用。

我希望这很有帮助,尽情享受!