用于嵌套集合的zipWithIndex(没有可变状态的发布)

时间:2014-09-15 11:52:14

标签: scala

有一些嵌套集合:

val xs = List(
           List("a","b"), 
           List("c"), 
           List("d", "e", "f"))

我想为嵌套列表的元素创建唯一索引:

val result = List(
         List(("a", 0), ("b", 1)), 
         List(("c", 2)),
         List(("d", 3), ("e", 4), ("f", 5)))

这是一个糟糕的解决方案(使用可变状态):

val indexes:List[Int] = xs.flatten.zipWithIndex.map(_._2)
var idx = 0
val result = xs.map(_.map{ el =>
   val copy = (el, indexes(idx))
   idx = idx + 1
   copy
})

如何在没有可变状态的情况下发布此任务?

7 个答案:

答案 0 :(得分:3)

另一种变化:

val index = Iterator.from(0)
for (sl <- xs) yield for (e <- sl) yield (e, index.next)

相当整洁,IMO,但迭代器不是纯函数,当然

功能性的,非常易读(无论如何)

 val starts = xs.scanLeft(0)(_ + _.size)
 (xs, starts, starts.tail).zipped map{ (sl, start, end) => sl zip ( start until end)}

答案 1 :(得分:3)

解决方案1(使用fold):

scala> xs.foldLeft((List[List[(String, Int)]](), 0)){ 
         case ((r, i), x) => (r:+x.zip(Stream.from(i)), i+x.size) 
       }._1
res1: List[List[(String, Int)]] = List(List((a,0), (b,1)), List((c,2)), List((d,3), (e,4), (f,5)))

解决方案2(使用递归)

scala> def deepZip[A](ls: List[List[A]], i: Int = 0): List[List[(A, Int)]] = ls match {
     |   case Nil => Nil
     |   case x::xs => x.zip(Stream.from(i)) :: deepZip(xs, i+x.size)
     | }
deepZip: [A](ls: List[List[A]], i: Int)List[List[(A, Int)]]

scala> deepZip(xs)
res2: List[List[(String, Int)]] = List(List((a,0), (b,1)), List((c,2)), List((d,3), (e,4), (f,5)))

解决方案3:

scala> (xs, xs.map(_.size).scanLeft(0){ _+_ }).zipped map { (a, b) => a.zip(Stream.from(b)) }
res3: List[List[(String, Int)]] = List(List((a,0), (b,1)), List((c,2)), List((d,3), (e,4), (f,5)))

答案 2 :(得分:2)

这就是我提出的:

def iterate(someList: List[List[Int]], counter: Int = 0, acc: List[List[(Int, Int)]] = List()): List[List[(Int, Int)]] = someList match {
  case head :: tail =>
    val newCounter = counter + head.length
    val sublist = head.zipWithIndex.map {
      // add the counter to the index
      case (v, i) => (v, i + counter)
    }
    // recurse
    iterate(tail, newCounter, acc :+ sublist)
  case _ => acc
}

scala> iterate(List(List(1,2), List(3,4)))
res3: List[List[(Int, Int)]] = List(List((1,0), (2,1)), List((3,2), (4,3)))

scala> iterate(List(List(1,2), List(3,4), List(5)))
res4: List[List[(Int, Int)]] = List(List((1,0), (2,1)), List((3,2), (4,3)), List((5,4)))

这基本上做的是迭代一个列表列表,用索引压缩每个子列表并添加一个计算器值,该值考虑所有以前的列表长度,当列表为空时我们返回一个累加器。

但正如我所说,我不会用可变版本交换它。

答案 3 :(得分:2)

您始终可以使用足够的强力将强制性while循环转换为功能foldLeft

val xs = List(List("a", "b"), List("c"), List("d", "e", "f"))

def addIndeces(xs: List[List[String]]): List[List[(String, Int)]] = {
  val outerLoopState = 0 -> Vector.empty[List[(String, Int)]]

  val (finalCounter, finalResult) = xs.foldLeft(outerLoopState) {
    case ((counter, result), sublist) =>
      val innerLoopState = counter -> Vector.empty[(String, Int)]
      val (newCounter, subResult) = sublist.foldLeft(innerLoopState) {
        case ((counter, subResult), element) =>
          (counter + 1, subResult :+ (element, counter))
      }

      (newCounter, result :+ subResult.toList)
  }

  finalResult.toList
}

// scala> addIndeces(xs)
// res0: List[List[(String, Int)]] = List(List((a,0), (b,1)), List((c,2)), List((d,3), (e,4), (f,5)))

我已将Vector用于中间结果,以获得更有效的功能append操作。使用List,我必须先添加然后反转中间结果。

答案 4 :(得分:1)

平原不是尾递归...

 def zipFrom[A](start:Int)(l:List[A]):(List[(A,Int)],Int) = {
  val end = start + l.length
  val ys = l zip (start to end)
  ys -> end
 }

 def zp[A](xs:List[List[A]],acc:Int):List[List[(A,Int)]] = xs match {
  case Nil    => Nil
  case h :: t =>
   val (l,e) = zipFrom(acc)(h)
   l :: zp(t,e)
  }

答案 5 :(得分:0)

var index = 0
val lim = li.map {
  case l @ List(_*) =>
    val s = index
    index += l.length
    l.zip(s until index)
}

它工作快速而简单

答案 6 :(得分:0)

@Archetypal Paul使用地图代替

的变体
val ys = Iterator.from(0)
val zs = xs.map { _.map ((_,ys.next)) }

使用zip&amp; amp;迭代器和地图

val ys = xs.flatten.zipWithIndex.iterator
val zs = xs.map { _.map ( x=> ys.next) }