Google Sheets Combining Like Dice Rolls (While removing Integers) - google-sheets

My apologies for asking an incomplete question previously.
This is what I'm trying to accomplish.
I'm building a TTRPG sheet that automatically combines dice rolls, bonuses (additive) and penalties (subtractive) from a variety of sources. All of this data is expressed as either dice notation (D4, D6, D8, D10, D12, D20, and D100) or an Integer (1, 2, 4, 6), or both (combined). These also include negative values (-1D4, -1D6, -2, etc.). The goal isn't to generate the random numbers, but instead combine like dice together for the player to roll manually (I tried the automatic random numbers... Players were not happy about it.)
So, the goal is to combine likes, so something like:
"1D6+1D6" would become "2D6". However, because penalties could outweigh the bonus, you can't combine "1D6+1D6+-1D6" into "1D6". (Since each of the rolls could be a different number, such as "6+6-1" compared to "1+1-6").
Additionally, Integers (2, 4, 6, 8, etc.) are by necessity handled in a different part of the sheet, so the goal is to strip the integers out from the output. (The reason for stripping them out has nothing to do with formula complexity, but other game factors that require it to be viewed separately.)
Here are some examples of typical inputs and expected outputs:
1D6+1D4+1D8+-1D4+1D6+2 = 1D4+-1D4+2D6+1D8 (Notice the integer is removed)
1D6+2+0+1+8 = 1D6 (Because all integers have been stripped out)
1D20+-1D4+2D6+0+1D6+-1D6 = +-1D4+3D6+-1D6+1D20
(Yes, negative numbers will have the "+-" in front of them).
My original "mostly working" formula was 2 solid pages long when copied/pasted into MS Word. This formula will be repeated THOUSANDS of times, so smaller/faster makes a huge difference in the overall scheme of things. Two previous amazing Spreadsheet Wizards (Player0 and TheMaster) gave great answers, but I failed to disclose the integer as a part of the overall process.
The table below shows the formula that works for the first example, but not the second (gives "2D" in the output).

For original explanation, see Google Sheets Formula for combining dice rolls
After the first split by +, check if the result is a TEXT and if not, FILTER it out:
=JOIN("+",BYROW(QUERY(REDUCE({"",""},SEQUENCE(2),LAMBDA(a,c,{a;QUERY({ARRAYFORMULA(SPLIT(TRANSPOSE(LAMBDA(ar,FILTER(ar,ISTEXT(ar)))(SPLIT(B1,"+"))),"D"))}," select sum(Col1),Col2 where Col1"&IF(c=1,">","<")&"0 group by Col2 label sum(Col1) ''")})),"order by Col2"),LAMBDA(r,JOIN("D",r))))
For no negative values, add a empty array {"",""} for NA:
=JOIN("+",BYROW(QUERY(REDUCE({"",""},SEQUENCE(2),LAMBDA(a,c,{a;IFNA(QUERY({ARRAYFORMULA(SPLIT(TRANSPOSE(LAMBDA(ar,FILTER(ar,ISTEXT(ar)))(SPLIT(B1,"+"))),"D"))},"select sum(Col1),Col2 where Col1"&IF(c=1,">","<")&"0 group by Col2 label sum(Col1) ''"),{"",""})})),"where Col1 is not null order by Col2"),LAMBDA(r,JOIN("D",r))))

