How to determine the order of a substring in xls - excel

In xls I have a single cell with multiple arguments in text format like this:
a;c;d;b
I want a formula which tells me the order, so for Input
a it should return 3
c it should return 2
d it should return 1
b it should return 0
the substrings "a", "b" etc. have multiple letters.

First of all take the string value of the cell, then split it based on the semicolons. After that you have an array (or a list) of strings.
Then sort this list as you need.
I cannot figure out your sort order - it looks like you made it up.
So maybe you just need to assemble the strings from the list in your order, but that is what string concatenation should solve.

You can try the following on cell D2:
=LET(split, TEXTSPLIT($A$1,";"), cols, COLUMNS(split),
pos, SEQUENCE(1, cols, cols-1,-1), XLOOKUP(C2:C5, split, pos))
Here is the output:
where the Lookup column have the input values for testing purpose. If you enter a letter that doesn't exist in cell A1, it returns #N/A.
Explanation
LET function is used to avoid repetitions of the same calculations in the formula.
From your requirement it seems you want to return the position in reverse order starting from the number of letters minus 1 and ending in 0.
split variable TEXTSPLIT($A$1,";") generates the following output (column wise):
a c d b
The cols variable COLUMNS(split) calculates the number of columns of the split array. In our case 4.
The pos variable represents the array positions and the output we are looking for, SEQUENCE(1, cols, cols-1,-1) would generate the following output:
3 2 1 0
and XLOOKUP(C2:C5, split, pos) generates the final result.
Notes
If you don't have TEXTSPLIT available in your excel version, you can try instead:
=FILTERXML("<t><s>"&SUBSTITUTE(A1,";","</s><s>")&"</s></t>","//s")
Remember FILTERXML has also some constraints: it is not available in Excel for the web and Excel for Mac. Since the solution generates column-wise arrays, you need to transpose the output of FILTERXML, i.e. TRANSPOSE(FILTERXML(...)).
An alternative if you can't use FILTERXML is the following one. Adapted the response of the question: Split a string (cell) in Excel without VBA (e.g. for array formula) provided by: #Carble:
=LET(txt,A1,del, ";",length, LEN(txt)+1,
items, length-LEN(SUBSTITUTE(txt,";","")),
seq, SEQUENCE(items,,0), findSpaces, FIND(" ",SUBSTITUTE(txt,del," ",seq)),
startTxt, IFERROR(findSpaces+1,1),startDel1, IFERROR(findSpaces,0),
startDel2, IFERROR(FIND(" ",SUBSTITUTE(txt,del," ",seq+1)), length),
MID(txt,startTxt,startDel2-startDel1-1)
)
If your version doesn't allow to use LET, then just replace the value of the variable defined in the formula. It will require SEQUENCE (available from O365 2021). Not having even SEQUENCE will make it very hard. A helper columns will be required to generate the sequences.

Related

Numbering Based on Condition(s)

