| Bytes | Lang | Time | Link |
|---|---|---|---|
| 046 | JavaScript ES11 | 241211T221621Z | Arnauld |
| 048 | Wolfram Language Mathematica | 241212T092232Z | Greg Mar |
| 049 | Charcoal | 241212T015342Z | Neil |
| 168 | R | 241212T034943Z | Eonema |
| 146 | Python3 | 241212T024520Z | Ajax1234 |
JavaScript (ES11), 46 bytes
-2 thanks to @Shaggy
-2 thanks to @tsh
Expects (n,d) as BigInts and returns [n',d'].
f=(n,d)=>[N]=n%d?[f(d,n%d)[1]+n/d*N,N]:[~-n,d]
Method
This is based on the relationship between Stern–Brocot trees and continued fractions.
Namely, if a rational number is represented by the continued fraction expression:
$$[a_0;a_1,a_2,\dots,a_k]$$
then its parent in the Stern-Brocot tree is represented by:
$$[a_0;a_1,a_2,\dots,a_k-1]$$
The function builds the continued fraction of the input \$n/d\$ during its recurse phase and then generates a new rational \$n'/d'\$ during its decurse phase using almost the same continued fraction, only with the last term decremented.
This algorithm is, quite surprisingly, very suitable for golfing.
Commented
f = ( // f is recursive function taking:
n, // n = numerator
d // d = denominator
) => //
[N] = // save in N the new numerator
n % d ? // if d is not a divisor of n:
[ // the numerator is obtained by:
f( // doing a recursive call with:
d, // d as the numerator
n % d // n % d as the denominator
)[1] + // taking the denominator returned by f
n / d * N, // adding floor(n / d) * N
N // use N as the denominator
] // NB: the variable N, which is defined in
// the global scope, is first set by
// the last call and updated during the
// 'decurse' phase, up to the root call
: // else (end of recursion):
[ //
~-n, // use n - 1 as the numerator
d // use d = 1 as the denominator
] //
Wolfram Language (Mathematica), 48 bytes
(r=ResourceFunction@"SternBrocot")@Floor[r@#/2]&
... yeah, there's a builtin (Mathematica has to load the builtin in a way that TIO can't handle), which we name r because it's used twice. The first use, r@#, takes the input rational number and returns its index in the flattened Stern–Brocot sequence. Conveniently, the index of the parent of a node in a binary tree is just half (rounded down) of the index of the node itself, which Floor[r@#/2] computes; then r applied to this new index returns the rational number at that index.
Wolfram Language (Mathematica), 67 bytes
FromContinuedFraction@Append[Most@#,Last@#-1]&@ContinuedFraction@#&
A direct port of Arnauld's solution—please upvote that one (which is even shorter than this ported version)!
Charcoal, 52 49 bytes
≔E²NθW¬№υθ≔⊞OEυ⟦§κ¹⁻Σκ⊗﹪§κ⁰§κ¹⟧E²¦¹υI§υ±⍘Φ⍘Lυ²⊖κ²
Try it online! Link is to verbose version of code. Explanation: Works by generating the Stern-Brocot sequence (formula now visible on my answer to Output the nth rational number according to the Stern-Brocot sequence) until the desired term and then deleting the second bit of the 1-indexed position of that term to find the 1-indexed position of the parent term.
≔E²Nθ
Input the desired term.
W¬№υθ
Repeat until the desired term is found.
≔⊞OEυ⟦§κ¹⁻Σκ⊗﹪§κ⁰§κ¹⟧E²¦¹υ
For each term, calculate the next term, appending 1/1 to the end. This makes the program O(n²), which means that the last test case now times out on TIO, but that's code golf for you.
I§υ±⍘Φ⍘Lυ²⊖κ²
Index to find the parent term. (This works because the terms are in reverse order and negative indices are -1-based.)
R, 168 bytes
f=\(x,m=diag(2)[2:1,],a=cbind(m,m[,-1]+m[,-ncol(m)]),n=a[,order(a[1,]/a[2,])],p=n[,which(apply(n,2,identical,x))+c(-1,1)])`if`(ncol(p),p[,which.max(colSums(p))],f(x,n))
Python3, 146 bytes
u=lambda a,b:[a[0]+b[0],b[1]+a[1]]
def f(n):
q=[([0,1],[1,1],[1,0],0)]
for a,b,c,p in q:
if b==n:return p
q+=[(a,u(a,b),b,b),(b,u(b,c),c,b)]