#function #julia
#функция #джулия
Вопрос:
Если у вас есть функция, есть ли простой или встроенный способ применять ее n
несколько раз или до тех пор, пока результатом не станет что-то конкретное. Так, например, если вы хотите применить sqrt
функцию 4 раза, с эффектом:
julia> sqrt(sqrt(sqrt(sqrt(11231))))
1.791229164345863
вы могли бы ввести что-то вроде:
repeatf(sqrt, 11231, 4)
Комментарии:
1. как насчет рекурсии?
repeatf(fn, x, n) = n == 1 ? fn(x) : repeatf(fn, fn(x), n-1)
Ответ №1:
Я не знаю такой функции, но вы могли бы использовать это
julia> repeatf(f, x, n) = n > 1 ? f(repeatf(f, x, n-1)) : f(x)
julia> repeatf(sqrt, 11321, 4)
106.40018796975878
кроме того, еще удобнее
repeatf(n, f, x...) = n > 1 ? f(repeatf(n-1, f, x...)...) : f(x...)
для функций с более чем одним аргументом
Ответ №2:
Я фанат определения этого ^
оператора для работы с Function
s и Int
s
julia> (^)(f::Function, i::Int) = i==1 ? f : x->(f^(i-1))(f(x))
^ (generic function with 1 method)
julia> (sqrt^1)(2)
1.4142135623730951
julia> (sqrt^2)(2)
1.189207115002721
julia> (sqrt^3)(2)
1.0905077326652577
Как указывает @DNF, поскольку у Julia нет оптимизации хвостовых вызовов,
лучше делать это итеративно;
julia> function (∧)(f::Function, i::Int)
function inner(x)
for ii in i:-1:1
x=f(x)
end
x
end
end
After warmup:
julia> @time((sqrt ∧ 1_000)(20e300)) #Iterative
0.000018 seconds (6 allocations: 192 bytes)
1.0
julia> @time((sqrt ^ 1_000)(20e300)) #Recursive
0.000522 seconds (2.00 k allocations: 31.391 KB)
1.0
#########
julia> @time((sqrt ∧ 10_000)(20e300)) #Iterative
0.000091 seconds (6 allocations: 192 bytes)
1.0
julia> @time((sqrt ^ 10_000)(20e300)) #Recursive
0.003784 seconds (20.00 k allocations: 312.641 KB)
1.0
#########
julia> @time((sqrt ∧ 30_000)(20e300)) # Iterative
0.000224 seconds (6 allocations: 192 bytes)
1.0
julia> @time((sqrt ^ 30_000)(20e300)) #Recursive
0.008128 seconds (60.00 k allocations: 937.641 KB)
1.0
#############
julia> @time((sqrt ∧ 100_000)(20e300)) #Iterative
0.000393 seconds (6 allocations: 192 bytes)
1.0
julia> @time((sqrt ^ 100_000)(20e300)) #Recursive
ERROR: StackOverflowError:
in (::##5#6{Base.#sqrt,Int64})(::Float64) at ./REPL[1]:1 (repeats 26667 times)
The overhead isn't too bad in this case, but that `StackOverflowError` at the end is a kicker.
Комментарии:
1. Это довольно круто. Я взял вашу идею и реализовал ее как повторяющееся приложение в цикле, думая, что, поскольку у Julia нет оптимизации хвостовых вызовов, это было бы более эффективно.
@code_native
Результат намного короче для моего кода, чем у вас, но производительность одинакова (вплоть до сотой доли микросекунды) дляsqrt
применения 1000 раз.2. @DNF это потому, что вам 1000 недостаточно. Я обновлю ответ
3. Я тоже пробовал с 10 ^ 6, и все равно никакой разницы. Как вы думаете, сколько нужно?
4. на какой версии вы работаете? Я использовал v0.5rc0. 10 ^ 6 для меня просто переполнение стека.
5. Хм. Использование версии версии 0.5.
Ответ №3:
[Редактировать: простое решение без итераторов см. Внизу, хотя я предлагаю использовать его и все полезные функции внутри пакета]
С помощью пакета итераторов решением может быть следующее:
julia> using Iterators # install with Pkg.add("Iterators")
julia> reduce((x,y)->y,take(iterate(sqrt,11231.0),5))
1.791229164345863
iterate
выполняет логику композиции (выполните ?iterate
в REPL для описания). В более новой версии итераторов (все еще не помеченных) вызывается функция nth
, которая сделает это еще проще:
nth(iterate(sqrt,11231.0),5)
В качестве примечания, (x,y)->y
анонимная функция может быть хорошо определена с именем, поскольку она потенциально может часто использоваться с reduce
as в:
first(x,y) = x
second(x,y) = y
Теперь,
julia> reduce(second,take(iterate(sqrt,11231.0),5))
1.791229164345863
работает. Кроме того, без рекурсии (которая влечет за собой выделение стека и отходы) и распределение, пропорциональное глубине итерации, это может быть более эффективным, особенно для более высоких значений итерации, чем 5.
Без пакета итераторов простое решение с использованием foldl
julia> foldl((x,y)->sqrt(x),1:4, init=11231.0)
1.791229164345863
Как и прежде, операция сокращения является ключевой, на этот раз она применяется sqrt
, но игнорирует значения итератора, которые используются только для задания количества раз, когда применяется функция (возможно, другой итератор или вектор, чем 1:4
мог бы использоваться в приложении для лучшей читаемости кода)
Комментарии:
1. Круто, так что вы можете сделать это с
reduce
помощью — я пробовал, но он сказал, что ему нужен двоичный оператор…
Ответ №4:
function apply(f, x, n=1)
for _ in 1:n
x = f(x)
end
return x
end
Ответ №5:
Я не нахожу ни одного из приведенных выше ответов удовлетворительным. Все они работают, но ни один из них не является по-настоящему элегантным. Мой личный фаворит — это:
Base.repeat(f::Function, n::Integer) = reduce(∘, fill(f, n))
Конечно, вам даже не нужно определять repeat
, вы можете просто использовать reduce(...)
конструкцию напрямую.
И вот как это будет использоваться в случае исходного примера:
julia> repeat(sqrt, 4)(11231)
1.791229164345863
или
julia> reduce(∘, fill(sqrt, 4))(11231)
1.791229164345863