Emulate Jelly's tie-scan

The golfing language Jelly has a built-in ƭ called "tie", that cycles through a list of functions every time it's called. For example, +×ƭ will switch between + (addition) and × (multiplication) every time it's called.

ƭ can be somewhat usefully combined with the built-in \ called "scan", which cumulatively reduces a list by a function (for example, calling +\, scan by addition, on the list [1, 2, 3, 4] would give [1, 3, 6, 10]). The function +×ƭ\ will scan a list alternately between addition and multiplication - calling it on the list [1, 2, 3, 4, 5] gives [1, 3 = 1 + 2, 9 = 3 * 3, 13 = 9 + 4, 65 = 13 * 5].

ƭ can take more than two arguments - for example, +×_ƭ\ cyclically reduces by +, ×, and _ (subtraction)*. Calling it on the list [1, 2, 3, 4, 5, 6, 7] would result in [1, 3 = 1 + 2, 9 = 3 * 3, 5 = 9 - 4, 10 = 5 + 5, 60 = 10 * 6, 53 = 60 - 7].

Your challenge is to, given a list of at least 2 black-box dyadic not-necessarily-commutative functions, along with a list of at least 2 positive integers, emulate the effect of ƭ\ on them. Specifically, you should call the first function on the first two elements, the second function on the result of the previous and the third element, and so on, cycling around the list of functions as necessary and outputting intermediate values. You may assume that the integer list is strictly longer than the function list, and that all intermediate results are positive integers.

* note that this isn't the exact syntax, ƭ taking 3 or more arguments has to be prefixed with the number of arguments, for example +×_3ƭ. It doesn't matter for the purposes of this challenge.

Testcases

Here, I'm using the symbols +, *, -, /, and ^ (exponentiation) to represent the functions that take two arguments and apply those operations to them. Also, f is a function that takes x, y and returns x^2 + y.

[+, *], [1, 2, 3, 4] => [1, 3, 9, 13]
[*, +], [9, 2, 8, 6, 3] => [9, 18, 26, 156, 159]
[-, +], [18, 4, 12, 2, 16, 18] => [18, 14, 26, 24, 40, 22]
[+, *, +], [12, 5, 3, 19, 2, 5, 4] => [12, 17, 51, 70, 72, 360, 364]
[f, +], [1, 2, 3, 4, 5, 6, 7] => [1, 3, 6, 40, 45, 2031, 2038]
[^, /, -], [2, 6, 8, 2, 4, 9, 44] => [2, 64, 8, 6, 1296, 144, 100]
[+, *, -, /], [1, 2, 3, 4, 5, 6, 7, 4, 9, 10] => [1, 3, 9, 5, 1, 7, 49, 45, 5, 15]

• Brownie points for tying/beating my 8-byte Vyxal answer Apr 3 at 19:54

Python 3.8, 55 bytes

lambda o,x,*v:[x]+[x:=a(x,n)for a,n in zip(o*len(v),v)]


An unnamed function that accepts a list of functions and some values and returns the cumulative list.

Try it online!

05AB1E, 8 bytes

(could have been 7 bytes, but there is a bug in the cumulative reduce-by builtins)

Å»I¾è.V¼


05AB1E lacks functions, but it can evaluate strings to mimic the behavior of functions.
First input is the list of integers; second it the list of function-strings.

Explanation:

Å»        # Cumulative left-reduce the first (implicit) input-list of integers:
I       #  Push the second input-list of function-strings
¾è     #  Pop and leave the (0-based modular) ¾'th function-string
#  (¾ is 0 by default)
.V   #  Evaluate it as 05AB1E code
¼  #  Increase ¾ by 1
# (after which the resulting list is output implicitly)

• Is the cumulative reduce by bug present in both versions of 05AB1E?
– Neil
Apr 6 at 23:32
• @Neil No, because the legacy version of 05AB1E lacks the cumulative builtins completely. :) They were added in the Elixir rewrite. Apr 7 at 11:50

(f:g)#(l:m:n)=l:(g++[f])#(f l m:n)
_#l=l


Try it online!

