How does recursion work in google sheets defined functions? - google-sheets

Here is an example given by google of a Named function using recursion
=IF(ISERROR(FIND(" ", str)), str, REVERSE_WORDS(RIGHT(str, LEN(str)-FIND(" ", str)))&" "&LEFT(str, FIND(" ",str)-1))
This function will take "1 2 3 4" and the final output will be "4 3 2 1" but I am trying to understand exactly why this is occurring specifically by taking it step by step and this is what I have so far:
Imaginary Debugging
Step 1
iserror false
Step 2
REVERSE_WORDS( "2 3 4" )
Step 3
iserror false
Step 4
REVERSE_WORDS( "3 4" )
Step 5
iserror false
Step 6
REVERSE_WORDS( "4" )
Step 7?
iserror true so return 4?
Step 8?
???
I don't understand exactly how the final output becomes "4 3 2 1" can someone please write out the remaining steps or correct them for me to visualize since I can't debug/evaluate functions in google sheets. I understand &" "&LEFT(str, FIND(" ",str)-1)) will output 1 then 2 then 3 and then not trigger the forth time because of the iserror but it seems like the output should be "4 1 2 3" or just "4 1" or "4 3".

Recursion is a programming strategy, where a function calls itself. If a function is made to call itself, it can call itself indefinitely. For recursion to return a valid value, the exit strategy should be inside the function itself.
IF(ISERROR(FIND(" ", str)), str, REVERSE_WORDS(RIGHT(str, LEN(str)-FIND(" ", str)))&" "&LEFT(str, FIND(" ",str)-1))
Here, the exit strategy is provided ISERROR. During each recursion, the function checks, if FIND() throws a error, if it throws a error, the function returns the string, else it keeps calling itself. Whenever it calls itself, it leaves a value.
REVERSE_WORDS()&" "&LEFT(str, FIND(" ",str)-1))
Here, it leaves LEFT(str, number of characters) on each call. The number of characters is determined by finding the first space in the string. In the case of
1 2 3 4
Imaginary Debugging
Step 1
iserror false
Return REVERSE_WORDS( "2 3 4" )& " 1"
Step 2
REVERSE_WORDS( "2 3 4" )
iserror false
Return REVERSE_WORDS( "3 4" )& " 2"
Step 3
REVERSE_WORDS( "3 4")
iserror false
Return REVERSE_WORDS( "4" )& " 3"
Step 4
REVERSE_WORDS( "4" )
iserror true so
Return "4"
Note that the first return is the real return or the return we receive from calling REVERSE_WORDS( "1 2 3 4" ). That return is
REVERSE_WORDS( "2 3 4" )& " 1"
The REVERSE_WORDS( "2 3 4" ) call in that first return returns:
REVERSE_WORDS( "3 4" )& " 2"
Combined, the first return can be written as,
REVERSE_WORDS( "3 4" )& " 2"& " 1"
If we keep substituting(recursing), we get,
"4"&" 3"&" 2"&" 1"
which is
4 3 2 1
Why isn't it 1 2 3 4?
Because of the order of operation. If we change
REVERSE_WORDS()&" "&LEFT()
to
LEFT()&" "&REVERSE_WORDS()
We'll get 1 2 3 4

#1 =IF(ISERROR(FIND(" ", str)), str,
#2 REVERSE_WORDS(RIGHT(str, LEN(str)-FIND(" ", str)))
#3 &" "&
#4 LEFT(str, FIND(" ",str)-1))
#1 If there is no space in the input, return the input.
#4 Extract the first element from the input and place it at the end of the output
#2 Extract the string without the first element from the input and recur
#3 Concat the return value of recursion (#2) and the first element (#4) with a space
1st level:
Step
Result
Input
1 2 3 4
#1
False
#4
1
#2
REVERSE_WORDS("2 3 4")
#3
Return of 2nd level 1
2nd level:
Step
Result
Input
2 3 4
#1
False
#4
2
#2
REVERSE_WORDS("3 4")
#3
Return of 3rd level 2
3rd level:
Step
Result
Input
3 4
#1
False
#4
3
#2
REVERSE_WORDS("4")
#3
Return of 4th level 3
4th level:
Step
Result
Input
4
#1
TRUE
#4
4
#2
#3
4
Finally, the return values are concatted as follows:
Level
Return
4th
4
3rd
4 3
2nd
4 3 2
1st
4 3 2 1

