Применить функцию повторно определенное количество раз

#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((sqrt1_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((sqrt10_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((sqrt30_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((sqrt100_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