f#(h:t)=scanl(flip id)h$zipWith flip(cycle f)t  Try it online! Even more functional approach, 46 bytes ((map snd.scanl1(fmap.uncurry id)).).zip.cycle  Try it online! Cool fmap usage brought to you by Wheat Wizard. • You can go completely pointfree for 43 bytes Apr 4 at 18:03 • @WheatWizard That fmap is pretty sick but to get the right output it goes back to 47 bytes. :( Apr 4 at 20:52 • Ah it's a scan. That's too bad, but in that case here's it in 46 bytes. Apr 4 at 21:30 Wolfram Language (Mathematica), 39 36 bytes -1 from totallyhuman FoldList[h=#;Last[h=##2|#&@@h]@##&]&  Try it online! Input [funcs][list]. Reusing f[funcs] resumes the cycle wherever the last call left off, as should be expected. However, calling f anew invalidates all previous f[funcs]s. We could fix that by uncurrying for +3 bytes: Try it online!  h=#;Last[h=##2|#&@@h]@##& tie (each call: rotate left and call the last function) FoldList[ ] scan  • Apr 5 at 4:25 JavaScript (Node.js), 46 bytes a=>b=>b.map((c,i)=>h=i--?a[i%a.length](h,c):c)  Try it online! VyxalWr, 61 bitsv2, 7.625 bytes ¨Sḣ(:n?†  Try it Online! Bitstring: 0011000000110111100101111100011001011110101000100101000001110  Expects the numbers, then the functions on the stack. Explained ¨Sḣ(:n?†­⁡​‎‎⁪⁡⁪⁠⁪⁡⁪‏⁠‎⁪⁡⁪⁠⁪⁢⁪‏‏​⁡⁠⁡‌⁢​‎‎⁪⁡⁪⁠⁪⁣⁪‏‏​⁡⁠⁡‌⁣​‎‎⁪⁡⁪⁠⁪⁤⁪‏‏​⁡⁠⁡‌⁤​‎‎⁪⁡⁪⁠⁪⁢⁡⁪‏‏​⁡⁠⁡‌⁢⁡​‎‎⁪⁡⁪⁠⁪⁢⁢⁪‏‏​⁡⁠⁡‌⁢⁢​‎‎⁪⁡⁪⁠⁪⁢⁣⁪‏‏​⁡⁠⁡‌⁢⁣​‎‎⁪⁡⁪⁠⁪⁢⁤⁪‏‏​⁡⁠⁡‌⁢⁤​‎‏​⁢⁠⁡‌­ ¨S # ‎⁡Set the input source to the list of functions ḣ # ‎⁢Push the head of the numbers list and then the rest ( # ‎⁣For each number in the rest: : # ‎⁤ Duplicate the top of the stack n # ‎⁢⁡ Push the number ? # ‎⁢⁢ Get the next function (cyclical input) † # ‎⁢⁣ Call the function. The r flag swaps the arguments before executing # ‎⁢⁤The W flag wraps the stack before printing 💎  Created with the help of Luminespire. R, 69 bytes \(L,v,k=v[1])for(i in c(v[-1],1))k=L[[F<-F%%length(L)+1]](print(k),i)  Attempt This Online! • 61 bytes Apr 5 at 0:36 • tantalizingly close 55 bytes but is off by one cycle, unfortunately. Apr 5 at 2:25 • 56 bytes by modifying the 55 byte answer; I haven't golfed in ages! Apr 5 at 2:28 • @Giuseppe - I think you should post it yourself :) Apr 5 at 6:15 • Posted! You're welcome to the 61-byter if you want it. Apr 5 at 13:20 PowerShell Core, 65 bytes param($n,$o)$n|%{if($p){$p=&$o[$i++%$o.count]$p $_}else{$p=$_}$p}


Try it online!

Ruby, 47 44 bytes

->o,l{k=1;l.map{|x|l=l*0!=0?x:o[k^=1][l,x]}}


Try it online!

R, 56 55 bytes

\(L,v)Reduce(\(x,y){L<<-c(L[-1],?<-el(L));x?y},v,,,T)


