このような、一定の要素を無限に繰り返したリストが欲しいことがある。無限に続くリストは通常のリストでは扱えないため、ストリームを使わなければならない。Scala 2.12まではストリームのためにStream
を使い、2.13以降はLazyList
を使うことになっている*1*2。
val xs = 1 :: 2 :: 3 :: Nil val xss = ??? xss.take(10) // => LazyList(1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
このようなストリームはどうやったら作れるのだろう?
tl;dr
LazyList.continually(1 :: 2 :: 3 :: Nil).flatten
環境
この記事ではScala 3.3.1を利用しています。2.13系でもだいたい同じように書けるはず。
要素が1つの場合
単一の要素を繰り返したいだけの場合は、LazyList.continually
を利用する:
val xs = LazyList.continually(42) val lis = xs.take(10).toList // => List(42, 42, 42, 42, 42, 42, 42, 42, 42, 42)
シンプルでとても簡単だ。
複数要素の場合(手でループさせる)
冒頭の例のような複数の要素を繰り返す無限リストを作るには、まず「基底」になるLazyList
を作成する:
val xs = 1 :: 2 :: 3:: Nil val xs1 = LazyList.from(xs0) // => LazyList[Int](1, 2, 3)
そして、LazyList
がループするように新たにLazyList
を構成する:
// 定義に自己を含むのでdefにする def xsInfinite: LazyList[Int] = xs1 #::: xsInfinite
こうすると、xs1
が終わるとまた最初からループするようなLazyList[Int]
が作られる。
xsInfinite
は自由にtake
なりzip
なりして利用できる。
xsInfinite.take(10).toList // => List(1, 2, 3, 1, 2, 3, 1, 2, 3, 1) xsInfinite.zip("foo" :: "bar" :: Nil).toList // => (1,foo) :: (2,bar) :: Nil
個人的にはLazyList
のオブジェクトメソッドとして標準ライブラリに含まれてほしいと思う。もしかして既にある??
// こんなかんじになっててほしい LazyList.repeat(1 :: 2 :: 3 :: Nil)
複数要素の場合(flatten
を使うテク)
flatten
を使うことでもう少し楽に記述できる。
val xs = LazyList.continually(1 :: 2 :: 3 :: Nil).flatten xs.take(10).toList // => List(1, 2, 3, 1, 2, 3, 1, 2, 3, 1)
これは、まずcontinually
でLazyList(List(1,2,3), List(1,2,3), ...)
が作られ、さらにflatten
されることでLazyList(1,2,3,1,2,3, ...)
になるという仕組み。一見flatten
するときに処理が止まらなそうに見えるが、LazyList
なのでうまく処理が遅延されて、必要に応じて必要な部分だけflatten
されるようだ。
ちなみにHaskellではcycle
という関数を用意している。