I'm trying to create auto numbering for Agents that are currently present and has numbers including zeroes 0 in 3rd or 4th column(zero meaning they don't get any stats but they are present)
Agents who has TEXT Value in the 3rd or 4th column are those who are not present (Ex: A = Absent, SL = Sick Leave, VL = Vacation Leave). Meaning, they should not be counted, therefore their value on 1st column should be blank, and therefore this should not stop the auto numbering for the rest of the agents below and should continue the count in sequence.
Can anyone help create formula that would fill the numbers on the 1st column automatically for those agents that are present and has value including 0 on column 3 or 4 (stats 1 or stats 2)?
To give more idea, I'm trying to show the current total number of agents who are currently present in this situation and will count their stats, and exclude all other agents who are not present and should not be counted.
Thank you!
Sequence Two Numeric Columns
Single Cell
In cell A3, a basic not spilling formula would be...
=IF(AND(ISNUMBER(C3),ISNUMBER(D3)),MAX(A$2:A2)+1,"")
... with the condition of a string in cell A2.
Without any conditions, you could try an improved version, similar to one of David Leal's suggestions:
=IF(AND(ISNUMBER(C3),ISNUMBER(D3)),
SUM(ISNUMBER(C$3:C3)*ISNUMBER(D$3:D3)),"")
Spill
In cell A3 you could use the following:
=LET(Data1,C3:C13,Data2,D3:D13,
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,""))
Line1: the inputs ('constants'), the same-sized single-column ranges
Line2: the zeros and ones, where the ones present the data of interest
Line3: the formula to replace the ones with the sequence and the zeros (errors due to division by zero) with an empty string
Converted to a LAMBDA, it could look like the following:
=LAMBDA(Data1,Data2,LET(
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,"")))(C3:C13,D3:D13)
Since it's such a long formula, you could create your own Lambda function by using this part...
=LAMBDA(Data1,Data2,LET(
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,"")))
... to define a name, e.g. SeqNumeric, when in the same cell, you could use
it simply with...
=SeqNumeric(C3:C13,D3:D13)
... instead.
Now you can use the function like any other Excel function anywhere in the workbook.
The Path
F3 =ISNUMBER(C3:C13)*ISNUMBER(D3:D13) - multiply: zeros-no, ones-yes
G3 =SCAN(0,F3#,LAMBDA(a,b,a+b)) - use the 'LAMBDA' helper function 'SCAN'
H3 =G3#/F3# - divide the 'scan' result by the zeros and ones
I3 =IFERROR(H3#,"") - convert the '#DIV/0!' errors to empty strings
The translation of the SCAN part could be something like the following:
Set the initial result a to 0.
Create a new array of the size of the initial array in F3#.
Loop through each element of the initial array, write its value to b, and replace a with the sum of a+b.
Write (the accumulated) a to the current element of the new array and repeat for the remaining elements of either array.
Return the new array.
Combine all of it in a LET.
J3 =LET(Data1,C3:C13,Data2,D3:D13,
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,""))
Convert to LAMBDA.
K3 =LAMBDA(Data1,Data2,LET(
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,"")))(C3:C13,D3:D13)
Copy the first part of the LAMBDA (note how it results in a #CALC! error since no parameters are supplied)...
L3 =LAMBDA(Data1,Data2,LET(
Data,ISNUMBER(Data1)*ISNUMBER(Data2),
IFERROR(SCAN(0,Data,LAMBDA(a,b,a+b))/Data,"")))
... and select Formulas -> Name Manager -> New to create your own function and finally use it with the following:
A3 =SeqNumeric(C3:C13,D3:D13)
You can try the following in cell A1:
=LET(B, B2:B12, C, C2:C12, f, 1*ISNUMBER(B*C),seq, SEQUENCE(ROWS(B)),
MAP(seq, LAMBDA(s, IF(INDEX(f,s)=0, "",SUM(FILTER(f, (seq<=s),0))))))
Here is the output:
A non-array version, expanding down the formula would be:
=IF(ISNUMBER(B2*C2), SUM(1*ISNUMBER(B$2:B2*C$2:C2)),"")
For the array version, it counts only if both columns Stat1 and Stat2 are numeric. The name f, has a value of 1 if the condition is TRUE, otherwise is 0. The MAP does the count if the index position of the f array is not zero, otherwise returns an empty string.
I think I got it.
This is the formula that I made
=IF(COUNTIFS(D2:BE2,"*",$D$1:$BE$1,TODAY())>0,"",MAX(A1:A$4)+1)
Countif criteria 1 = if the cell contains a letter and is counted > 0 then it will return blank, otherwise it will start the count using max function. The countif criteria 2 will will return the correct value according to the date today since the excel sheet has several data daily.

How to transpose multiple values between two specific rows within the same column

I have to edit some log result in excel sheet to make it more meaningful. But the log file is very large and there are many differences. The pattern is not perfectly the same.
Because of these differences, VLOOKUP didn't work. But the only similarity is that there are rows as delimeters such as "________".
So for a better explanation, I have a data like this;
__________
A
B
C
__________
A
B
D
__________
But I want it to be like this;
A B C
A B D
.
.
.
and so on.
I know that TRANSPOSE does the convert thing. But It is so simple for me to use as I need to use this function for multiple groups within the same column.
I was going to ask if this is possible to do.
With the sample given, try:
Formula in B1:
=IFERROR(DROP(REDUCE(0,TOCOL(A:A,1),LAMBDA(a,b,IF(b= "________",VSTACK(a,0),VSTACK(DROP(a,-1),HSTACK(TOROW(TAKE(a,-1),3),b))))),1,1),"")
If you have TEXTSPLIT available, you can try the following in cell C1:
=TEXTSPLIT(TEXTJOIN("",,IF(A1:A13="________", ";",A1:A13&",")),",",";",1,,"")
here is the output:
I am using as row, and column delimiter ;, , respectively, but you can customize it to a different token if such characters are present in your input data. I replaced the row separator: (_____) with ; in the IF statement just for convenience, it is not really necessary.
Another possible solution is to calculate the index position (idx) of the row delimiter (_____). Identify start and end of each subset. Use REDUCE to iterate over each segment (SEQUENCE(ROWS(start))) and via FILTER to identify the corresponding subset. Apply TOROW to transpose FILTER result and append each row added via VSTACK on each iteration. DROP is used to remove the first row related to the initial value of the accumulator (ac) and IFERROR to remove #N/A values generated by VSTACK when the number of columns is less than the maximum number of columns of the output.
Here is the approach:
=LET(rng,A1:A13,seq,SEQUENCE(ROWS(rng)),
idx,FILTER(seq, ISNUMBER(FIND("_____",rng))), start,DROP(idx+1,-1),
end,DROP(idx-1,1), IFERROR(DROP(REDUCE("", SEQUENCE(ROWS(start)),
LAMBDA(ac,p,VSTACK(ac,TOROW(FILTER(rng, (seq >= INDEX(start,p))
* (seq <= INDEX(end,p))))))),1),""))
It is a lengthy solution but it is similar to the mental steps an algorithm should follow: 1) Find start and end, 2) Extract subset, 3) Transpose, 4) Append.

Loop through a column and vstack to a new single column using formula

I have a column of strings, example below. Each string is a delimited combinations of texts. Each row has different number of texts. I want to create a single column with one text per row based on this column.
FROM:
a;b
x;y;z
p;q;r;s;t
TO:
a
b
x
y
z
p
q
r
s
t
How do I achieve this using a single formula?
I tried TRANSPOSE(TEXTSPLIT(TEXTJOIN(";",TRUE, data),";"))
However, this fails because the TEXTJOIN part results in more than 32767 characters.
I also tried building a 2D array of mxn where m=no. of rows in the original data and n=no. of texts. However, the MAKEARRAY still results in a single column. Had it worked, I would have used TOCOL or something similar to convert to a single column.
=MAKEARRAY(ROWS(data),COLUMNS(MAX(num_of_texts_in_each_row)), LAMBDA(r,c, LET(
drow, INDEX(data,r,1),
splits, TEXTSPLIT(drow,";"),
INDEX(splits,,c)
)))
Another approach would be by using REDUCE:
=DROP(REDUCE(0,A1:A3,LAMBDA(a,b,VSTACK(a,TEXTSPLIT(b,,";")))),1)
REDUCE behaves like a BYROW, where the VSTACK stacks the spilled result per row on top of eachother after the full spill value.
As it starts at 0, we use DROP the first value to get the desired result.
We could also avoid DROP, but that makes the formula more complicated and longer, but for reference:
=REDUCE(TEXTSPLIT(A1,,";"),A2:A3,LAMBDA(a,b,VSTACK(a,TEXTSPLIT(b,,";"))))
Use:
=LET(
rng,A1:A3,
clm,MAX(BYROW(rng,LAMBDA(a,COUNTA(TEXTSPLIT(a,";"))))),
TOCOL(MAKEARRAY(ROWS(rng),clm,LAMBDA(a,b,INDEX(TEXTSPLIT(INDEX(A1:A3,a),";"),b))),3))

Create random list of strings in random size in excel

I have the following data in my excel.
A
Text 1
Text 2
Text 3
Text 4
Text 5
Text 6
I want to fill column B with random joined data from column A. It should respect the criteria 1> B > 6. i.e. Column B should have a min 1 value from column A or can have a max of up to 6 unique values joined by ,. I can have column B dragged up to 100 rows. But still, they should respect the criteria.
I'm able to get a random value from column A using the formula INDEX($A$1:$A$6, RANDBETWEEN(1, ROWS($A$1:$A$6)), 1), and to join 2 random texts I'm using the formula
=TEXTJOIN(",",true, INDEX($A$1:$A$6, RANDBETWEEN(1, ROWS($A$1:$A$6)), 1), INDEX($A$1:$A$6, RANDBETWEEN(1, ROWS($A$1:$A$6)), 1))
Currently, I'm able to get 2 fixed strings using this formula. Instead of doing the above 6 times, I want to know If there is a way to get this joined string with a random number of unique strings(of the max size of column A length concatenated with a ,).
I'm able to get only 1 value using the random function. Please let me know how can I do this.
You could try:
Formula in B1:
=TEXTJOIN(",",,TAKE(SORTBY(A1:A6,RANDARRAY(COUNTA(A1:A6))),RANDBETWEEN(1,6)))
Note that TAKE() is a new function which is still in BETA. If you don't have access just yet, then try:
=TEXTJOIN(",",,INDEX(SORTBY(A1:A6,RANDARRAY(COUNTA(A1:A6))),SEQUENCE(RANDBETWEEN(1,6))))
In each option:
SORTBY(A1:A6,RANDARRAY(COUNTA(A1:A6))) - Will create a randomized array of the values in column A;
RANDBETWEEN(1,6) - The part which defines the lower- & upper-limit of strings to concatenate;
TAKE/INDEX - A way to retrieve an X amount of rows from the above randomized array. In your case X itself is randomized (see 2nd bullit);
TEXTJOIN() - Concatenate all selected values into a single string.
This formula exploits the functions RANDARRAY and RANDBETWEEN to get a random number of text items to join.
First, I created a dynamic named range called AllTextItems. This automatically expands to capture any number of rows in your dataset:
Then, use the formula:
=TEXTJOIN(",",TRUE,INDEX(AllTextItems,RANDARRAY(RANDBETWEEN(1,ROWS(AllTextItems)),1,1,ROWS(AllTextItems),TRUE)))
in the cells you'd like your joined list.
=TEXTJOIN(",", TRUE, INDEX(A:A, RANDARRAY(1, RANDBETWEEN(1, COUNTA(A:A)), 1, COUNTA(A:A), TRUE)))
EDIT didn't quite beat JvdV to it, but basically a similar basis to his but not using TAKE and not sorting the output

Excel - Return the first negative number of a column

I'm using Excel 2010 and I'm looking for a way to return the first negative number of a column. For instance, I have the following numbers distributed in a column:
1
4
6
-3
4
-1
-10
8
Which function could I use to return -3?
Thanks!
This could be interpreted two ways... If all the numbers are in a single cell (one column) as a string, the MID function can be used. If the numbers are in A1, a formula that could work is this:
=VALUE(MID(A1,SEARCH("-",A1),SEARCH(" ",A1,SEARCH("-",A1))-SEARCH("-",A1)))
If the numbers are each in their own columns (in my example, A3:H3), a different technique must be used:
{=INDEX(A3:H3,1,MATCH(TRUE,A3:H3<0,0))}
Don't type the { } - enter the equation using CTRL+SHIFT+ENTER.
In each case, the formula will return the number -3, which is the first negative number in the series.
Another possibility, avoiding the array formula (which are a big source of performance issues):
=LOOKUP(1;1/(M2:M15<0);M2:M15)
(I assume your numbers are in the M2:M15 range).
This will return the first number matching the "<0" condition. You may use any other condition, including text comparisons.
You may also extract the value of another array corresponding to the matching cell:
=LOOKUP(1;1/(M2:M15<>"OK");T2:T15)
In this example, the first cell containing another string than "OK" will be searched for in the m2:m15 array and the corresponding value in array t2:t15 will be returned.
Please note that the usage of the lookup function should be avoided whenever possible (but in this case, it's very handy !)
(I got the original inspiration for this answer from this post)

Resources