I need to generate a random alphanumeric string as a reference in Google Sheets. I couldn't find a loop function outside of creating a new function in App Script.
I used some existing functions and the new "Named Function" in Google Sheets to make something. It seems to work fine, but I wonder if there is a better way to do it.
The breakdown of what I did to solve this is:
Get a random letter between A and Z.
=RANDBETWEEN( CODE("A"), CODE("Z") )
Get a random number between 0 and 9.
=RANDBETWEEN( CODE("0"), CODE("9") )
Create a random binary value. (We added the +1 because the receiving function isn't zero-based.)
=ROUND( RAND()+1 )
Put it together in a CHOOSE function wrapped in a CHAR function to return a random number or letter.
=CHAR(
CHOOSE(
ROUND( RAND()+1 ),
RANDBETWEEN( CODE("0"), CODE("9") ),
RANDBETWEEN( CODE("A"), CODE("Z") )
)
)
That covers the mechanism to randomly generate a single character, but we need a string.
After some digging, I found that Google recently released the LAMBDA function, and combined with the MAP function, I thought maybe I solved it.
The MAP function takes an array, so I had to figure out how to create one dynamically, which landed me on the SEQUENCE function. Finally, I join the resulting array as a string.
=JOIN( "",
MAP(
SEQUENCE(1, 4),
LAMBDA(slot,
CHAR(
CHOOSE(
ROUND( RAND()+1 ),
RANDBETWEEN( CODE("0"),CODE("9") ),
RANDBETWEEN(CODE("A"),CODE("Z") )
)
)
)
)
)
Once I got this, I made a named function with an argument where I could pass in the number of characters or "slots" like this:
=RANDALPHANUM(4)
Below is a link to a demo sheet, would love to see how much better this can be.
https://docs.google.com/spreadsheets/d/1ODLkUm1maj5_5rxhr_KlmB3vI_OE_-fIyesAevlaogc
random weighted distribution between A-Z & 0-9:
=JOIN(, BYROW(1:4, LAMBDA(x, SORTN(CHAR({
RANDBETWEEN(48, 57); RANDBETWEEN(65, 90)}), 1,,
RANDARRAY(2, 1), 0))))
random non-weighted distribution between A-Z & 0-9:
=JOIN(, BYROW(1:4, LAMBDA(x, (SORTN(CHAR(
{ROW(48:57); ROW(65:90)}), 1,, RANDARRAY(36), )))))
if you wish to freeze it see: https://stackoverflow.com/a/66201717/5632629
Related
I am trying to SUM every number in a range on a different sheet. This range is dictated by an employee number, as well as the year; it changes for each employee and it also changes every year.
So I need the sum function to work like this:
=sum(first cell:second cell)
With the cells being variable with each employee.
I had used the ADDRESS function to obtain the cell values, but it put the sheet name between apostrophes, e.g. 'sheetx!'!A1 , which I thought caused the sum function to return an error.
To test this, I made a test SUM function using input from the CELL function, which returns the sheet value without apostrophes (e.g. sheetx!A1). I still got an error.
What am I doing wrong?
Edit: filter did not seem to work for me, but the index and indirect combo worked like a charm
Functions like INDEX, OFFSET,INDIRECT return the value as well as the range reference. So, you could use something like
=SUM(
INDEX(Sheetx!A:A,1,1)
:
INDIRECT(ADDRESS(1,2,1,1,"Sheetx"))
)
Note however, INDEX and OFFSET are preferred over INDIRECT as string ranges are better avoided to help internal optimization of spreadsheet.
Agree with #Harun24hr, try with the following:
=SUM(
FILTER(
FILTER( history_earnings!1:1000, history_earnings!A:A=B2 ),
MATCH( COLUMN( history_earnings!1:1000 ),
FILTER( COLUMN( history_earnings!1:1 ), history_earnings!1:1=A1 ),
0
)
)
)
you use ADDRESS that returns a string and then you join your two strings with a colon : but that is still a string not a valid range. try:
=SUM(INDIRECT("history_earnings!"&
ADDRESS(MATCH(B$2, history_earnings!$A:$A, ),
MATCH($A$1, history_earnings!$A$1:$Z$1, ))&":"&
ADDRESS(MATCH(B$2, history_earnings!$A:$A, ),
MATCH($A$1, history_earnings!$A$1:$Z$1, )+11)))
also, in your question, you mention: 'sheetx!'!A1 which is also wrong due to two exclamation marks ! correct being: 'sheetx'!A1 unless your sheet is called sheetx!
My apologies, no link to a spreadsheet (its a big one). Was hoping an expert could give some anecdotal advice why the first formula works and the second does not.
Specifically, I can narrow it down to SEQUENCE function (if I remove SEQUENCE there is no #VALUE! error). All range names are columns except for MaxOneTerms which is a number.
Following using IF works fine.
=ARRAYFORMULA(
IF(NOT(ISNUMBER(OneStep)),,
TRIM(TRANSPOSE(QUERY(TRANSPOSE(
IF(SEQUENCE(1,MaxOneTerms,1)>OneTerms,,
IF(OneType="Geo",
ROUND(OneStart*OneStep^SEQUENCE(1,MaxOneTerms,0)),
IF(OneType="Ari",
ROUND(OneStart+OneStep*SEQUENCE(1,MaxOneTerms,0)),
"OneType-ERR"
)
)&","
)
),,100000)))
)
)
The following with SWITCH (replacing IF for readability) returns #VALUE! for each value.
=ARRAYFORMULA(
IF(NOT(ISNUMBER(OneStep)),,
TRIM(TRANSPOSE(QUERY(TRANSPOSE(
IF(SEQUENCE(1,MaxOneTerms,1)>OneTerms,,
SWITCH(OneType,
"Geo",ROUND(OneStart*OneStep^SEQUENCE(1,MaxOneTerms,0)),
"Ari",ROUND(OneStart+OneStep*SEQUENCE(1,MaxOneTerms,0)),
"OneType-ERR"
)&","
)
),,100000)))
)
)
The following with SWITCH, but without SEQUENCE executes (but obviously not the expected values).
=ARRAYFORMULA(
IF(NOT(ISNUMBER(OneStep)),,
TRIM(TRANSPOSE(QUERY(TRANSPOSE(
IF(SEQUENCE(1,MaxOneTerms,1)>OneTerms,,
SWITCH(OneType,
"Geo",ROUND(OneStart*OneStep),
"Ari",ROUND(OneStart+OneStep),
"OneType-ERR"
)&","
)
),,100000)))
)
)
Edit 1: (linked added below).
Sheet "SeriesX" column "S" row 3. Please ignore the rest.
I've tried variants in Sheet "SeriesX2" F3,G3,H3
google sheet
I'm using Coingecko to get cryptocurrency prices, and update the data using a 1 hourly Trigger. As the IMPORTDATA function often fails (a known issue using Coingecko), I have a second Trigger than copies this data as a 'backup', using CopyPasteType.PASTE_VALUES.
The primary data is Named Range 'Crypto_Data', the backup is 'Crypto_DataBackup', and the cell's formula is:
=iferror( vlookup(F24,"Crypto_Data",2,false), vlookup(F24,"Crypto_DataBackup",2,false) )
However, the vlookup to "Crypto_DataBackup" fails as it "evaluates to an out of bound range". It works AOK if I just substitute the range of the Named Range (CryptoData!M3:W502).
Does anyone have an idea what the problem could be?
but if you like quotation marks use:
=IFERROR(VLOOKUP(F24, INDIRECT("Crypto_Data"), 2, ),
VLOOKUP(F24, INDIRECT("Crypto_DataBackup"), 2, ))
also, why not:
=IFNA(VLOOKUP(F24, {Crypto_Data; Crypto_DataBackup}, 2, ))
As #Sergey pointed out: "The named range does not need to be enclosed in quotation marks. Change your formula like this: vlookup(F24,Crypto_Data,2,false)"
I have a Google Sheet that is populated automatically via Zapier integration. For each new row added, I need to evaluate a given cell (Shipper Name) to find last instance of Shipper Name in prior rows, and if so, return Row# for the last entry.
Example Data Sheet
I am trying to create a formula that simply looks at name in new row and returns the number of the most recent row with that name.
Formula needs to run as an Array formula so that the data auto populates with each new row added to the Sheet.
I have tried to use this formula, but when refactored as Array formula, it doesn't populate new values for new rows, it just repeats the first value for all rows.
From Row J:
=sumproduct(max(row(A$1:A3)*(F4=F$1:F3)))
I need this formula refactored to be an Array formula that auto populates all the cells below it.
I have tried this version, but it doesn't work:
=ArrayFormula(IF(ISBLANK($A2:$A),"",sumproduct(max(row(A$1:A3)*($F4:$F=F$1:F3))))
A script (custom function maybe?) would be better.
Solution 1
Below is a formula you can place into the header (put in in J1, remove everything below).
It works much faster than the second solution and has no N² size restriction. Also it works with empty shippers (& "♥" is for those empty ones): as long as A:A column has some value it will not be ignored.
={
"Row of Last Entry";
ARRAYFORMULA(
IF(
A2:A = "",
"",
VLOOKUP(
ROW(F2:F)
+ VLOOKUP(
F2:F & "♥",
{
UNIQUE(F2:F & "♥"),
SEQUENCE(ROWS(UNIQUE(F2:F)))
* POWER(10, INT(LOG10(ROWS(F:F))) + 1)
},
2,
0
),
SORT(
{
ROW(F2:F) + 1
+ VLOOKUP(
F2:F & "♥",
{
UNIQUE(F2:F & "♥"),
SEQUENCE(ROWS(UNIQUE(F2:F)))
* POWER(10, INT(LOG10(ROWS(F:F))) + 1)
},
2,
0
),
ROW(F2:F);
{
SEQUENCE(ROWS(UNIQUE(F2:F)))
* POWER(10, INT(LOG10(ROWS(F:F))) + 1),
SEQUENCE(ROWS(UNIQUE(F2:F)), 1, 0, 0)
}
},
1,
1
),
2,
1
)
)
)
}
Details on how it works
For every row we use VLOOKUP to search for a special number in a sorted virtual range to get the row number of the previous entry matching current.
A special number for a row is constructed like this: we get a sequential number for the current entry among unique entries and append to it current row number.
The right part (row number) of the resulting special numbers must be aligned between them. If the entry has sequential number 13 and the row number is 1234 and there are 100500 rows, then the number must be 13001234. 001234 is the aligned right part.
Alignment is done by multiplying a sequential number by 10 to the power of (log10(total number of rows) + 1), gives us 13000000 (from the example above). This approach is used to avoid using LEN and TEXT - working with numbers is faster then working with strings.
Virtual range has almost the same special numbers in the first column and original row numbers in the second.
Almost the same special numbers: they just increased by 1, so VLOOKUP will stop at most one step before the number corresponding to the current string.
Also virtual range has some special rows (added at the bottom before sorting) which have all 0's as the right part of their special numbers (1st column) and 0 for the row number (2nd column). That is done so VLOOKUP will find it for the first occurrence of the entry.
Virtual range is sorted, so we could use is_sorted parameter of the outer VLOOKUP set to 1: that will result in the last match that is less or equal to the number being looked for.
& "♥" are appended to the entries, so that empty entries also will be found by VLOOKUP.
Solution 2 - slow and has restrictions
But for some small enough number of rows this formula works (put in in J1, remove everything below):
={
"Row of Last Entry";
ARRAYFORMULA(
REGEXEXTRACT(
TRANSPOSE(QUERY(TRANSPOSE(
IF(
(FILTER(ROW(F2:F), F2:F <> "") > TRANSPOSE(FILTER(ROW(F2:F), F2:F <> "")))
* (FILTER(F2:F, F2:F <> "") = TRANSPOSE(FILTER(F2:F, F2:F <> ""))),
TRANSPOSE(FILTER(ROW(F2:F), F2:F <> "")),
""
)
), "", ROWS(FILTER(F2:F, F2:F <> "")))),
"(\d*)\s*$"
)
)
}
But there is a problem. The virtual range inside of the formula is of size N², where N is the number of rows. For current 1253 rows it works. But there is a limit after which it will throw an error of a range being too large.
That is the reason to use FILTER(...) and not just F2:F.
Here is a significantly simpler way to get at the information you're interested in. (I think.) I'm mostly guessing about what you want because your question wasn't really about what you want, but rather about how to get something that you think would help you get what you want. This is an example of an XY problem. I attempted to guess based on experience at what you're really after.
This editable sheet contains just 3 formulas. 2 on the raw data sheet and one in a new tab called "analysis."
The first formula on the Raw data tab extracts a properly formatted timestamp using a combination of MMULT and SPLIT functions and looks like this:
=ARRAYFORMulA({"Good Timestamp";IF(A2:A="",,MMULT(N(IFERROR(SPLIT(A2:A,"T"))),{1;1}))})
The second formula finds the amount of time since the previous timestamp for that Shipper. and subtracts it from the current timestamp thereby giving you the time between timestamps. However, it only does this if the time is less than 200 minutes. IF it is more than 200 minutes, it assumes that was a different shift for that shipper. It looks like this and uses a combination of LOOKUP() and SUBSTITUTE() to make sure it's pulling the correct timestamps. Obviously, you can find and change the 200 value to something more appropriate if it makes sense.
=ARRAYFORMULA({"Minutes/Order";IF(A2:A="",,IF(IFERROR((G2:G-1*SUBSTITUTE(LOOKUP(F2:F&G2:G-0.00001,SORT(F2:F&G2:G)),F2:F,""))*24*60)>200,,IFERROR((G2:G-1*SUBSTITUTE(LOOKUP(F2:F&G2:G-0.00001,SORT(F2:F&G2:G)),F2:F,""))*(24*60))))})
The third formula, on the tab called analysis uses query to show the average minutes per order and the number of orders per hour that each shipper is processing. It looks like this:
=QUERY({'Sample Data'!F:I},"Select Col1,AVG(Col3),COUNT(Col3)/(SUM(Col3)/60) where Col3 is not null group by Col1 label COUNT(Col3)/(SUM(Col3)/60)'Orders/ hour',AVG(Col3)'Minutes/ Order'")
Hopefully I've guessed correctly at your real goals. Always do your best to explain what they are rather than asking for only a small portion that you think will help you get to the answer. You can end up overcomplicating your process without realizing it.
I am trying to implement a formula that will auto number rows within a group
** OPEN Google Sheets URL **
https://docs.google.com/spreadsheets/d/1Uhy1jLTiozXq1M2S3b0ACjqxv3-pCMC2Rr4BhkuBzfU/edit?usp=sharing
The way I have it now, works but if I add a row in a subgroup it doesnt work:
=ArrayFormula({"1";COLUMNS($B$1:B$1) & "." & ROW($B$1:indirect("A"&counta(A13:A25)))})
and for Phase 2 I have to do this:
=ArrayFormula({"2";COLUMNS($A$1:B$1) & "." & ROW($B$1:indirect("A"&counta(A27:A33)))})
And besides that I have a feeling that the first part ;COLUMNS($B$1:B$1) decide what number is in front of the x.1 So if I use B$1:B$1 it will use 2, but if I do ;COLUMNS($E$1:E$1) it will use 5. I don't think this should be the way.
There are some more notes in the document if anyone is interested
Is there anyone who knows how to fix this numbering?
Well, here's a reusable formula that doesn't depend on you figuring out the row number or doing any tedious editing:
=ArrayFormula(SUBSTITUTE(REGEXEXTRACT(INDIRECT("C"&ROW());"Phase (\d+)")&"."&SEQUENCE(LEN(REGEXREPLACE(SUBSTITUTE(REGEXEXTRACT(TEXTJOIN("|";;INDIRECT("C"&ROW()&":C"));"Phase.*?\|(?:Phase|\|)");"||";"|");"[^|]";));1;0);".0";))
Depending on your locale, you may have to use , instead of ; as argument separators.
Caveats:
You need to put it next to a cell that starts with "Phase"
You have to paste it every time you make a new Phase.
None of your tasks can have a | in them.
Your table must have at least one blank row following it.
Here's a more readable version:
=ArrayFormula(
SUBSTITUTE(
REGEXEXTRACT(
INDIRECT("C"&ROW());
"Phase (\d+)"
)&"."
&SEQUENCE(
LEN(
REGEXREPLACE(
SUBSTITUTE(
REGEXEXTRACT(
TEXTJOIN("|";;INDIRECT("C"&ROW()&":C"));
"Phase.*?\|(?:Phase|\|)"
);
"||";
"|"
);
"[^|]";
)
);
1;
0
);
".0";
)
)
Explanation:
Stringify the ProjectTitle Column starting on the current row, joining with |'s. The end of the string is a bunch of |s.
For a given phase, use a Regex to extract Phase 1|Task 1|...|...Phase 2. If it's the last phase, we extract Phase N|...|| instead, then substitute || with |. This is why it only works on a Phase row.
Count the number of |s to determine number of tasks. Do this by removing all non-|'s from the string and getting the length.
Generate a Sequence starting at 0 to determine sub-numbering.
Replace .0 with empty for phase row.