Excel Formula to Insert Spaces Before Capital Letters and Numbers - excel

In column A, starting with A1, I have a set of database column names which are Pascale case and without spaces. I'd like to use an Excel formula in column B to insert spaces before each Capital letter or number. Ideally any consecutive capital letters or numbers would remain together. I've done this in the past with C#, but on this project, I can't even use VBA macros. Example output:
Can this, or something close, be achieved using only formulas?

This is pretty hard, but with ms365 doable with the give sample data:
Formula in B1:
=MAP(A1:A10,LAMBDA(v,TRIM(REDUCE(v,SEQUENCE(LEN(v),,LEN(v),-1),LAMBDA(a,b,LET(x,MAKEARRAY(26,3,LAMBDA(r,c,CHOOSE(c,CHAR(r+64),CHAR(r+96),r-0))),y,MID(a,b,1),z,MID(a,b+1,1),r,BYCOL(x,LAMBDA(c,SUM(EXACT(c,y)+EXACT(c,z)))),IF(MAX(r)=1,LEFT(a,b-1)&IF((CONCAT(r)="110")*(EXACT(UPPER(y),y))," "&y,y&" ")&RIGHT(a,LEN(a)-b),a)))))))
Maybe others have shorter solutions...

Just for fun, this uses a single Reduce but I have defined some auxiliary functions. I put them in a module called 'is' in the Advanced Formula Environment so their full names are Is.Upper, Is.Lower and Is.Digit:
Upper=lambda(c,if(c="",false,and(code(c)>64,code(c)<91)));
Digit=lambda(c,if(c="",false,and(code(c)>47,code(c)<58)));
Lower=lambda(c,if(c="",false,and(code(c)>96,code(c<123))))
=REDUCE(LEFT(A1,1),SEQUENCE(1,LEN(A1)-1,2),LAMBDA(a,c,a&IF(OR(AND(is.Digit(MID(A1,c,1)),NOT(is.Digit(MID(A1,c-1,1)))),AND(is.Upper(MID(A1,c,1)),OR(NOT(is.Upper(MID(A1,c-1,1))),is.Lower(MID(A1,c+1,1)))))," ","")&MID(A1,c,1)))
This is how the main formula looks in the Advanced Formula Environment:
=REDUCE(
LEFT(A2, 1),
SEQUENCE(1, LEN(A2) - 1, 2),
LAMBDA(a, c,
a &
IF(
OR(
AND(
is.Digit(MID(A2, c, 1)),
NOT(is.Digit(MID(A2, c - 1, 1)))
),
AND(
is.Upper(MID(A2, c, 1)),
OR(
NOT(is.Upper(MID(A2, c - 1, 1))),
is.Lower(MID(A2, c + 1, 1))
)
)
),
" ",
""
) & MID(A2, c, 1)
)
)
Note - assumes string length>1.

Related

Validate Alphabetic Characters in a Cell

