[kotlinfp]コレクションを使用してデータ-2を処理する
54109 ワード
前回の授業でダンテはマッピング関数のボルド関数を紹介し、集合を単一値のボルド関数に縮小することで、再帰するたびに集合を終了条件に収束する操作を実行しました.
データの収集を段階的に削減
코틀린으로 배우는 함수형 프로그래밍을 읽으며 정리한 내용입니다.
復習として、ボルド関数を再検討しましょう.データの収集を段階的に削減
JavaScriptでArrayprototype.reduceのように、測定可能なタイプを巡回し、접는다
、폴드
と呼ばれる値に収束させる.
リストに最初から最後に加算されたsum関数がある場合は、次のようにします.
sum関数はfoldleftと呼ばれます.左から右に減少しているからです.sumには2つの論理が混合されている:1つの値に収束する論理と加算された論理.収束させる論理を一般化しましょう.fun sum(list: FunList<Int>): Int = when (list) {
FunList.Nil -> 0
is FunList.Cons -> list.head + sum(list.tail)
}
foldLeft関数の作成
tailrec fun <T,R> FunList<T>.foldLeft(acc: R, f: (R,T) -> R): R = when(this) {
FunList.Nil -> acc
is FunList.Cons -> tail.foldLeft(f(acc,head), f)
}
回帰中はFunListNilに一致すると、これまでに作成されたaccが返され、元のリストのすべての値、すなわちFunListが遍歴されていることを示します.Consに一致する場合、tailのfoldLeft関数は、リストの一番左から値(head)をf関数のパラメータとして減算します.
上のsum関数はfoldLeft、sumbyFoldLeftの結合で代用できます.fun sumByFoldLeft(list: FunList<Int>): Int = list.foldLeft(0) { acc, x -> acc + x}
fun main() {
val intList = Cons(1, Cons(2, Cons(3)))
println(sumByFoldLeft(intList))
}
foldLeft関数を使用してtoUpper関数を作成する
toUpper関数は、小文字のリストを受け入れ、大文字のリストに変更します.fun toUpper(list: FunList<Char>): FunList<Char> = list.foldLeft(Nil) {
acc: FunList<Char>, char: Char -> acc.appendTail(char.toUpperCase())
}
これを拡張関数に変えましょう.fun<T,R> FunList<T>.mapByFoldLeft(f: (T) -> R): FunList<R> = this.foldLeft(FunList.Nil) {
acc: FunList<R>, x -> acc.appendTail(f(x))
}
foldRight関数の作成
fun <T, R> FunList<T>.foldRight(acc: R, f: (T,R) -> R): R = when (this) {
FunList.Nil -> acc
is FunList.Cons -> f(head, tail.foldRight(acc,f))
}
tailrec fun <T, R> FunList<T>.foldLeft(acc: R, f: (R, T) -> R): R = when(this) {
FunList.Nil -> acc
is FunList.Cons -> tail.foldLeft(f(acc,head), f)
}
foldRightは右から左に減算される関数です.右から左に減算するのでheadは最後に演算する必要があります.
foldleftとよく似ていますが、foldleftとは違って尻尾ではありません.val intList = Cons(1, Cons(3, Cons(10)))
intList.foldRight(0) { x, acc -> x - acc }
f(1, [3,10].foldRight(0, { x, y -> x - y}), acc: 0
f(1, f(3, [10].foldRight(0, { x, y -> x - y}), acc: 0
f(1, f(3, f(10, [].foldRight(0, { x, y -> x - y}))))
f(1, f(3, f(10,0)))
f(1, f(3,10))
f(1, -7)
8
foldLeft関数はスタックに対して安全であるため、リストのサイズが大きくなり、f関数の実行コストが大きい場合に使用することが望ましい.
ではfoldRight関数はいつ使用すればいいのでしょうか.fun <T ,R> FunList<T>.mapByFoldLeft(f: (T) -> R): FunList<R> = foldLeft(FunList.Nil) {
acc: FunList<R>, x -> acc.appendTail(f(x))
}
fun <T, R> FunList<T>.mapByFoldRight(f: (T) -> R): FunList<R> = foldRight(FunList.Nil) {
x, acc: FunList<R> -> acc.addHead(f(x))
}
mapByFoldRight関数を表示し、foldRightはaddHeadを使用します.appendTailはO(2 n)であり、addHeadはO(1)であるため、実行コストが低い.
したがって、一般的なリストではfoldRightを使用することが望ましい.
コマンドと関数のパフォーマンス比較 fun imperativeWay(intList: List<Int>): Int {
for (value in intList) {
val doubleValue = value * value
if( doubleValue < 10) {
return doubleValue
}
}
throw NoSuchElementException("there is no value")
}
fun functionalWay(intList: List<Int>): Int =
intList
.map {n -> n * n }
.filter { n -> n < 10 }
.first()
fun main() {
val bigIntList = (1..10000000).toList()
var start = System.currentTimeMillis()
imperativeWay(bigIntList)
println("${System.currentTimeMillis()}ms") // "0 ms"
start = System.currentTimeMillis()
functionalWay(bigIntList)
println("${System.currentTimeMillis()}ms") // "2180 ms"
}
非常に大きなリストに対して同じ演算を実行すると,関数化方式にはより長い時間がかかることがわかる.
このようなパフォーマンスの違いは、コマンド型方式では1つのユニットに対してmap、filterの論理が同時に実行され、関数型方式ではmap、filterなどの組合せ演算子ごとにリストが全循環するためである.
コトリンの集合は基本的に即時評価されるため,性能コストが高い.
各言語は、Cottinに対してシーケンスを使用する怠惰評価(lazy evaluation)を提供する構文を規定している.fun realFunctionalWay(intList: List<Int>): Int =
intList.asSequence()
.map { n -> n * n }
.filter { n -> n < 10 }
.first()
fun main() {
val bigIntList = (1..10000000).toList()
var start = System.currentTimeMillis()
start = System.currentTimeMillis()
realFunctionalWay(bigIntList)
println("${System.currentTimeMillis()}ms") // “8 ms"
}
怠惰な集合FunStream
以前に作成したFunListは、コンストラクション関数パラメータを適用するとすぐに評価されたが、FunStreamはコンストラクション関数パラメータとして1次関数を提供するため、必要に応じて評価される怠惰な評価集合である.sealed class FunList<out T> {
object Nil: FunList<Nothing>()
data class Cons<out T>(val head: T, val tail: FunList<T>):FunList<T>()
}
sealed class FunStream<out T> {
object Nil: FunStream<Nothing>()
data class Cons<out T>(val head: () -> T, val tail: () -> FunStream<T>) : FunStream<T>()
}
コンビネーションフローの再作成
これはappendTailロジックです.fun <T> FunStream<T>.appendTail(value: T): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Cons({ value }, { FunStream.Nil })
is FunStream.Cons -> FunStream.Cons(head, { tail().appendTail(value) })
}
フィルタとmapをストリーム方式で再実現した.tailrec fun <T> FunStream<T>.dropWhile(f: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> this
is FunStream.Cons -> if (f(head())) {
this
} else {
tail().dropWhile(f)
}
}
fun <T> FunStream<T>.filter(p: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> {
val first = dropWhile(p)
if (first != FunStream.Nil) {
FunStream.Cons({ first.getHead() }, { first.getTail().filter(p) })
} else {
FunStream.Nil
}
}
}
fun <T, R> FunStream<T>.map(f: (T) -> R): FunStream<R> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> FunStream.Cons({ f(head()) }, { tail().map(f) })
}
性能をもう少し比較するfun funListWay(intList: FunList<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun funStreamWay(intList: FunStream<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun main() {
val bigIntList = (1..10000000).toFunList()
var start = System.currentTimeMillis()
funListWay(bigIntList)
println("${System.currentTimeMillis() - start} ms") // 9467 ms
val bigIntStream = (1..10000000).toFunStream()
start = System.currentTimeMillis()
funStreamWay(bigIntStream)
println("${System.currentTimeMillis() - start} ms") // 7 ms
}
既存のFunListに比べて性能が著しく向上した.
無限値の保存
一般的なデータ構造では、無限大の値を格納できません.値を保存するには、評価がメモリに格納され、無限大の値がメモリに格納されないため、評価が必要です.
したがって、無限値を格納するには、ストリームなどの無限値を表す関数を格納する必要があります.
値は格納そのものではなく、値を表す関数を格納します.
次のgenerateFunStream関数はseed値から始まり、Cons
で2番目に増分値を返す関数をheadに入れます.これにより、評価が必要な場合にのみ、メモリに選択的に配置できます.fun <T> generateFunStream(seed: T, generate: (T) -> T): FunStream<T> =
FunStream.Cons({ seed }, { generateFunStream(generate(seed), generate) })
forEachのRamda式でprintlnを加えます.メモリに表現しにくいほど大きい数値があるか、ユーザーがランダムにプログラムを停止しない限り、実行は続行されます.fun main() {
val infiniteVal = generateFunStream(0) { it + 5 }
infiniteVal.forEach { println (it)}
}
tailrec fun <T> FunStream<T>.forEach(f: (T) -> Unit): Unit = when (this) {
FunStream.Nil -> Unit
is FunStream.Cons -> {
f(head())
tail().forEach(f)
}
}
Reference
この問題について([kotlinfp]コレクションを使用してデータ-2を処理する), 我々は、より多くの情報をここで見つけました
https://velog.io/@jay/kotlin-fp-collection-2
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
fun sum(list: FunList<Int>): Int = when (list) {
FunList.Nil -> 0
is FunList.Cons -> list.head + sum(list.tail)
}
tailrec fun <T,R> FunList<T>.foldLeft(acc: R, f: (R,T) -> R): R = when(this) {
FunList.Nil -> acc
is FunList.Cons -> tail.foldLeft(f(acc,head), f)
}
fun sumByFoldLeft(list: FunList<Int>): Int = list.foldLeft(0) { acc, x -> acc + x}
fun main() {
val intList = Cons(1, Cons(2, Cons(3)))
println(sumByFoldLeft(intList))
}
fun toUpper(list: FunList<Char>): FunList<Char> = list.foldLeft(Nil) {
acc: FunList<Char>, char: Char -> acc.appendTail(char.toUpperCase())
}
fun<T,R> FunList<T>.mapByFoldLeft(f: (T) -> R): FunList<R> = this.foldLeft(FunList.Nil) {
acc: FunList<R>, x -> acc.appendTail(f(x))
}
fun <T, R> FunList<T>.foldRight(acc: R, f: (T,R) -> R): R = when (this) {
FunList.Nil -> acc
is FunList.Cons -> f(head, tail.foldRight(acc,f))
}
tailrec fun <T, R> FunList<T>.foldLeft(acc: R, f: (R, T) -> R): R = when(this) {
FunList.Nil -> acc
is FunList.Cons -> tail.foldLeft(f(acc,head), f)
}
val intList = Cons(1, Cons(3, Cons(10)))
intList.foldRight(0) { x, acc -> x - acc }
f(1, [3,10].foldRight(0, { x, y -> x - y}), acc: 0
f(1, f(3, [10].foldRight(0, { x, y -> x - y}), acc: 0
f(1, f(3, f(10, [].foldRight(0, { x, y -> x - y}))))
f(1, f(3, f(10,0)))
f(1, f(3,10))
f(1, -7)
8
fun <T ,R> FunList<T>.mapByFoldLeft(f: (T) -> R): FunList<R> = foldLeft(FunList.Nil) {
acc: FunList<R>, x -> acc.appendTail(f(x))
}
fun <T, R> FunList<T>.mapByFoldRight(f: (T) -> R): FunList<R> = foldRight(FunList.Nil) {
x, acc: FunList<R> -> acc.addHead(f(x))
}
fun imperativeWay(intList: List<Int>): Int {
for (value in intList) {
val doubleValue = value * value
if( doubleValue < 10) {
return doubleValue
}
}
throw NoSuchElementException("there is no value")
}
fun functionalWay(intList: List<Int>): Int =
intList
.map {n -> n * n }
.filter { n -> n < 10 }
.first()
fun main() {
val bigIntList = (1..10000000).toList()
var start = System.currentTimeMillis()
imperativeWay(bigIntList)
println("${System.currentTimeMillis()}ms") // "0 ms"
start = System.currentTimeMillis()
functionalWay(bigIntList)
println("${System.currentTimeMillis()}ms") // "2180 ms"
}
非常に大きなリストに対して同じ演算を実行すると,関数化方式にはより長い時間がかかることがわかる.このようなパフォーマンスの違いは、コマンド型方式では1つのユニットに対してmap、filterの論理が同時に実行され、関数型方式ではmap、filterなどの組合せ演算子ごとにリストが全循環するためである.
コトリンの集合は基本的に即時評価されるため,性能コストが高い.
各言語は、Cottinに対してシーケンスを使用する怠惰評価(lazy evaluation)を提供する構文を規定している.
fun realFunctionalWay(intList: List<Int>): Int =
intList.asSequence()
.map { n -> n * n }
.filter { n -> n < 10 }
.first()
fun main() {
val bigIntList = (1..10000000).toList()
var start = System.currentTimeMillis()
start = System.currentTimeMillis()
realFunctionalWay(bigIntList)
println("${System.currentTimeMillis()}ms") // “8 ms"
}
怠惰な集合FunStream
以前に作成したFunListは、コンストラクション関数パラメータを適用するとすぐに評価されたが、FunStreamはコンストラクション関数パラメータとして1次関数を提供するため、必要に応じて評価される怠惰な評価集合である.sealed class FunList<out T> {
object Nil: FunList<Nothing>()
data class Cons<out T>(val head: T, val tail: FunList<T>):FunList<T>()
}
sealed class FunStream<out T> {
object Nil: FunStream<Nothing>()
data class Cons<out T>(val head: () -> T, val tail: () -> FunStream<T>) : FunStream<T>()
}
コンビネーションフローの再作成
これはappendTailロジックです.fun <T> FunStream<T>.appendTail(value: T): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Cons({ value }, { FunStream.Nil })
is FunStream.Cons -> FunStream.Cons(head, { tail().appendTail(value) })
}
フィルタとmapをストリーム方式で再実現した.tailrec fun <T> FunStream<T>.dropWhile(f: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> this
is FunStream.Cons -> if (f(head())) {
this
} else {
tail().dropWhile(f)
}
}
fun <T> FunStream<T>.filter(p: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> {
val first = dropWhile(p)
if (first != FunStream.Nil) {
FunStream.Cons({ first.getHead() }, { first.getTail().filter(p) })
} else {
FunStream.Nil
}
}
}
fun <T, R> FunStream<T>.map(f: (T) -> R): FunStream<R> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> FunStream.Cons({ f(head()) }, { tail().map(f) })
}
性能をもう少し比較するfun funListWay(intList: FunList<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun funStreamWay(intList: FunStream<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun main() {
val bigIntList = (1..10000000).toFunList()
var start = System.currentTimeMillis()
funListWay(bigIntList)
println("${System.currentTimeMillis() - start} ms") // 9467 ms
val bigIntStream = (1..10000000).toFunStream()
start = System.currentTimeMillis()
funStreamWay(bigIntStream)
println("${System.currentTimeMillis() - start} ms") // 7 ms
}
既存のFunListに比べて性能が著しく向上した.
無限値の保存
一般的なデータ構造では、無限大の値を格納できません.値を保存するには、評価がメモリに格納され、無限大の値がメモリに格納されないため、評価が必要です.
したがって、無限値を格納するには、ストリームなどの無限値を表す関数を格納する必要があります.
値は格納そのものではなく、値を表す関数を格納します.
次のgenerateFunStream関数はseed値から始まり、Cons
で2番目に増分値を返す関数をheadに入れます.これにより、評価が必要な場合にのみ、メモリに選択的に配置できます.fun <T> generateFunStream(seed: T, generate: (T) -> T): FunStream<T> =
FunStream.Cons({ seed }, { generateFunStream(generate(seed), generate) })
forEachのRamda式でprintlnを加えます.メモリに表現しにくいほど大きい数値があるか、ユーザーがランダムにプログラムを停止しない限り、実行は続行されます.fun main() {
val infiniteVal = generateFunStream(0) { it + 5 }
infiniteVal.forEach { println (it)}
}
tailrec fun <T> FunStream<T>.forEach(f: (T) -> Unit): Unit = when (this) {
FunStream.Nil -> Unit
is FunStream.Cons -> {
f(head())
tail().forEach(f)
}
}
Reference
この問題について([kotlinfp]コレクションを使用してデータ-2を処理する), 我々は、より多くの情報をここで見つけました
https://velog.io/@jay/kotlin-fp-collection-2
テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol
sealed class FunList<out T> {
object Nil: FunList<Nothing>()
data class Cons<out T>(val head: T, val tail: FunList<T>):FunList<T>()
}
sealed class FunStream<out T> {
object Nil: FunStream<Nothing>()
data class Cons<out T>(val head: () -> T, val tail: () -> FunStream<T>) : FunStream<T>()
}
fun <T> FunStream<T>.appendTail(value: T): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Cons({ value }, { FunStream.Nil })
is FunStream.Cons -> FunStream.Cons(head, { tail().appendTail(value) })
}
tailrec fun <T> FunStream<T>.dropWhile(f: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> this
is FunStream.Cons -> if (f(head())) {
this
} else {
tail().dropWhile(f)
}
}
fun <T> FunStream<T>.filter(p: (T) -> Boolean): FunStream<T> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> {
val first = dropWhile(p)
if (first != FunStream.Nil) {
FunStream.Cons({ first.getHead() }, { first.getTail().filter(p) })
} else {
FunStream.Nil
}
}
}
fun <T, R> FunStream<T>.map(f: (T) -> R): FunStream<R> = when (this) {
FunStream.Nil -> FunStream.Nil
is FunStream.Cons -> FunStream.Cons({ f(head()) }, { tail().map(f) })
}
fun funListWay(intList: FunList<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun funStreamWay(intList: FunStream<Int>): Int = intList
.map { n -> n * n }
.filter { n -> n < 1000000 }
.map { n -> n - 2 }
.filter { n -> n < 1000 }
.map { n -> n * 10 }
.getHead()
fun main() {
val bigIntList = (1..10000000).toFunList()
var start = System.currentTimeMillis()
funListWay(bigIntList)
println("${System.currentTimeMillis() - start} ms") // 9467 ms
val bigIntStream = (1..10000000).toFunStream()
start = System.currentTimeMillis()
funStreamWay(bigIntStream)
println("${System.currentTimeMillis() - start} ms") // 7 ms
}
fun <T> generateFunStream(seed: T, generate: (T) -> T): FunStream<T> =
FunStream.Cons({ seed }, { generateFunStream(generate(seed), generate) })
fun main() {
val infiniteVal = generateFunStream(0) { it + 5 }
infiniteVal.forEach { println (it)}
}
tailrec fun <T> FunStream<T>.forEach(f: (T) -> Unit): Unit = when (this) {
FunStream.Nil -> Unit
is FunStream.Cons -> {
f(head())
tail().forEach(f)
}
}
Reference
この問題について([kotlinfp]コレクションを使用してデータ-2を処理する), 我々は、より多くの情報をここで見つけました https://velog.io/@jay/kotlin-fp-collection-2テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol