14

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.mkString.split('\n').map(_.toArray)

しかし、私は変換をスキップしたいと思います。

私がこれまでに持っている解決策は、スパンを再帰的に使用することを含み、定型的すぎます:

  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 に欠けているものがあるはずです。何か案が?

ありがとう、ルーベン

4

11 に答える 11

3

これは最も簡潔な実装ではありませんが、公平に実行する必要があり、リフレクションに頼ることなく配列型を保持します。もちろん、ループは再帰に置き換えることができます。

あなたの質問は、私が推測するセパレーターで何をすべきかを明示的に述べていないので、出力リストにエントリが発生しないはずです(以下のテストケースを参照)。

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()
于 2013-01-11T13:39:52.813 に答える
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())
*/

Array の代わりに List を使用すると、コードを少し一般化できます。

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))
*/

このソリューションが洗練されている、または読みにくいと見なされる場合は、読者と関数型プログラミングに対する彼女の好みに委ねられます:)

于 2013-01-11T17:54:13.373 に答える
1

このメソッドを使用しspanて配列を 2 つの部分に分割し、2 番目の部分で分割メソッドを再帰的に呼び出すことができます。

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 次性能であるため、あまり効率的ではありません。高速なものが必要な場合は、単純な末尾再帰ソリューションがおそらく最良のアプローチです。

配列の代わりにリストを使用すると、線形のパフォーマンスが得られ、リフレクションは必要ありません。

于 2013-01-11T13:20:16.317 に答える
1

A は、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))
于 2013-01-11T14:21:09.247 に答える
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 で言及されていましたが、何らかの理由で必要以上に読みにくくなりました ;)

于 2013-11-22T12:33:48.363 に答える
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))
于 2013-01-11T13:02:50.693 に答える
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)
  }
于 2015-12-19T05:02:12.577 に答える
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]()will yieldの入力を意味しArray(Array())、より直感的ではありませんArray()が、あまり興味深いものではありません)。

配列の追加/前置 ( :+、 ) は、配列を使用すると無駄になりますが、追加/前置+:できる適切なコレクションを使用して解決できないものは何もありません。O(1)

于 2013-01-11T14:03:28.050 に答える