Attempt This Online!

Test harness borrowed from pajonk's answer; -1 byte thanks to pajonk.

A rare instance of golfing with the <<- operator, which in this case causes L to be re-defined in the parent environment; this is a few bytes more succinct than using a for loop and manually cycling through both lists.

• Good to see you back :) Also, -1 byte Apr 5 at 18:20

cr<<<lz(m<U id)<<zcz


Attempt This Online!

Explanation

• zcz zips the lists together looping the first one to the length of the second.
• lz creates a left-hand scan
• m<U id apply the function to the both values.
• cr get the value discarding the leftover function.

In regular haskell this would be

map snd ... foldl1 (fmap . uncurry id) ... zip . cycle


Where (...) is the blackbird combinator.

Reflection

I'm quite happy with this. It's nice to see zcz getting used, especially because that's an operation I ported over from Jelly in the first place.

m<U id seems like it might be too long, but it's a very niche operation.

• There's already an operation f <% g for U f < g. Maybe there could be a f %< g for f < U g. It would be helpful here, but this is a pretty synthetic operation.
• U id could itself be potentially useful, even if a little niche.

That's all I have for local optimizations of this. However, the overall operation appears to be useful.

• The "tie" operation is not usable in Haskell as a pure function. But the scan and fold of it are. So the scan and fold of it could be added wholesale. The original challenge claims its useful, so I'll take their word for it.
• The tie operation could be implemented as acting within a state-monad. In this case I would need a scanM operation to call it. scanM could be more generally useful anyway so it should be added.

Pip-x, 16 bytes

FibYPy?(a@Uvyi)i


Takes the list of functions and the list of integers as command-line arguments. Attempt This Online!

Explanation

FibYPy?(a@Uvyi)i
; y is ""; v is -1 (implicit)
; Eval both command-line args as Pip objects (-x flag)
Fi                ; For each integer i
b               ; in the second argument (list of ints):
y?           ;  Is y truthy?
;  If so (all iterations after the first):
Uv      ;   Increment v
a@        ;   Use it to index cyclically into first argument (list of funcs)
(    yi)   ;   Call that function on the arguments y and i
;  If not (first iteration):
i  ;   Use i unchanged
YP             ;  Print that value and yank it into y for the next iteration


tinylisp 2, 78 bytes

(d G(\(A D N)(c A(? N(G((h D)A(h N))(,(t D)(] 1 D))(t N))(
(\(D N)(G(h N)D(t N


The first line defines a helper function G; the second line is an anonymous function that performs the task. Try It Online!

Explanation

First, the main function:

(\(D N)(G(h N)D(t N)))
(\                   ) ; Lambda function
(D N)                ; that takes two args, D (list of Dyads) and N (list of Numbers):
(G           )  ;  Call G with the following arguments:
(h N)         ;   Head of N
D        ;   D
(t N)   ;   Tail of N


The recursive helper function does the actual work:

(d G(\(A D N)(c A(? N ... ()))))
(d G                           ) ; Define G to be
(\                        )  ; a lambda function
(A D N)                    ; that takes an accumulator A, plus D & N as before:
(c A            )   ;  Cons A to...
(? N       )    ;   If N is not empty,
...        ;   recursive call (see below)
()     ;   else, nil

(G((h D)A(h N))(,(t D)(] 1 D))(t N))
(G                                 ) ; Call G recursively on these args:
((h D)      )                      ;  New accumulator value: call first function in D
A                            ;   on the old accumulator
(h N)                       ;   and the first number in N
(,            )       ;  New list of dyads: concatenate
(t D)               ;   the tail of D
(] 1 D)        ;   with a list of the first 1 elements of D
(t N)  ;  New list of numbers: tail of N


tinylisp, 84 bytes

(d G(q((A D N P)(c A(i N(G((h P)A(h N))D(t N)(i(t P)(t P)D))(
(q((D N)(G(h N)D(t N)D


Try it online!

Works similarly, but we avoid expensive library functions like concat by using a fourth argument, P, a partial list of dyads. Whenever P becomes empty, we refill it from D.