我一直在寻找类似于Scala Array 中 String.split 的方法,但我找不到它。
我想要做的是用分隔符分割数组。
例如,分隔以下数组:
val array = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
使用'\n'
分隔符,结果应为:
List(Array(a, b), Array(c, d, e), Array(g))
我知道我可以将Array转换为String,并在那里应用split:
array.mkString.split('\n').map(_.toArray)
但我更愿意跳过转换。
到目前为止我的解决方案是递归地使用 span 并且有点过于样板:
def splitArray[T](array: Array[T], separator: T): List[Array[T]] = {
def spanRec(array: Array[T], aggResult: List[Array[T]]): List[Array[T]] = {
val (firstElement, restOfArray) = array.span(_ != separator)
if (firstElement.isEmpty) aggResult
else spanRec(restOfArray.dropWhile(_ == separator), firstElement :: aggResult)
}
spanRec(array, List()).reverse
}
我确信Scala中肯定有一些东西我不见了。有什么想法吗?
感谢, 鲁
答案 0 :(得分:2)
这不是最简洁的实现,但它应该公平地执行并保留数组类型而不需要求助于反射。循环当然可以用递归代替。
由于你的问题没有明确说明我应该对分隔符做什么,我认为它们不应该在输出列表中引起任何条目(参见下面的测试用例)。
def splitArray[T](xs: Array[T], sep: T): List[Array[T]] = {
var (res, i) = (List[Array[T]](), 0)
while (i < xs.length) {
var j = xs.indexOf(sep, i)
if (j == -1) j = xs.length
if (j != i) res ::= xs.slice(i, j)
i = j + 1
}
res.reverse
}
一些测试:
val res1 =
// Notice the two consecutive '\n'
splitArray(Array('a', 'b', '\n', 'c', 'd', 'e', '\n', '\n', 'g', '\n'), '\n')
println(res1)
// List([C@12189646, [C@c31d6f2, [C@1c16b01f)
res1.foreach(ar => {ar foreach print; print(" ")})
// ab cde g
// No separator
val res2 = splitArray(Array('a', 'b'), '\n')
println(res2)
// List([C@3a2128d0)
res2.foreach(ar => {ar foreach print; print(" ")})
// ab
// Only separators
val res3 = splitArray(Array('\n', '\n'), '\n')
println(res3)
// List()
答案 1 :(得分:1)
您可以使用span
方法将数组拆分为两部分,然后在第二部分递归调用split方法。
import scala.reflect.ClassTag
def split[A](l:Array[A], a:A)(implicit act:ClassTag[Array[A]]):Array[Array[A]] = {
val (p,s) = l.span(a !=)
p +: (if (s.isEmpty) Array[Array[A]]() else split(s.tail,a))
}
这不是很有效,因为它具有二次性能。如果你想要快速的东西,一个简单的尾递归解决方案可能是最好的方法。
使用列表而不是数组,您将获得线性性能,并且不需要反射。
答案 2 :(得分:1)
借用了sschaef解决方案的论据:
def split[T](array : Array[T])(where : T=>Boolean) : List[Array[T]] = {
if (array.isEmpty) Nil
else {
val (head, tail) = array span {!where(_)}
head :: split(tail drop 1)(where)
}
} //> split: [T](array: Array[T])(where: T => Boolean)List[Array[T]]
val array = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
split(array){_ =='\n'} //> res2: List[Array[Char]] = List(Array(a, b), Array(c, d, e), Array(g))
def splitByNewLines(array : Array[Char]) = split(array){_ =='\n'}
splitByNewLines(array) //> res3: List[Array[Char]] = List(Array(a, b), Array(c, d, e), Array(g))
答案 3 :(得分:1)
这是一个应该完成这项工作的简短表述:
def split(array:Array[Char], sep:Char) : Array[Array[Char]] = {
/* iterate the list from right to left and recursively calculate a
pair (chars,list), where chars contains the elements encountered
since the last occurrence of sep.
*/
val (chars, list) = array.foldRight[(List[Char],List[Array[Char]])]((Nil,Nil))((x,y) => if (x == sep) (Nil, (y._1.toArray)::y._2) else (x::y._1, y._2) );
/* if the last element was sep, do nothing;
otherwise prepend the last collected chars
*/
if (chars.isEmpty)
list.toArray
else
(chars.toArray::list).toArray
}
/* example:
scala> split(array,'\n')
res26: Array[Array[Char]] = Array(Array(a, b), Array(c, d, e), Array(g), Array())
*/
如果我们使用List而不是Array,我们可以稍微概括一下代码:
def split[T](array:List[T], char:T) : List[List[T]] = {
val (chars, list) = array.foldRight[(List[T],List[List[T]])]((Nil,Nil))((x,y) => if (x == char) (Nil, (y._1)::y._2) else (x::y._1, y._2) )
if (chars.isEmpty) list else (chars::list)
}
/* example:
scala> split(array.toList, '\n')
res32: List[List[Char]] = List(List(a, b), List(c, d, e), List(g), List())
scala> split(((1 to 5) ++ (1 to 5)).toList, 3)
res35: List[List[Int]] = List(List(1, 2), List(4, 5, 1, 2), List(4, 5))
*/
如果这个解决方案被认为是优雅或不可读的,请留给读者和她对函数式编程的偏好:)
答案 4 :(得分:1)
使用foldLeft
进行此操作的简单方法val f = array.foldLeft((Array[Char](),List[Array[Char]]()))(
(acc, char: Char) => {
char match {
case '\n' => (Array(),acc._1 :: acc._2)
case _ => (acc._1 :+ char,acc._2)
}
}
)._2.reverse
答案 5 :(得分:1)
我想出了一个针对以下方面的解决方案:
Array
一样拆分Vector
,并且像任意对象的集合一样拆分Char
的集合Array[A]
被分成Array[Array[A]]
,Vector[A]
被分成Vector[Vector[A]]
Iterator
)split
方法)在进行解释之前,请注意,您可以使用here on Scastie之后的代码。
第一步是实现一个Iterator
,用于分块您的收藏集:
import scala.language.higherKinds
import scala.collection.generic.CanBuildFrom
final class Split[A, CC[_]](delimiter: A => Boolean, as: CC[A])(
implicit view: CC[A] => Seq[A], cbf: CanBuildFrom[Nothing, A, CC[A]])
extends Iterator[CC[A]] {
private[this] var it: Iterator[A] = view(as).iterator
private def skipDelimiters() = {
it = it.dropWhile(delimiter)
}
skipDelimiters()
override def hasNext: Boolean = it.hasNext
override def next(): CC[A] = {
val builder = cbf()
builder ++= it.takeWhile(!delimiter(_))
skipDelimiters()
builder.result()
}
}
我使用谓词而不是值来更灵活地拆分集合,尤其是在拆分非标量值的集合(例如Char
s)时。
我正在使用集合类型的隐式视图,以便将其应用于可以视为Seq
的所有类型的集合(例如Vector
和Array
s)和CanBuildFrom
,以便能够构建我作为输入接收的确切集合类型。
Iterator
的实现只是确保删除定界符,并对其余部分进行分块。
我们现在可以使用implicit class
提供一个友好的界面,并将split
方法添加到所有集合中,两者都允许将谓词或值定义为定界符:
final implicit class Splittable[A, CC[_]](val as: CC[A])(implicit ev1: CC[A] => Seq[A], ev2: CanBuildFrom[Nothing, A, CC[A]], ev3: CanBuildFrom[Nothing, CC[A], CC[CC[A]]]) {
def split(delimiter: A => Boolean): CC[CC[A]] = new Split(as)(delimiter).to[CC]
def split(delimiter: A): CC[CC[A]] = new Split(as)(_ == delimiter).to[CC]
}
现在您可以在Char
个集合中自由使用您的方法
val a = Array('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
val b = List('\n', '\n', '\n')
val c = Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n')
val d = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
val e = Array('a', 'b', 'c', 'd', 'e', 'g', '\n')
a.split('\n')
b.split('\n')
c.split('\n')
d.split('\n')
e.split('\n')
和任意对象都一样:
final case class N(n: Int, isDelimiter: Boolean)
Vector(N(1, false), N(2, false), N(3, true), N(4, false), N(5, false)).split(_.isDelimiter)
请注意,通过直接使用迭代器,您可以使用惰性方法,因为您可以看到是否向next
方法中添加了调试打印并尝试执行以下操作:
new Split(Vector('\n', 'c', 'd', 'e', '\n', 'g', '\n'))(_ == '\n'}).take(1).foreach(println)
如果需要,可以在Splittable
上添加几个返回Iterator
的方法,以便可以直接通过它公开惰性方法。
答案 6 :(得分:0)
我不知道任何内置方法,但我提出了一个比你更简单的方法:
def splitOn[A](xs: List[A])(p: A => Boolean): List[List[A]] = xs match {
case Nil => Nil
case x :: xs =>
val (ys, zs) = xs span (!p(_))
(x :: ys) :: splitOn(zs.tail)(p)
}
// for Array
def splitOn[A : reflect.ClassTag](xs: Array[A])(p: A => Boolean): List[Array[A]] =
if (xs.isEmpty) List()
else {
val (ys, zs) = xs.tail span (!p(_))
(xs.head +: ys) :: splitOn(zs.tail)(p)
}
scala> val xs = List('a', 'b', '\n', 'c', 'd', 'e', '\n', 'g', '\n')
xs: List[Char] =
List(a, b,
, c, d, e,
, g,
)
scala> splitOn(xs)(_ == '\n')
res7: List[List[Char]] = List(List(a, b), List(c, d, e), List(g))
答案 7 :(得分:0)
这个怎么样?没有反射,也没有递归,但尝试使用尽可能多的scala库。
def split[T](a: Array[T], sep: T)(implicit m:ClassManifest[T]): Array[Array[T]] = {
val is = a.indices filter (a(_) == sep)
(0 +: (is map (1+))) zip (is :+ (a.size+1)) map {
case(from,till) => a.slice(from, till)
}
}
可能很慢,但只是为了好玩。 :-)
indices filter
为您提供找到分隔符的位置的索引(is
)。
在您的示例中,那是2,6,8
。我认为这是O(n)
。
下一行将其转换为(0,2), (3,6), (7,8), (9, 10)
。因此k
分隔符会产生k+1
个范围。
这些将交给slice
,其余的工作完成。转换也是O(n)
,其中n
是找到的分隔符数。
(这意味着Array[Char]()
的输入将产生Array(Array())
而不是更直观的Array()
,但这并不太有趣。)
数组追加/前置(:+
,+:
)使用数组是浪费的,但是使用允许O(1)
追加/的适当集合无法解决任何问题预规划。
答案 8 :(得分:0)
您也可以使用fold:
完成此操作def splitArray[T](array: Array[T], separator: T) =
array.foldRight(List(List.empty[T])) { (c, list) =>
if (c == separator) Nil :: list
else (c :: list.head) :: list.tail
}.filter(!_.isEmpty).map(_.reverse).toArray
已经被lambda.xy.x提到了,但由于某种原因,它的可读性稍差一点;)
答案 9 :(得分:0)
Pimped版本的通用序列/数组拆分 -
implicit def toDivide[A, B <% TraversableLike[A, B]](a : B) = new {
private def divide(x : B, condition: (A) => Boolean) : Iterable[B] = {
if (x.size > 0)
x.span(condition) match {
case (e, f) => if (e.size > 0) Iterable(e) ++ divide(f.drop(1),condition) else Iterable(f)
}
else
Iterable()
}
def divide(condition: (A) => Boolean): Iterable[B] = divide(a, condition)
}
答案 10 :(得分:0)
几乎是单线的:
sed -E 's/REGEX/REPLACE/g' FILENAME
对于给定的val it = array.iterator
List.range(0, array.count(_ == '\n')).map(_ => it.takeWhile(_ != '\n').toArray)
,它使用array
的{{1}}版本,以便调用Iterator
的次数与出现分隔符的次数相同。
使用Array
(虽然它是一个范围内的映射)的另一版本,但可读性较差,但稍短一些:
.takeWhile
可以通过以下方式将List.tabulate
设置为val it = array.iterator
List.tabulate(array.count(_ == '\n'))(_ => it.takeWhile(_ != '\n').toArray)
:
array.mkString.split("\n", -1).map(_.toArray)
并以此方式使用:
Array
要摆脱连续出现两次分隔符而产生的空子数组,可以用implicit class ArrayExtensions[T: ClassTag](array: Array[T]) {
def split(sep: T): List[Array[T]] = {
val it = array.iterator
List.range(0, array.count(_ == sep)).map(_ => it.takeWhile(_ != sep).toArray)
}
}
用管道传递结果。