Add `Iterators.cycle(iter, n)` (#47354)
At present `Iterators.repeated(iter, n)` is the lazy cousin of
`fill(iter, n)`, but there is no iterator for `repeat(iter, n)`. This PR
proposes that `Iterators.cycle(iter, n)` should fill that role.
This relates to the one-argument form in the same way as
`Iterators.repeated`. That is, `cycle(iter)` means `cycle(iter, Inf)` in
the same way that `repeated(iter)` means `repeated(iter, Inf)`... or
would be if Inf were an integer.
The implementation uses `flatten(repeated(xs, n))`. It could instead use
`take(cycle(xs), n * length(xs))` but that only works when the contents
has known length. `take(cycle...)` tends to be faster, perhaps it should
be used when possible? Some timing below. But perhaps this detail is a
secondary question.
<details>
```julia
julia> takecycle(x, n::Int) = Iterators.take(Iterators.cycle(x), n * length(x));
julia> flatrep(x, n::Int) = Iterators.flatten(Iterators.repeated(x, n)); # as in PR, first commit
julia> takecycle(1:10, 100) |> length
1000
julia> flatrep(1:10, 100) |> Base.haslength # and won't be helped by 47353
false
julia> @btime collect(takecycle(1:10, 100));
min 1.642 μs, mean 2.554 μs (1 allocation, 7.94 KiB)
julia> @btime collect(flatrep(1:10, 100));
min 6.617 μs, mean 9.107 μs (6 allocations, 21.86 KiB)
julia> flatrep(Tuple(1:10), 100) |> Base.haslength # behaves better with tuples, but not faster:
true
julia> @btime collect(takecycle($(Tuple(rand(10))), 100));
min 1.100 μs, mean 1.977 μs (1 allocation, 7.94 KiB)
julia> @btime collect(flatrep($(Tuple(rand(10))), 100));
min 10.458 μs, mean 11.220 μs (1 allocation, 7.94 KiB)
```
</details>