【问题标题】:Split a sequence into two alternating sequences将一个序列拆分为两个交替的序列
【发布时间】:2019-11-21 12:01:01
【问题描述】:

我想将一个大小大于 2 的序列拆分成这样的交替序列:

def splitAlt(s: Seq[Char]): (Seq[Char], Seq[Char]) = ???

splitAlt(Nil)  // raise an exception
splitAlt("a")  // raise an exception  
splitAlt("ab") // (Seq('a'), Seq('b'))
splitAlt("abc") // (Seq('a', 'c'), Seq('b'))

我找到了一个优雅的 solutiongroupedtranspose 我想使用。
不幸的是,它仅在输入序列具有偶数大小时才有效。
您将如何修改该解决方案以适用于任何大小的输入?

你有更优雅的解决方案吗?

【问题讨论】:

    标签: scala collections split


    【解决方案1】:

    这是一个非常简单的解决方案:

    def splitAlt[T](s: Seq[T]): (Seq[T], Seq[T]) = {
      val (fsts, snds) = s.zipWithIndex.partition { case (x, i) => i % 2 == 0 }
      (fsts.map(_._1), snds.map(_._1))
    }
    
    splitAlt("")      // -> (Seq(), Seq())
    splitAlt("a")     // -> (Seq(a), Seq())
    splitAlt("ab")    // -> (Seq(a), Seq(b))
    splitAlt("abc")   // -> (Seq(a, c), Seq(b))
    splitAlt("abcd")  // -> (Seq(a, c), Seq(b, d))
    splitAlt("abcde") // -> (Seq(a, c, e), Seq(b, d))
    

    我声称它很优雅,因为:

    • 不抛出异常,只返回空序列;
    • 它适用于任何类型的序列,而不仅仅是字符;
    • 它适用于任何长度的序列;
    • 它只遍历序列一次。

    更新:这是对任意数量组的概括:

    def splitGen[T](xs: Seq[T], n: Int): Seq[Seq[T]] = {
      val groups =
        xs.zipWithIndex
          .groupBy { case (x, i) => i % n }
          .mapValues { vs => vs.map(_._1) }
      0 until n map groups
    }
    
    splitGen("abcdefg", 1)  // -> Seq(Seq(a, b, c, d, e, f, g))
    splitGen("abcdefg", 2)  // -> Seq(Seq(a, c, e, g), Seq(b, d, f))
    splitGen("abcdefg", 3)  // -> Seq(Seq(a, d, g), Seq(b, e), Seq(c, f))
    splitGen("abcdefg", 4)  // -> Seq(Seq(a, e), Seq(b, f), Seq(c, g), Seq(d))
    splitGen("abcdefg", 5)  // -> Seq(Seq(a, f), Seq(b, g), Seq(c), Seq(d), Seq(e))
    

    你可以泛化grouped+transpose的解决方案,将原始序列填充到恰到好处的长度,然后取消填充结果,但这需要你处理一些特殊情况:

    def splitGen[T](xs: Seq[T], n: Int): Seq[Seq[T]] = {
      /* Pad */
      val paddedLength: Int = math.ceil(xs.length / n.toDouble).toInt * n
      val padded: Seq[T] =
        if (xs.isEmpty) xs
        else            xs.padTo(paddedLength, xs.head)
    
      /* Transpose */
      val transposed = padded.grouped(n).toList.transpose
    
      /* Unpad */
      if (paddedLength == xs.length) transposed
      else transposed.zipWithIndex.map { case (row, i) =>
        if (i < xs.length % n) row
        else                   row.init
      }
    

    }

    【讨论】:

    • 我同意如果输入大小
    • 还有一个问题是很难推广这种将输入拆分为 3、4、5 和更多子序列的解决方案。概括不是问题的一部分。我可能会单独询问。
    • @Michael:我添加了通用解决方案的比较。
    【解决方案2】:

    这是一种基于grouped + transpose 解决方案的方法。

    def splitAlt[T](s: Seq[T]) = {
        s.grouped(2).toList.map(p => Seq(p.headOption, p.tail.headOption)).transpose.map(_.flatten)
    }
    

    基本上,它将grouped 返回的内部List[T] 转换为恰好有2 个元素的List[Option[T]],因为transpose 要求所有集合大小相同。

    请注意,这将返回一个嵌套列表而不是一对列表。如果我们要将结果转换为元组,我们需要特别注意少于 2 个元素的情况,所以为了“优雅”,我保持这样。

    【讨论】:

    • 谢谢。不错,但对我来说有点太复杂了。希望能找到更简单的解决方案。
    猜你喜欢
    • 2010-11-22
    • 2022-08-09
    • 2021-12-31
    • 2010-09-27
    • 2012-12-04
    • 1970-01-01
    • 2017-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多