Related

Why does SCAN/LAMBDA give unexpected results?

It is very possible that I dont understand the lambda logic or do I? I have dataset A2:A5 like:
1
3
6
10
If I do: =SCAN(0, A2:A5, LAMBDA(aa, bb, aa+bb)) i get:
1
4
10
20
If I do: =SCAN(0, A2:A5, LAMBDA(aa, bb, ROW(bb)-1)) I get
1
2
3
4
if I run: =SCAN(0, A2:A5, LAMBDA(aa, bb, (aa+bb)*(ROW(bb)-1))) the result is
1
8
42
208
Why there is 42 and 208 ? How this results in such values? How can it be 42 and 208 ?
Expected result is
1
8
30
80
And I can get it with:
=ArrayFormula(SCAN(0, A2:A5, LAMBDA(aa, bb, aa+bb))*(ROW(A2:A5)-1))
But not with
=SCAN(0, A2:A5, LAMBDA(aa, bb, (aa+bb)*(ROW(bb)-1)))
SCAN is a great intermediate results function. To understand how SCAN operates, you need to understand how REDUCE operates. The syntax is:
=REDUCE(initial_value, array, LAMBDA(accumulator, current_value, some_function()))
Going through =SCAN(0, A2:A5, LAMBDA(aa, bb, (aa+bb)*(ROW(bb)-1))) step by step,
A2:A5 is 1,3,6,10
Step 1:
aa = 0(initial_value)
bb = 1(current_value:A2)
Result((aa+bb)*(ROW(bb)-1)): (0+1)*(2-1)=1
Step 2:
aa = 1(accumulator(previous return value))
bb = 3(current_value:A3)
Result((aa+bb)*(ROW(bb)-1)): (1+3)*(3-1)=8
Step 3:
aa = 8(accumulator(previous return value))
bb = 6(current_value:A4)
Result((aa+bb)*(ROW(bb)-1)): (8+6)*(4-1)=42
Step 4:
aa = 42(accumulator(previous return value))
bb = 10(current_value:A5)
Result((aa+bb)*(ROW(bb)-1)): (42+10)*(5-1)=52*4=208
aa stores the result of the previous calculation, so you have:
above answers pretty much contain all so I will add only this:
you probably expected that by doing (aa+bb)*(ROW(bb)-1) you will get:
(aa+bb)
*
(ROW(bb)-1)
1
*
1
=
1
4
*
2
=
8
10
*
3
=
30
20
*
4
=
80
but that's not how it works. to get your expected result and by not using your formula where ROW is outside of SCAN:
=ArrayFormula(SCAN(0, A2:A5, LAMBDA(aa, bb, aa+bb))*(ROW(A2:A5)-1))
you would need to do:
=INDEX(MAP(SCAN(0, A2:A5, LAMBDA(aa, bb, (aa+bb))), ROW(A2:A5)-1, LAMBDA(cc, dd, cc*dd)))
where cc is the entire SCAN and dd is ROW(A2:A5)-1 eg. first do the running total and then multiplication, which is not so feasible length-wise.
or shorter but with SEQUENCE:
=MAP(SCAN(0, A2:A5, LAMBDA(aa, bb, (aa+bb))), SEQUENCE(4), LAMBDA(cc, dd, cc*dd))

How to replace text in a column after filter

