勉強のためにListのAPIはなるべく使わない方向。
// P17 (*) Split a list into two parts. // The length of the first part is given. Use a Tuple for your result. // Example: // scala> split(3, List('a, 'b, 'c, 'd, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) // res0: (List[Symbol], List[Symbol]) = (List('a, 'b, 'c),List('d, 'e, 'f, 'g, 'h, 'i, 'j, 'k)) // tail revursion but too slow def split_tail_too_slow[T](nth: Int, xs: List[T]) = { def recursive(n: Int, rest: List[T], acc: (List[T], List[T])): (List[T], List[T]) = rest match { case Nil => throw new NoSuchElementException case l if (n == 0) => (acc._1, l) case h :: t => recursive(n - 1, t, (acc._1 ::: List(h), Nil)) // add to tail -> O(n) } if(nth <= 0) throw new IllegalArgumentException else recursive(nth, xs, (Nil, Nil)) } // tail revursion def split_tail_fast[T](nth: Int, xs: List[T]) = { def recursive(n: Int, rest: List[T], acc: (List[T], List[T])): (List[T], List[T]) = rest match { case Nil => throw new NoSuchElementException case l if (n == 0) => (acc._1.reverse, l) case h :: t => recursive(n - 1, t, (List(h) ::: acc._1 , Nil)) // add to head -> O(1) } if(nth <= 0) throw new IllegalArgumentException else recursive(nth, xs, (Nil, Nil)) } def split_take[T](nth: Int, xs: List[T]): (List[T], List[T]) = (xs.take(nth) , xs.takeRight(xs.length - nth)) val l = (1 to 1000000).toList println(split_take(100000, l)) println(split_tail_fast(100000, l)) println(split_tail_too_slow(100000, l)) // 超遅い3方式で書いてみた。
split_tail_too_slowはリストの最後尾に追加する。追加の度にリストをたどらなければならないので、再帰の度に遅くなる。O(n)
split_tail_fastはリストの先頭に追加するため、リストの要素数に関係なくO(1)
split_tail_takeはList APIを使ったバージョン。
100万個のリストで(10万, 90万)で分割(split_take以外の2つは10万回再帰)してみたところ、
split_tail_too_slowはいつまでたっても終わらなかった。
末尾再帰にすりゃいいってもんじゃないのね。勉強になりました。
解答例
ふむふむ、splitAtってのがあるのね。
match式にタプルを使えばいいのか。if式がいらなくなるのね。なるほど。
No comments:
Post a Comment