【问题标题】:Split the list when duplicate is found scala找到重复项时拆分列表
【发布时间】:2016-08-31 17:29:10
【问题描述】:

我在 Scala 中有一个元素列表,我正在寻找一种在找到重复项时拆分列表的方法。

例如:List(x,y,z,e,r,y,g,a) 将转换为 List(List(x,y,z,e,r),List(y,g,a))List(x,y,z,x,y,z)List(x,y,z), List(x,y,z)List(x,y,z,y,g,x)List(x,y,z), List(y,g,x)

有没有比单独迭代和检查每个元素更有效的方法?

【问题讨论】:

  • 您是要在每个重复还是只在第一个处进行拆分?
  • 没有什么比迭代和检查更有效了。如何不查看“下一个”元素就知道它是否重复?
  • 我正在寻找每个重复项以拆分列表。最后我想要多个不包含重复项的列表
  • @Kratos 你应该更新你的问题,然后,最好用一个例子来说明你想用List(x,y,z,x,y,z)List(x,y,z,y,g,x)发生什么。
  • 谢谢。但这仍然是模棱两可的。 (a, b, c, c, d, e, a, f) 是否应导致 (a, b, c), (c, d, ,e, a, f) [任一列表中无重复项] 或 (a,b,c), (c, d, e), (a, f) [任何先前列表中均无重复项]

标签: list scala split duplicates


【解决方案1】:

快速而肮脏的O(n) 使用O(n) 额外内存:

import scala.collection.mutable.HashSet
import scala.collection.mutable.ListBuffer

val list = List("x", "y", "z", "e", "r", "y", "g", "a", "x", "m", "z")

var result = new ListBuffer[ListBuffer[String]]()
var partition = new ListBuffer[String]()

list.foreach { i => 
    if (partition.contains(i)) {
        result += partition
        partition = new ListBuffer[String]()
    }
    partition += i
}

if (partition.nonEmpty) {
    result += partition
}
result

ListBuffer(ListBuffer(x, y, z, e, r), ListBuffer(y, g, a, x, m, z))

【讨论】:

  • 这个列表工作正常。但它会检查列表中的每个出现,这意味着对于列表:“x”,“y”,“z”,“e”,“r”,“y”,“g”,“a”,“x ", "m", "z" 它将返回 ListBuffer(ListBuffer(x, y, z, e, r), ListBuffer(y, g, a), ListBuffer(x, m), ListBuffer(z))。当我希望它返回时: ListBuffer(ListBuffer(x, y, z, e, r), ListBuffer(y, g, a, x, m,z))
  • @Kratos 啊...我误解了原来的问题。您可以通过检查项目是否为lookup 而是在partition 中轻松修复它。我会编辑我的答案
  • 是的。我刚刚在第一个条件下的分区创建下添加了一个lookup.clear()。谢谢@vsminkov。你真的帮了大忙!
  • @Kratos 没问题!我已经完全放弃了lookup 并更新了我的答案
【解决方案2】:

此解决方案有一些注意事项:

  • 虽然我认为它比 O(n^2) 更好,但我并没有声称它是“性能”,这是蛮力。
  • 这是假设您在找到重复项时进行拆分,其中“重复项”表示“存在于先前拆分项中的内容”。我只检查最后一段来作弊。原因是我认为它澄清了如何使用foldLeft,这是一种很自然的方式。
  • 这里的一切都颠倒过来了,但保持秩序。这可以很容易地纠正,但会增加一个额外的 O(n)(累积)调用,并且实际上可能不需要(取决于您正在使用它做什么)。

代码如下:

def partition(ls: List[String]): List[ListSet[String]] = {
  ls.foldLeft(List(ListSet.empty[String]))((partitionedLists, elem:String) => {
    if(partitionedLists.head.contains(elem)) {
      ListSet(elem) :: partitionedLists
    } else {
      (partitionedLists.head + elem) :: partitionedLists.tail
    }
  })
}

partition(List("x","y","z","e","r","y","g","a"))
// res0: List[scala.collection.immutable.ListSet[String]] = List(ListSet(r, e, z, y, x), ListSet(a, g, y))

我使用ListSet 来获得Set 的好处和适合您的用例的排序。

foldLeft 是一个函数,它接受一个累加器值(在本例中为List(ListSet.empty[String]))并在它通过集合的元素时对其进行修改。如果我们像这里所做的那样将累加器构造成一个段列表,当我们完成时,它将拥有原始列表的所有有序段。

【讨论】:

  • 这样我将不得不依赖 listSet 来保持列表的顺序。实施虽然有效。谢谢。
【解决方案3】:

一个语句尾递归版本(但效率不高,因为列表中有contains

var xs = List('x','y','z','e','r','y','g','a') 

def splitAtDuplicates[A](splits: List[List[A]], right: List[A]): List[List[A]] = 
  if (right.isEmpty)// done
    splits.map(_.reverse).reverse
  else if (splits.head contains right.head) // need to split here
    splitAtDuplicates(List()::splits, right)
  else // continue building current sublist
    splitAtDuplicates((right.head :: splits.head)::splits.tail, right.tail)

使用Set 加快速度以跟踪我们迄今为止所看到的:

def splitAtDuplicatesOptimised[A](seen: Set[A], 
                                  splits: List[List[A]],
                                  right: List[A]): List[List[A]] = 
  if (right.isEmpty)
     splits.map(_.reverse).reverse 
  else if (seen(right.head))
     splitAtDuplicatesOptimised(Set(), List() :: splits, right)
  else
    splitAtDuplicatesOptimised(seen + right.head,
                              (right.head :: splits.head) :: splits.tail, 
                              right.tail)

【讨论】:

    【解决方案4】:

    您基本上需要使用查找表进行迭代。我可以为以下不可变和函数式 tailrec 实现提供帮助。

    import scala.collection.immutable.HashSet    
    import scala.annotation.tailrec
    
    val list = List("x","y","z","e","r","y","g","a", "x", "m", "z", "ll")
    
    def splitListOnDups[A](list: List[A]): List[List[A]] = {
      @tailrec
      def _split(list: List[A], cList: List[A], hashSet: HashSet[A], lists: List[List[A]]): List[List[A]] = {
        list match {
          case a :: Nil if hashSet.contains(a) => List(a) +: (cList +: lists)
          case a :: Nil => (a +: cList) +: lists
          case a :: tail if hashSet.contains(a) => _split(tail, List(a), hashSet, cList +: lists)
          case a :: tail => _split(tail, a +: cList, hashSet + a, lists)
        }
      }
    
      _split(list, List[A](), HashSet[A](), List[List[A]]()).reverse.map(_.reverse)
    }
    
    def splitListOnDups2[A](list: List[A]): List[List[A]] = {
      @tailrec
      def _split(list: List[A], cList: List[A], hashSet: HashSet[A], lists: List[List[A]]): List[List[A]] = {
        list match {
          case a :: Nil if hashSet.contains(a) => List(a) +: (cList +: lists)
          case a :: Nil => (a +: cList) +: lists
          case a :: tail if hashSet.contains(a) => _split(tail, List(a), HashSet[A](), cList +: lists)
          case a :: tail => _split(tail, a +: cList, hashSet + a, lists)
        }
      }
    
      _split(list, List[A](), HashSet[A](), List[List[A]]()).reverse.map(_.reverse)
    }
    
    splitListOnDups(list)
    // List[List[String]] = List(List(x, y, z, e, r), List(y, g, a), List(x, m), List(z, ll))
    
    splitListOnDups2(list)
    // List[List[String]] = List(List(x, y, z, e, r), List(y, g, a, x, m, z, ll))
    

    【讨论】:

    • 我认为是。让乐队继续演奏吧!
    • 如何更改函数,以便在找到重复项时进行拆分,但不保留以前找到的内容的历史记录?例如,在您的结果列表数组中,最后三个应该放在一起,因为它们不包含重复项。
    • 只需在递归调用中将hashSet 重置为空HashSet[A](),以防出现重复。请参阅splitListOnDups2 中的case a :: tail if hashSet.contains(a)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-20
    • 2020-10-16
    • 1970-01-01
    • 1970-01-01
    • 2015-04-24
    • 1970-01-01
    相关资源
    最近更新 更多