Julia Language
итерируемыми
Поиск…
Синтаксис
- начать (ITR)
- next (itr, s)
- done (itr, s)
- take (itr, n)
- drop (itr, n)
- цикл (ITR)
- Base.product (xs, ys)
параметры
параметр | подробности |
---|---|
За | Все функции |
itr | Итерируемый для работы. |
За | next и done |
s | Состояние итератора, описывающее текущую позицию итерации. |
За | take и drop |
n | Количество элементов, которые нужно взять или удалить. |
За | Base.product |
xs | Итерируемое, чтобы взять первые элементы пар из. |
ys | Итерабельность для принятия вторых элементов пар из. |
... | (Обратите внимание, что product принимает любое количество аргументов, если предусмотрено более двух, он построит кортежи длиной более двух.) |
Новый тип итерации
В Julia, когда цикл через итерируемый объект I
выполняется с синтаксисом for
:
for i = I # or "for i in I" # body end
За кулисами это переводится на:
state = start(I) while !done(I, state) (i, state) = next(I, state) # body end
Поэтому, если вы хотите , I
быть итерацию, вам нужно определить start
, next
и done
методы его типа. Предположим, вы определяете тип Foo
содержащий массив как одно из полей:
type Foo bar::Array{Int,1} end
Мы создаем объект Foo
, выполняя:
julia> I = Foo([1,2,3])
Foo([1,2,3])
julia> I.bar
3-element Array{Int64,1}:
1
2
3
Если мы хотим итерации через Foo
, каждая bar
элементов возвращается каждой итерацией, мы определяем методы:
import Base: start, next, done start(I::Foo) = 1 next(I::Foo, state) = (I.bar[state], state+1) function done(I::Foo, state) if state == length(I.bar) return true end return false end
Обратите внимание: поскольку эти функции принадлежат модулю Base
, мы должны сначала import
их имена, прежде чем добавлять к ним новые методы.
После определения методов Foo
совместим с интерфейсом итератора:
julia> for i in I println(i) end 1 2 3
Объединение ленивых итераций
Стандартная библиотека поставляется с богатой коллекцией ленивых итераций (и библиотеки, такие как Iterators.jl, обеспечивают еще больше). Lazy iterables может быть составлен для создания более мощных итераций в постоянное время. Самые важные ленивые итерации - это выбор и удаление, из которых можно создать множество других функций.
Ленько нарежьте итерируемый
Массивы могут быть нарезаны нотной записью. Например, следующее возвращает 10-15 элементов массива, включая:
A[10:15]
Однако нотация среза не работает со всеми повторами. Например, мы не можем разрезать выражение генератора:
julia> (i^2 for i in 1:10)[3:5]
ERROR: MethodError: no method matching getindex(::Base.Generator{UnitRange{Int64},##1#2}, ::UnitRange{Int64})
Строки для нарезки могут не иметь ожидаемого поведения в Юникоде:
julia> "αααα"[2:3]
ERROR: UnicodeError: invalid character index
in getindex(::String, ::UnitRange{Int64}) at ./strings/string.jl:130
julia> "αααα"[3:4]
"α"
Мы можем определить функцию lazysub(itr, range::UnitRange)
чтобы сделать этот вид нарезки на произвольных итерациях. Это определяется в терминах take
and drop
:
lazysub(itr, r::UnitRange) = take(drop(itr, first(r) - 1), last(r) - first(r) + 1)
Реализация здесь работает, потому что для значения UnitRange
a:b
выполняются следующие шаги:
- сбрасывает первые
a-1
элементы - берет
a
й элемент,a+1
й элемент и т. д., покаa+(ba)=b
й элемент
Всего берутся элементы ba
. Мы можем подтвердить, что наша реализация верна в каждом случае выше:
julia> collect(lazysub("αααα", 2:3))
2-element Array{Char,1}:
'α'
'α'
julia> collect(lazysub((i^2 for i in 1:10), 3:5))
3-element Array{Int64,1}:
9
16
25
Ленько сдвиньте итерируемый кругооборот
Операция circshift
на массивах будет перемещать массив, как если бы это был круг, а затем переиздавать его. Например,
julia> circshift(1:10, 3)
10-element Array{Int64,1}:
8
9
10
1
2
3
4
5
6
7
Можем ли мы сделать это лениво для всех повторений? Мы можем использовать cycle
, drop
и take
итерации для реализации этой функции.
lazycircshift(itr, n) = take(drop(cycle(itr), length(itr) - n), length(itr))
Наряду с ленивыми типами, которые более circshift
во многих ситуациях, это позволяет нам circshift
функцию circshift
для типов, которые в противном случае не поддерживали бы ее:
julia> circshift("Hello, World!", 3)
ERROR: MethodError: no method matching circshift(::String, ::Int64)
Closest candidates are:
circshift(::AbstractArray{T,N}, ::Real) at abstractarraymath.jl:162
circshift(::AbstractArray{T,N}, ::Any) at abstractarraymath.jl:195
julia> String(collect(lazycircshift("Hello, World!", 3)))
"ld!Hello, Wor"
Создание таблицы умножения
Давайте создадим таблицу умножения, используя ленивые итерационные функции для создания матрицы.
Ключевыми функциями для использования здесь являются:
-
Base.product
, который вычисляет декартово произведение . -
prod
, который вычисляет регулярное произведение (как при умножении) -
:
, который создает диапазон -
map
, которая является функцией более высокого порядка, применяющей функцию к каждому элементу коллекции
Решение:
julia> map(prod, Base.product(1:10, 1:10))
10×10 Array{Int64,2}:
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
Лено-оцененные списки
Можно сделать простой лениво-оцененный список, используя изменяемые типы и замыкания . Ленько-оцененный список - это список, чьи элементы не оцениваются при его построении, а скорее при его доступе. Преимущества лениво оцениваемых списков включают возможность бесконечности.
import Base: getindex
type Lazy
thunk
value
Lazy(thunk) = new(thunk)
end
evaluate!(lazy::Lazy) = (lazy.value = lazy.thunk(); lazy.value)
getindex(lazy::Lazy) = isdefined(lazy, :value) ? lazy.value : evaluate!(lazy)
import Base: first, tail, start, next, done, iteratorsize, HasLength, SizeUnknown
abstract List
immutable Cons <: List
head
tail::Lazy
end
immutable Nil <: List end
macro cons(x, y)
quote
Cons($(esc(x)), Lazy(() -> $(esc(y))))
end
end
first(xs::Cons) = xs.head
tail(xs::Cons) = xs.tail[]
start(xs::Cons) = xs
next(::Cons, xs) = first(xs), tail(xs)
done(::List, ::Cons) = false
done(::List, ::Nil) = true
iteratorsize(::Nil) = HasLength()
iteratorsize(::Cons) = SizeUnknown()
Что действительно работает так, как на языке Haskell , где все списки лениво оцениваются:
julia> xs = @cons(1, ys)
Cons(1,Lazy(false,#3,#undef))
julia> ys = @cons(2, xs)
Cons(2,Lazy(false,#5,#undef))
julia> [take(xs, 5)...]
5-element Array{Int64,1}:
1
2
1
2
1
На практике лучше использовать пакет Lazy.jl. Тем не менее, реализация ленивого списка выше проливает свет на важные детали о том, как построить свой собственный итерируемый тип.