After I apply a filter in A1 I get a result like:
A B C D
Uno 1 3 1
Uno 1 2 2
Tres 2 2 3
I want to replace "Uno" for "Dos" in the same cells.
A B C D
Dos 1 3 1
Dos 1 2 2
Tres 2 2 3
I have seen SUBSTITUTE() and REPLACE() functions, but, they put the replacement in another cells.
I have tried:
A1: SUBSTITUTE(A1:A)
try:
=ARRAYFORMULA(REGEXREPLACE(IFERROR(FILTER(K:N, L:L="ok"), " "), "Uno", "Dos"))
REGEXREPLACE(range, "replace this", "to this")
ARRAYFORMULA - for continuity
=ARRAYFORMULA(REGEXREPLACE(REGEXREPLACE(IFERROR(FILTER(K:N, L:L="ok"), " "),
"Uno", "Dos"), "Tres", "Cuatro")

Formula with reference to header (factor list)

I would like to do something like this (Formula to find the header index of the first non blank cell of a range in Excel?) except that I want to capture all the nonblank cells.
An application of what I am expecting would produce column "prod"
2 3 5 7 11 13 | prod |
2 1 2^1
3 1 3^1
4 2 2^2
5 1 5^1
6 1 1 2^1 3^1
7 1 7^1
8 3 2^3
9 2 3^2
10 1 1 2^1 5^1
11 1 11^1
12 2 1 2^2 3^1
13 1 13^1
14 1 1 2^1 7^1
15 1 1 3^1 5^1
16 4 2^4
I wouldn't mind a result with multiple separators ie. 6= 2^1*3^1**** , as they could be removed.
This user defined function will stitch together the header value with the range value.
In a standard public module code sheet:
Option Explicit
Function prodJoin(rng As Range, hdr As Range, _
Optional op As String = "^", _
Optional delim As String = " ")
Dim tmp As String, i As Long
For i = 1 To rng.Count
If Not IsEmpty(rng.Cells(i)) Then
tmp = tmp & delim & hdr.Cells(i).Text & op & rng.Cells(i).Text
End If
Next i
prodJoin = Mid(tmp, Len(delim) + 1)
End Function
On the worksheet as,
If you absolutely must use worksheet functions then stitch 6 conditional concatenations together.
=TRIM(IF(B2, B$1&"^"&B2&" ", TEXT(,))&
IF(C2, C$1&"^"&C2&" ", TEXT(,))&
IF(D2, D$1&"^"&D2&" ", TEXT(,))&
IF(E2, E$1&"^"&E2&" ", TEXT(,))&
IF(F2, F$1&"^"&F2&" ", TEXT(,))&
IF(G2, G$1&"^"&G2&" ", TEXT(,)))

Doesn't remove duplicate elements from array

I am trying to take input from user in an array .And want to remove duplicate elements but the result is weird .I don't have to use uniq or any other ruby method.Here is my code
digits = []
digits = gets.chomp.to_i
k= digits & digits
puts k
input - 1 2 3 4 1 2 3 <br>
Required output- 1 2 3 4<br>
Getting output 1
gets.chomp returns string "1 2 3 4 1 2 3"
Then you call to_i on that string:
"1 2 3 4 1 2 3".to_i => 1
Consequentially 1 & 1 => 1
You should do this:
digits = gets.chomp.split(' ').map(&:to_i)
k = digits & digits
puts k

can we use foreach loop like this in tcl?

I have two list:
set lista {5 6}
set listb {8 9}
and the following code is used to loop the two lists:
foreach listaelement listbelement $lista $listb {
puts $listaelement &listbelement
}
how can we use foreach to achieve the output is:
first element from lista, first element from listb,
first element from lista, second element from listb,
second element from lista, first element from listb,
second element from lista, second element from listb,
5 8
5 9
6 8
6 9
Just nesting the loops and using -nonewline should give the output you desire:
foreach listelementa $lista {
foreach listelementb $listb {
puts -nonewline "$listelementa $listelementb "
}
}
puts ""
NB: You need to use quotes in the puts statement to stop Tcl interpreting the first argument as a channel id.
Tcl does allow you to reference multiple lists in a foreach statement
foreach listelementa $lista listelementb $listb {
puts -nonewline "$listelementa $listelementb "
}
puts ""
However this gives 5 8 6 9 as its output - not what you require.
Edit: I'm pretty sure when I answered the question that the output was formatted as 1 line, if you actually want 1 line for each pair then you don't need the -nonewline on the puts and the trailing spaces and final puts can also go.
set lista {5 6}
set listb {8 9}
set indexb [expr ([llength $listb ] -1)]
foreach listano $lista {
for {set i 0 } {$i <= $indexb } {incr i} {
set element [lindex $listb $i]
puts "element is $listano $element"
}
}
element is 5 8
element is 5 9
element is 6 8
element is 6 9

Resources