try:
=INDEX(REGEXREPLACE(TEXTJOIN("+", 1, FLATTEN(QUERY(TRANSPOSE(QUERY(QUERY(IFERROR(IFNA(TRANSPOSE({
REGEXEXTRACT(SPLIT(C5, "+"), "^\d+")*1; REGEXEXTRACT(SPLIT(C5, "+"), "D\d+");
REGEXEXTRACT(SPLIT(C5, "+"), "^-\d+")*1; REGEXEXTRACT(SPLIT(C5, "+"), "D\d+");
REGEXEXTRACT(SPLIT(C5, "+"), "D(\d+)")*1}), 0)),
"select sum(Col1),Col2,'+',sum(Col3),Col4,Col5
where Col2 is not null group by Col2,Col4,Col5 order by Col5"),
"select Col1,Col2,Col3,Col4,Col5 offset 1", )),,9^9))), " |\+ 0 D\d+", ))

Related

Count uniques in comma separated column list

In one column I have a list of comma (and whitespace) separated responses to a question such as "what music genre do you listen to?"
Alternative, EDM, Electronic, Hip Hop,
Drum & Bass (D&B), Indie, House, R&B, Rap, Rock
Indie, House, R&B, Rap, Rock,
Rap, Rock
I want a function that will return the unique values of the respones. So something like
UNIQUE(SPLIT({A:A})
(Although that doesn't quite work).
Desired output is a column of unique like:
Rock
Rap
...
Hip Hop
If your data starts in column A1, try this formula:
=QUERY(SORT(UNIQUE(FLATTEN(ARRAYFORMULA(TRIM(SPLIT(FILTER(A1:A,LEN(A1:A)),",",1,1)))))),"where Col1 <>''",0)
This gets the data in column A, ignoring blank cells, splits the cells on the commas, trims any leading or trailing spaces, "flattens" the resulting columns into one, gets unique values, sorts, them, and then removes any blank rows.
UPDATE: A more efficient version of the above formula is:
=QUERY(UNIQUE(IFERROR(FLATTEN(ARRAYFORMULA(TRIM(SPLIT(A1:A,",",1,1)))))),"where Col1 <>'' order by Col1")
Note that FLATTEN is an unsupported function that may possibly be removed from Google Sheets at some point. There are other ways of performing its function, if necessary.
The caution, provided by Matt King, on the use of FLATTEN.
If this doesn't work for some reason, please share a sample of your sheet.
use:
=INDEX(UNIQUE(FLATTEN(TRIM(SPLIT(TEXTJOIN(",", 1, A:A), ",")))))

Google Sheets - Filtering out values in a range from another range

-- EDIT #2 -- Updated the Google Sheet again with a solution which is painfully close. Best formula I've had so far is below. --
=ARRAYFORMULA(SPLIT(UNIQUE({ARRAYFORMULA(QUERY(IF(COUNTIF(G4:G&"|||"&H4:H,ARRAYFORMULA(A4:A&"|||"&B4:B))>0,REGEXREPLACE(A4:A&"|||"&B4:B,".*",""),A4:A&"|||"&B4:B),"SELECT * WHERE Col1 IS NOT NULL"));ARRAYFORMULA(QUERY(IF(COUNTIF(G4:G&"|||"&H4:H,ARRAYFORMULA(D4:D&"|||"&E4:E))>0,REGEXREPLACE(D4:D&"|||"&E4:E,".*",""),D4:D&"|||"&E4:E),"SELECT * WHERE Col1 IS NOT NULL"))}),"|||"))
-- EDIT -- Updated the Google Sheet to more closely reflect my use case --
Pretty confident someone's asked this before but I've been Googling for a few hours now and I'm starting to lose hair. I think I've got to use a QUERY function but not 100% on that.
Demo sheet here: https://docs.google.com/spreadsheets/d/1p_hqk9WydcyXQZT4bIm4DSZZnaPKbngtnZ0-laYHwk8/edit?usp=sharing
What I want to do:
I want to combine the ranges under DATA 1 and DATA 2, but I want to exclude and rows which start with the values in DATA 3.
RESULT 1 doesn't add value but shows how I was adding DATA 1 and DATA 2 together.
RESULT 2 shows the result I'm trying to get.
RESULT 3 hidden but where I got to (and doesn't add value again, sorry). I can get it mostly working, but I'd have to manually specify in the QUERY which combinations I'm looking for... and frankly my dataset is HUGE. That formula currently looks like this:
=QUERY(UNIQUE({FILTER(A4:B,NOT(ISBLANK(A4:A)));FILTER(D4:E,NOT(ISBLANK(D4:D)))}),"SELECT * WHERE NOT Col1 STARTS WITH 'a' OR NOT Col2 STARTS WITH 'v'",-1)
Hope someone can help me out! You're my only hope.
This works for your example data. It uses ARRAYFORMULA and MATCH to concatenate the two columns and LEFT to only use the first character in the match function. You might need to find a slightly smarter way to do the starts with element depending on your actual data.
=UNIQUE({FILTER(A4:B,not(isblank(A4:A)),iserror(MATCH(ARRAYFORMULA(A4:A&left(B4:B,1)),ARRAYFORMULA(G4:G&H4:H),0)));FILTER(D4:E,not(isblank(D4:D)),iserror(MATCH(ARRAYFORMULA(D4:D&left(E4:E,1)),ARRAYFORMULA(G4:G&H4:H),0)))})
Documentation:
MATCH: here
ARRAYFORMULA: here
Was also looking for how to find the set-difference between two ranges.
All my values were in one range but I reworked my solution to fit your case of needing to join ranges too.
Finding the set-difference between two ranges:
// given A1:A9 and C1:C9
// return all values in A1:A10 excluding those in C1:C10
=filter(A1:A9, not(iferror( match(A1:A9,C1:C9,0), false )), A1:A9<>"")
Finding the set-difference between two joined ranges and a third range:
// given A1:A9, B1:B9, and C1:C9
// return all values in A1:A10 and B1:B10 excluding those in C1:C10
=filter({A1:A9;B1:B9}, not(iferror( match({A1:A9;B1:B9},C1:C9,0), false )), {A1:A9;B1:B9}<>"")
Breakdown:
Goal is to get an output range that is the input range excluding all values in an "exclusion" range
MATCH: match all values that are in both your input range and your exclusion range (remaining values will produce an error)
NOT + IFERROR : convert matches to false and errors to true
FILTER: filter the input down to only the true values (i.e. whats not matched, a.k.a. remove the excluded values), and also add a 2nd condition to remove blanks
tips:
{X;Y}: unions two ranges, ; adds rows , adds columns
X<>"": true for all non-blank values in range X

Comparing attributes from 2 sets

I have 2 sets of data, let's say, Workers and Platform. Both have attributes, say, Drilling, Grinding, Hammering.
I need a way to compare, in Excel, the attributes for each element within both sets. For example, if Platform A requires Hammering and Drilling, and Worker A has Drilling, Grinding and Hammering, he'd be accepted for platform A. Worker B only has Drilling, so he'd be rejected at platform B.
The following spreadsheet illustrates this clearly:
https://docs.google.com/spreadsheets/d/1qvkZbDNIWe9gmFjGNr4dhtvqagJZOkS89YD4fzqjvQQ/edit?usp=sharing
In the sheet, "Canta Baila Pinta" are the attributes. The solution I've come up so far is:
Generate a string for each Worker and Platform, which equals to its attributes
How do I compare both strings as if to ask, "Does string B contain any element not present in B?"
Furthermore, this code is not dynamic... how would you go about making it so that any newly added attribute got processed automatically, without user intervention?
This isn't dynamic, but here's another approach that may be of interest:
=ArrayFormula(mmult(if(Trabajadores!E2:G6="Sí",1,0),if(Plataformas!B2:F4="Sí",1,0))=
mmult(transpose(row(Plataformas!B2:F4))^0,if(Plataformas!B2:F4="Sí",1,0)))
The idea is that you multiply the Trabajadores and Plataformas matrices (converting Sí into 1, anything else to zero) to count the number of correspondences in skills for each worker/platform combination. Then you calculate the column totals of requirements in the plataformas matrix by doing another mmult, and finally compare the results to see which workers have the required number of skills for each platform.
delete all your formulae and use these:
=ARRAYFORMULA(
IF(E2:E="Sí", E1, )&
IF(F2:F="Sí", F1, )&
IF(G2:G="Sí", G1, ))
=ARRAYFORMULA(SUBSTITUTE(QUERY(IF(INDIRECT("B2:"&ROW()-1)<>"",
INDIRECT("A2:A"&ROW()-1), ),,999^99), " ", ))
=ARRAYFORMULA(IFERROR(VLOOKUP(A2:A,
{Trabajadores!A:A, Trabajadores!H:H}, 2, 0)&" "&
HLOOKUP(B1:F1, {Plataformas!B1:1; INDIRECT("Plataformas!B"&
MATCH("Endstring", Plataformas!A:A, 0)&":"&
MATCH("Endstring", Plataformas!A:A, 0))}, 2, 0)))
UPDATE:
=ARRAYFORMULA(IFERROR(REGEXMATCH(IFERROR(VLOOKUP(A2:A,
{Trabajadores!A:A, Trabajadores!H:H}, 2, 0)&SUBSTITUTE(COLUMN(B1:F1)^0, 1, )),
SUBSTITUTE(ROW(INDIRECT("A2:A"&COUNTA(A2:A)+1))^0, 1, )&
HLOOKUP(B1:F1, {Plataformas!B1:1; INDIRECT("Plataformas!B"&
MATCH("Endstring", Plataformas!A:A, 0)&":"&
MATCH("Endstring", Plataformas!A:A, 0))}, 2, 0))))
spreadsheet demo

Converting formula to ARRAYFORMULA issues with SUM and INDEX

I have a scoring spreadsheet for a competition I'm working on. Competitors' place/rank are converted into points towards the overall series based on a chart of corresponding values. For ties, the sum of the points covered by all of the tied places are split evenly among the tied competitors (i.e. 2-way tie for 3rd; if 3rd usually gets 10 points and 4th usually gets 8, these competitors will receive (10+8)/2 (2 being the # of tied competitors), so they each receive 9 points).
I have a formula which does this exact calculation:
=IFERROR(IF(ISBLANK($A4:$A),,SUM(INDEX(SeriesPoints, E4:E):INDEX(SeriesPoints, MIN(E4:E + COUNTIF(E$4:E, E4:E) - 1, ROWS(SeriesPoints)))) / COUNTIF(E$4:E, E4:E), 0))
Where 'SeriesPoints' is a 2 column array; column 1 is the places/ranks (1:125) and column 2 is their corresponding point values. Column 'E' is the competitors' rank from the competition.
I have been unable to convert this formula to an ARRAYFORMULA() so I can avoid dragging it down the entire sheet (possibly up to 1000+ competitors over the series).
I'm mildly proficient with MMULT(), so I understood that would be a good approach for switching out SUM(), however, I haven't been able to create a matrix of the values to be summed.
INDEX():INDEX() doesn't work with ARRAYFORMULA() so I've tried switching to VLOOKUP(). With VLOOKUP() I've been able to produce the start and end values of the range of values for a tie, but not the full list. For example, if there is a 3-way tie for 4th, I can produce the respective points for 4th and 6th (the bounds of the tie).
In an attempt to list out even just the numbers from 4:6, I've hit a wall converting what would be a simple ROW() or SEQUENCE() formula to a matrix/array.
The following formula produces an array of the upper and lower bounds of ties or the single place should there be no tie, although the single place gets repeated.
=ARRAYFORMULA(IF(COUNTIF(E$4:E,E4:E)=1,E4:E,{E4:E,E4:E+COUNTIF(E$4:E,E4:E)-1}))
I'm assuming if I can get VLOOKUP({#:#}) to fill properly, I'll be where I need to be.
From here, I feel confident in my abilities to wrap a VLOOKUP() for the actual point values, an MMULT() to sum across these rows for the total, then a simple division to produce the correct point value.
Spreadsheet: https://docs.google.com/spreadsheets/d/1lpNewR3p4i7ZHmlFGLlG1tLuxgO-6onSeH8mWTeclBw/edit?usp=sharing
Currently, my workspace is off to the right. The original formula is in F4 and my test codes are working on column G instead of E.
So for sample placements of 1,1,3,3,3,6,7,8 and sample points values of 1000, 850,738,663,633,603,573,550 I expect the output to be 925 for the two 1st place tied competitors, 678 for the tied 3rd places, 603 for 6th, 573 for 7th, and 550 for 8th.
I'd appreciate any and all help!
=ARRAYFORMULA(IFERROR(IFERROR(VLOOKUP(G4:G, QUERY({INDIRECT("G4:G"&counta(A4:A)+3),
VLOOKUP(ROW(INDIRECT("A1:A"&COUNTA(A4:A))), SeriesPoints, 2, 0)},
"select Col1,sum(Col2) group by Col1 label sum(Col2)''", 0), 2, 0))/
IFERROR(VLOOKUP(G4:G, QUERY(G4:G,
"select G,count(G) where G is not NULL group by G label count(G)''", 0), 2, 0))))

How to sum largest $n$ values in a range in Google Spreadsheet?

I have a list of values and I need to sum the largest 10 values (in a row). I found this but I can't figure it out/get it to work:
https://productforums.google.com/forum/#!topic/docs/A5jiMqkRLYE
let's say you want to sum the 10 highest values of the range E2:EP
then try:
=sumif(E2:P2, ">="&large(E2:P2,10))
and see if that works ?
EDIT: Maybe this is a better option ? This will only sum the 10 outputted by the array_constrain. Will only work in the new google sheets, though..
=sum(array_constrain(sort(transpose($A3:$O3), 1, 0), 10 ,1))
Can you see if this works ?
This works in old google sheets too:
sum(query(sort(transpose($A3:$O3), 1, false), "select * limit 10"))
Transpose puts the data in a column, sort sorts the data in a descending order and then query selects first 10 numbers.
Unfortunately, replacing sort with "order by" in a query statement does not work, because you can not reference a column in a range returned by transpose.
The sortn function seems to be just what you need.
From the documentation linked above, it "[r]eturns the first n items in a data set after performing a sort." The data set does not have to be sorted. It takes a bunch of optional parameters as it can sort on multiple columns.
SORTN(range, [n], [display_ties_mode], [sort_column1, is_ascending1], ...)
The interesting ones for your case are n, sort_column1, and is_ascending1. Specifically, your required formula would be
sum(sortn(transpose(A3:O3), 10, 0, 1, false)))
Some notes:
This assumes your data in A3:O3. You can replace it with your range.
transpose converts the data row to a data column as required by sortn.
10 is n, indicating the number of values that you require.
0 is the value for display_ties_mode. We are ignoring this value.
1 is the value of sort_column1, telling that we want to sort the first column (after transpose).
false tells sortn to sort descending and thus pick the largest values. The default is to pick the smallest.

Resources