28

「こんにちは」という文字列があり、文字頻度マップを生成したいとしましょう。

Map[Char,Int] = Map(h -> 1, e -> 1, o -> 1, l -> 2)

これを繰り返し行うことができます:

val str = "hello"
var counts = new scala.collection.mutable.HashMap[Char,Int]
for (i <- str) {
    if (counts.contains(i))
        counts.put(i, counts(i) + 1)
    else
        counts.put(i, 1)
}

REPL をいじってみると、変更可能なコレクションを使用せずに、もう少し簡潔にできることがわかりました。

> str.groupBy(_.toChar).map{ p => (p._1, p._2.length)}
scala.collection.immutable.Map[Char,Int] = Map(h -> 1, e -> 1, o -> 1, l -> 2)

しかし、 groupBy() のパフォーマンス特性や、 map に渡されたブロックで何が起こっているのか (正確には p が何であるかなど) についてはわかりません。

Scala の関数型パラダイムを使用して慣用的にこれを行うにはどうすればよいですか?


背景として、Ruby から初めて Scala を使用しています。Rubyでは、私は使用しますinjectが、Scalaでそれを行う並列方法が何であるかはわかりません:

counts = str.each_byte.inject(Hash.new(0)){ |h, c| h[c] += 1; h}
4

4 に答える 4

37

1) とはpどういう意味ですか?

groupBy要素を type のキーにマップする関数を取りますK。一部のコレクションで呼び出されると、キーから同じキーにマップされたすべての要素へのマッピングを含む をColl返します。Map[K, Coll]K

したがって、あなたの場合、キー(文字)からすべての要素(文字)を含む文字列へのstr.groupBy(_.toChar)マップマッピングを生成します. あなたはこれを得る:kck == c.toChar

Map(e -> "e", h -> "h", l -> "ll", o -> "o")

AMapは、キーと値のペアの iterable です。この場合、各ペアは文字と要素の文字列です。mapで操作を呼び出すにはMap、これらのペアのマッピングが含まれますpp._1は文字であり、p._2は関連付けられた文字列です (length上記のように を呼び出すことができます)。

2)慣用的にこれを行う方法

上記は慣用的に行う方法です - と を使用groupBymapます。または、文字列の長さに対して不変のマップと再帰を使用して頻度を計算するか、不変のマップとfoldLeft.

3) 性能特性

違いを確認するためにベンチマークすることをお勧めします。反復性の高い文字列 (~3GHz iMac、JDK7、Scala 2.10.0 nightly) のマイクロベンチマークを次に示します。

object Imperative extends testing.Benchmark {
  val str = "abc" * 750000

  def run() {
    var counts = new scala.collection.mutable.HashMap[Char,Int]
    var i = 0
    val until = str.length
    while (i < until) {
      var c = str(i)
      if (counts.contains(c))
        counts.put(c, counts(c) + 1)
      else
        counts.put(c, 1)
      i += 1
    }

    //println(f)
  }
}


object Combinators extends testing.Benchmark {
  val str = "abc" * 750000

  def run() {
    val f = str.groupBy(_.toChar).map(p => (p._1, p._2.length))
  }
}


object Fold extends testing.Benchmark {
  val str = "abc" * 750000

  def run() {
    val f = str.foldLeft(Map[Char, Int]() withDefaultValue 0){(h, c) => h.updated(c, h(c)+1)}
  }
}

結果:

  • 必須:$ 103 57 53 58 53 53 53 53 53 53

  • コンビネータ:$ 72 51 63 56 53 52 52 54 53 53

  • 折り畳み:$ 163 62 71 62 57 57 57 58 57 57

命令バージョンを使用するように変更することに注意してくださいwithDefaultValue

var counts = new scala.collection.mutable.HashMap[Char,Int].withDefaultValue(0)
var i = 0
val until = str.length
while (i < until) {
  var c = str(i)
  counts.put(c, counts(c) + 1)
  i += 1
}

put各呼び出しを転送するため、明らかに非常に遅いです。

  • withDefaultValue:$ 133 87 109 106 101 100 101 100 101 101

結論: この場合のキャラクターのボックス化とボックス化解除は十分に高いため、これらのアプローチ間のパフォーマンスの違いを観察するのは困難です。

編集:

更新:トレイトの代わりにScalaMeter インライン ベンチマークBenchmarkを使用することもできます。

于 2012-08-24T08:06:35.833 に答える
26

アクセルの答えを拡張します。

あなたのgroupByソリューションはすでに機能しています。それをよりきれいにすることができるほんの小さな修正があります:

str.groupBy(_.toChar).mapValues(_.size)

Scala の代替injectfoldLeft, foldRight,です。使い方によって異なりますreduce。あなたのソリューションは変異に基づいており、機能的な世界では可変性は「いいえ」であるため、RubyでreduceOption使用した方法は機能的ではありません。これは、Scala の関数型スタイルに近い方法でソリューションを実行する方法です。injecthinject

str.foldLeft( Map[Char, Int]() ){ (m, c) => m + (c -> (m.getOrElse(c, 0) + 1)) }

明らかgroupByにはるかに良く見えます。

于 2012-08-24T08:26:03.923 に答える
11

ruby での例は、foldLeftand immutableを使用してほぼ直接 Scala に変換できますMap

考えられる解決策の 1 つを次に示します。

str.foldLeft(Map[Char, Int]() withDefaultValue 0){(h, c) => h.updated(c, h(c)+1)}

実際、ローカルの可変性に問題がなければ、次のようなものを作成できます。

def charFrequencies(str: String): collection.Map[Char, Int] = {
  val hash = collection.mutable.HashMap.empty[Char, Int] withDefaultValue 0
  str foreach { hash(_) += 1 }
  hash
}

hash(_) += 1は脱糖されc => hash(c) = hash(c) + 1、次にc => hash.update(c, hash.apply(c) + 1)

このソリューションは、中間コレクションを作成しないため、機能的なソリューションよりも効率的です。また、メソッドが immutable を返すためcollection.Map[Char, Int]、結果は不変として扱われます (誰も安全でないダウンキャストを実行しない限り)。

于 2012-08-24T08:26:00.693 に答える
6

から始めて、 (その名前が示すように) に続くと削減ステップに相当Scala 2.13するメソッドを使用できます。groupMapReducegroupBymapValues

"hello".groupMapReduce(identity)(_ => 1)(_ + _)
// immutable.Map[Char,Int] = Map(e -> 1, h -> 1, l -> 2, o -> 1)

これ:

  • groups 文字 (グループMapReduce のグループ部分)

  • mapグループ化された各値の出現を 1 にする (グループMap Reduce のマップ部分)

  • reduces 値のグループ内の値 ( _ + _) を合計する (groupMap Reduceの一部を減らす)。

これは、次の一連の文字を1 回のパスで実行する同等のバージョンです。

"hello".groupBy(identity).mapValues(_.map(_ => 1).reduce(_+_))
于 2018-10-06T10:16:37.270 に答える