问题
I would like to split a sequence of size > 2 into alternating sequences like this:
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'))
I found an elegant solution with grouped and transpose I'd like to use.
Unfortunately it works only i fthe input sequence has even size.
How would you modify that solution to work for input of any size ?
Do you have a more elegant solution ?
回答1:
This is a very straightforward solution:
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))
I claim it's elegant because:
- it doesn't throw an exception, it just returns empty sequences;
- it works for sequences of any type, not just characters;
- it works for sequences of any length;
- it traverses the sequence only once.
Update: this is a generalization for an arbitrary number of groups:
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))
You can generalize the grouped+transpose solution by padding the original sequence for the length to be just right and then unpadding the result, but it requires you to take care of some special cases:
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
}
}
回答2:
Here's an approach based on the grouped + transpose solution.
def splitAlt[T](s: Seq[T]) = {
s.grouped(2).toList.map(p => Seq(p.headOption, p.tail.headOption)).transpose.map(_.flatten)
}
Basically, it turns the inner List[T] returned by grouped into List[Option[T]] with exactly 2 elements, as transpose requires all collections to be of equal size.
Noted that this returns a nested list rather than a pair of lists. We'd need some special care for the less-than-2-elements cases if we are to turn the results into tuple, so for the sake of 'elegance' I keep it like this.
来源:https://stackoverflow.com/questions/58974560/split-a-sequence-into-two-alternating-sequences