I am trying to validate ID's that follow specific conditions. These are:
10 character length
1st character = numerical
2nd character = alphabetical
3rd-8th character = numerical
9th character = "/"
10th character = alphabetical
e.g. 1A234567/B
I am have no problem with most of the formula, but I am stuck how to validate the alphabetic characters at character 2 & 10.
My current formula (excluding the formulas for the alphabetic characters):
=IF(AND(LEN(F2)=10,ISNUMBER(--MID(F2,3,6)),MID(F2,9,1)="/"),"Valid","Invalid")
I hope someone can help ! Thanks.
One, IMHO fun, way to do this is through FILTERXML(). Yes, it's probably more verbose than nested AND() statements, but you could try:
Formula in B1:
=NOT(ISERROR(FILTERXML("<t><s>"&A1&"</s></t>","//s[string-length()=10][concat(substring(.,1,1), substring(., 3,6))*0=0][translate(concat(substring(., 2, 1), substring(., 10)), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','')=''][substring(.,9,1)='/']")))
[string-length()=10] - Test if total length equals 10;
[concat(substring(.,1,1), substring(., 3,6))*0=0] - Test if when we concatenate the 1st character with the 3-8th character equals 0 when multiplied by 0. Meaning: we validate that these characters are numeric;
[translate(concat(substring(., 2, 1), substring(., 10)), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ','')=''] - Validate that when we translate all uppercase characters to nothing in the concatenated string from the 2nd and last character equals an empty string;
[substring(.,9,1)='/'] - Validate that the 9th character equals a forward slash.
Note:
WAAR is Dutch for TRUE;
You can use this in cell-validation when you would apply a rule;
FILTERXML() is case-sensitive;
More on FILTERXML() and the usage of it here.
Just an excuse for me to try out the shiny new Advanced Formula Environment
You can create more extensive lambdas like this:
=LAMBDA(cell, lower, upper,
LET(
length, LEN(lower),
seq, SEQUENCE(1, length),
splitCell, MID(cell, seq, 1),
splitLower, MID(lower, seq, 1),
splitUpper, MID(upper, seq, 1),
AND(
SUM(
(splitCell >= splitLower) *
(splitCell <= splitUpper)
) = length,
LEN(cell) = length
)
)
)
EDIT
The formula above would be case insensitive. To make it cases sensitive, you could use CODE, but this would need to be wrapped in IFERROR:
=LAMBDA(cell, lower, upper,
IFERROR(
LET(
length, LEN(lower),
seq, SEQUENCE(1, length),
splitCell, CODE(MID(cell, seq, 1)),
splitLower, CODE(MID(lower, seq, 1)),
splitUpper, CODE(MID(upper, seq, 1)),
AND(
SUM(
(splitCell >= splitLower) *
(splitCell <= splitUpper)
) = length,
LEN(cell) = length
)
),
FALSE
)
)

How to split numbers from one cell to make it look like Mac address

How to split the data from one cell to 8 rows in excel
For example:
There is given long data in one cell: 65178492194051241284...
The sorted values should look like:
65:17:84:92
40:51:24:12
Excel offers multiple options to help you build your own formula
LEFT is used to extract characters from the LEFT of the string (also MID might be useful in your case)
https://www.excel-easy.com/examples/substring.html
Combining LEFT/MID with CONCATENATE you should be able to produce the expected output
https://edu.gcfglobal.org/en/excelformulas/using-concatenate-to-combine-names/1/
Something similar to
=CONCATENATE(LEFT(A1,2),':',MID(A1,2,2),':', MID(A1,4,2),.... )
You have Excel365 then could try-
=TEXTJOIN(":",TRUE,MID(INDEX(MID($A$1,SEQUENCE(INT(LEN($A$1)/8)+1,,1,8),8),ROW(1:1)),{1,3,5,7},2))
This will only work in Excel 365:
=MID(TEXTJOIN(":",TRUE,MID(A1,SEQUENCE(1,LEN(A1)/2,1,2),2)),SEQUENCE(LEN(A1)/8,, 0)*12+1,11)
Or if you want a more readable (and slightly faster running) version:
=LET( t,A1,
L, LEN( t ),
S, TEXTJOIN( ":", TRUE, MID( t, SEQUENCE( 1, L / 2, 1, 2 ), 2) ),
MID( S, SEQUENCE( L/8,, 0 )*12 + 1, 11 ) )
With Microsoft Excel 365 you can use:
Formula in C1:
=TEXT(MID(A1,SEQUENCE(LEN(A1)/8,,,8),8),"00\:00\:00\:00")
If you haven't always have an exact multiple of 8 characters you should nest FLOOR():
=TEXT(MID(A1,SEQUENCE(FLOOR(LEN(A1)/8,1),,,8),8),"00\:00\:00\:00")

One formula to combine 2 lists (2021)

I have one list starting from B1 (=UNIQUE(A1:A8)), and another list starting from D1 (=UNIQUE(C1:C8)). Thus =B1# and =D1# in other cells both spill.
Now, I would like to know if we could find one formula to combine List B1# and List D1# (extract only unique values) by dynamic array functions, LAMBDA, LET, etc.
I don't want to move the position of the two lists. Does anyone have any good idea?
I may not be following what you want the shape to be, but here are two shapes:
Side-by-Side
=CHOOSE({1,2},B1#,D1#)
If you want it to take the original A and C columns as input and do all the work, then:
=CHOOSE({1,2},UNIQUE(FILTER(A:A,NOT(ISBLANK(A:A)))),UNIQUE(FILTER(C:C,NOT(ISBLANK(C:C)))))
or a LET version of the same which does not require retyping the inputs:
=LET( Ltrs, A:A,
Nmbrs, C:C,
CHOOSE( {1,2},
UNIQUE(FILTER(Ltrs,NOT(ISBLANK(Ltrs)))),
UNIQUE(FILTER(Nmbrs,NOT(ISBLANK(Nmbrs)))) ) )
End-on-End
=LET( uLtrs, B1#,
uNmbrs, D1#,
ltrCt, ROWS(uLtrs),
idx, SEQUENCE( ltrCt + ROWS(uNmbrs) ),
IF( idx <= ltrCt, uLtrs, INDEX( uNmbrs, idx-ltrCt ) ) )
Similar as above, if you want it to take the original A and C columns as input and do all the work, then:
=LET( Ltrs, A:A,
Nmbrs, C:C,
uLtrs, UNIQUE(FILTER(Ltrs,NOT(ISBLANK(Ltrs)))),
uNmbrs, UNIQUE(FILTER(Nmbrs,NOT(ISBLANK(Nmbrs)))),
ltrCt, ROWS(uLtrs),
idx, SEQUENCE( ltrCt + ROWS(uNmbrs) ),
IF( idx <= ltrCt, uLtrs, INDEX( uNmbrs, idx-ltrCt ) ) )
Both spill the results.
Place the following code into cell F2 and drag formula downwards to F14. This will give you a unique list of both Column A and Column D
=IF(IFERROR(IF(INDEX($A$1:$A$99999,MATCH(0,COUNTIF($F$1:F1,$A$1:$A$99999),0))=0,NA(),INDEX($A$1:$A$99999,MATCH(0,COUNTIF($F$1:F1,$A$1:$A$99999),0))),INDEX($C$1:$C$99999,MATCH(0,COUNTIF($F$1:F1,$C$1:$C$99999),0)))=0,NA(),IFERROR(IF(INDEX($A$1:$A$99999,MATCH(0,COUNTIF($F$1:F1,$A$1:$A$99999),0))=0,NA(),INDEX($A$1:$A$99999,MATCH(0,COUNTIF($F$1:F1,$A$1:$A$99999),0))),INDEX($C$1:$C$99999,MATCH(0,COUNTIF($F$1:F1,$C$1:$C$99999),0))))
Let me know if you need it to behave differently.

Excel use SUMIF but with a multiplier before summation: x2, x3, x4, etc

I have a spreadsheet that is using the SUMIF function. It works very well to summarize single values from another column but I want to be able to potentially multiply a single entries value before summing it.
I'm unable to attach photos so I'll try to describe. My spreadsheet has this equation on it:
=SUMIF(L25:L31,"x",$K$25:$K$31)
Then if I put an 'x' on a row in column 'L' it will include the value from column 'K' in the summation. I want to be able to use 'x2', 'x3', 'x4', etc. and multiply the value before the summation. Is that possible with Excel?
Multiplication is distributive; the product of the sum is the same as the sum of the products. So you can move the multiplier to the outside of the equation if I am understanding your question correctly. Where "y" is the multiplier and "x" is the criteria you are looking up, the below should work:
=y*SUMIF(L25:L31,"x",$K$25:$K$31)
If the multiple is a variable, then I would suggest adding helper columns, and simply change the sum range on the SUMIF formula.
Note that the SUMPRODUCT suggested does not take into account the conditional nature of the sum that I assume you are looking for (otherwise why would you use SUMIF instead of SUM?)
This should do the trick:
=SUMPRODUCT(IFERROR($K$25:$K$31*IF($L$25:$L$31="x",1,MID($L$25:$L$31,2,255)),0))
But must the "x" be there? If you have the flexibility to switch to 1, 2, 3 instead of x, x2 and x3, that would open up to simplify the formula to this
=SUMPRODUCT($K$25:$K$31*$L$25:$L$31)
The latter should be more efficient as well, in case your actual data is large.
If I've not misunderstood, this would produce the result of applying a variable multiplier to the qualified data points and then sum it up:
=SUM( (L25:L30="x") * K25:K30 * {1;2;3;4;5;6} )
The 1;2;3... column array is arbitrary - I just invented it for example. If it really is an incremental sequence, then you can produce the same this way and gain the advantage that it is easier to extend (without typing lots of numbers):
=SUM( (L25:L30="x") * K25:K30 * SEQUENCE( ROWS(L25:L30) ) )
If this is the solution and you really want to streamline it, then make it a little cleaner with LET:
=LET( sumRange, K25:K30,
criteriaRange, L25:L30,
criteria, "x",
size, MIN( ROWS( criteriaRange), ROWS( sumRange ) ),
SUM( (criteriaRange = criteria ) * sumRange * SEQUENCE( size ) ) )
If the multipliers are not an incremental sequence, then a third variable could be introduced to replace SEQUENCE( size ) that would contain the multiplier array.

How to reverse comma separated string in Excel?

How can I use a formula in MS Excel to reverse a comma separated sting in row? The number of values are not always same so some rows have 3, 4 and some only one element.
So the output looks like following image
If you have Office 365 Excel then use This array formula:
=TEXTJOIN(",",,TRIM(MID(SUBSTITUTE(A2,",",REPT(" ",99)),((LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1)-ROW($XFD$1:INDEX(XFD:XFD,LEN(A2)-LEN(SUBSTITUTE(A2,",",""))+1)))*99+1,99)))
Being an array formula it needs to be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode.
If one does not have Office 365 Excel then vba will probably be the only choice.
Sloppy UDF solution:
Function REVERSESTRING(original As Range, delim As String)
Dim i As Long, reversed As String, arr As Variant
arr = Split(original.Value, delim)
For i = UBound(arr) To 0 Step -1
reversed = reversed & arr(i) & ","
Next i
REVERSESTRING = Left(reversed, Len(reversed) - 1)
End Function
A pure single-cell formula works in Excel 365; with that version there is no need for special array calculation entry, VBscripts, or helper columns:
Procedure
Make the following table in Excel:
Add column headings
Select headings, and press the Table icon on the Insert ribbon tab
Copy formula 1 & 2 below into the appropriate columns
Original | # | Reversed
---------+-----+-----------
| {1} | {2}
Place the string you would like to reverse in the first column.
Details and customization
'Original' column
(holds the delimited string value you wish to process)
Place your source data in this column (as seen in the example images)
'#' column
(counts number of items in delimited cell)
Note: (this column is completely optional; it simply shows the number of items in the original column)
{1} <- replace with formula #1:
=LET(
existingDelimiter, ","
, originalValue, [#[Original]]
, SUM(
LEN(originalValue)
- LEN(
SUBSTITUTE(
originalValue
, existingDelimiter
, ""
)
)
)
+1
)
(Note: If your list uses a delimiter other than a comma or if the first column of your table will have a different name, edit the above as appropriate using the same instructions given for formula #2 below.)
'Reversed' column
(reverses order of delimited items in list; optionally changes delimiter)
{2} <- replace with formula #2:
=LET(
existingDelimiter, ","
, newDelimiter, ","
, originalValue, [#[Original]]
, SUBSTITUTE(
ARRAYTOTEXT(
LET(
list,
IFERROR(
FILTERXML(
"<t><s>"
& SUBSTITUTE(
originalValue
,existingDelimiter
,"</s><s>"
)
& "</s></t>"
, "//s"
)
,""
)
,SORTBY(
list,
SEQUENCE(
ROWS(list)
,1
,ROWS(list)
,-1
)
)
)
,0
)
, ", "
, newDelimiter
)
)
Adjust the formula for what you are trying to accomplish by changing the values for existingDelimiter, newDelimiter, and originalValue, as necessary.
A. To reverse a comma-separated string, use the formula as written:
existingDelimiter, ","
, newDelimiter, ","
Example:
B. To reverse DNS names, replace the comma with a period in the definitions for existingDelimiter and newDelimiter:
existingDelimiter, "."
, newDelimiter, "."
This can be very useful for reverse DNS names (aka Java class names / Universal Type Indicators (UTIs) / etc.)
Example:
Replace "Original" in [#[Original]] with the name of your first column, if different.
A. If using just a single cell for input instead of a table column, replace [#[Original]] with the reference to that cell (e.g. B2):
, originalValue, B2
Example:
Explaination of the "Reversed" column formula:
By manually converting to XML, we can use the FilterXML function, which converts the data to an array.
Having the data in an array allows the use of the SortBy function.
SortBy reverses the array by using a helper array created with the Sequence function.
Finally, the ArrayToText functions converts this (now reverse-ordered) array back to a text string that will fit in a single spreadsheet cell.
This is what allows us not to need a loop, helper columns, or VBScript.
Bonus column
To extract a specific term from a list, use the following formula in another table column:
(Change the number in termNumber to the desired value):
=LET(
existingDelimiter, ","
, originalValue, [#[Original]]
, termNumber, "[2]"
, IFERROR(
FILTERXML(
"<t><s>"
& SUBSTITUTE(
originalValue
, existingDelimiter
, "</s><s>"
)
& "</s></t>"
, "//s"
& termNumber
)
, ""
)
)
Example:
Other notes
Needed:
Excel 365 (for at least FilterXML and Let functions, and dynamic arrays) (*)
It might work with other versions but I have not tested those. Please note in the comments if you notice other or future versions (e.g. Excel 2022) work with this.
Not needed:
dynamic array entry
VBscript
macro-enabled files
(*) This can be done without the Let function, but using Let allows the calculation to be edited / repurposed with less chance for user error.
Note: When the Lambda function is released (hopefully later in 2021) then this all can be wrapped up in a named worksheet function
Bonus: to edit more complex Excel formulas with code highlighting (and other features such as auto-intents and folding), try a text editor with Swift programming language support. Examples:
VSCode editor (free; cross-platform)
Until there is an Excel-specific extension available, install a Swift VSCode extension such as this: Swift language VSCode extension (it seems to work quite well to provide code highlighting and folding)
Notepad++ (free; Windows-only)
Select "Swift" from the "Language" menu
Inspired by (apologies if I've forgotten someone):
https://www.howtoexcel.org/tutorials/split-text-by-delimiter/
https://www.sumproduct.com/news/article/have-you-got-what-it-text-introducing-arraytotext-and-valuetotext
https://exceljet.net/formula/reverse-a-list-or-range

Resources