"Weighted" random order in Craft CMS - craftcms

I have a field in a channel called "weight" where the user can set a number between 0-1000. On page load I want to generate a random number between 0 and "weight" for each entery, and then sort the enteries based on these numbers.
How can I do this with Craft 2/Twig?

Create the random number:
{% set range = range(1, weight) %}
{% set numbers= random(range) %}
Random No: {{ numbers }}

Related

How to sort TWO columns in specific range by priorities?

I have a small desire with my football table...
I want to sort automatically ranges B3:J7, B11:J15, B19:J23, B27:J31, B35:J39, B43:J47, B51:J56 and B59:J64 by COLUMN K (Pts - Points) which is FIRST priority and when the teams are on equal points then by column J (GD - Goal Difference).
Is it possible?
I have a code in apps script but this sorts only by column K.
Thanks in advance!
Table link: https://docs.google.com/spreadsheets/d/17ZyUuDAp85Vr76NMmqQXuLqTUMMuHP6sQu2gvQREhDU/edit?usp=sharing
.sort() accepts multiple sort criteria, you can modify line 7 of your script for it to include an additional column:
ranges.forEach(r => r.sort([{ column: r.getLastColumn(), ascending: false }, { column: r.getLastColumn()-1, ascending: false }]));
This assumes that the range's last column is "K" and previous column to the left is "J", you would need to add the whole range when declaring the range, for example:
const a1Notations = ["B3:K7", "B11:K15", "B19:K23", "B27:K31", "B35:K39", "B43:K47", "B51:K56", "B59:K64"];
Reference:
Range.sort()

Count specific columns based on multiple criteria and ranges of different sizes

I'm working with the following table:
I need a formula I can use on a different sheet that counts how many classes were cancelled for each of the groups like:
GE COL INT MIX W 2
GE COL INT 1 W 0
The criteria to decide if a class was cancelled or not is to type "C" in one of the columns for the class (i.e. January 2, meaning second class in January). In the example table you can see that the 4 people are part of 2 groups, 2 per group; if I write C for 2 of them in the same group I want the macro to only count 1 cancelled class instead of counting both "C"s.
All of the space where the "C"s will be written (Month columns) is a named range = "Attendance" and there's also a named range for the names of all groups = "Groups".
I'm using the following simple formula:
=COUNTIFS(METRICS!F:F,H5,Attendance,"C")
METRICS!F:F Refers to the sheet where the table I just showed you is and F is the column where the levels are. H5 is the cell in the other sheet where I'm comparing the group name to make sure it's the same. Attendance is the range where I'll be writing "C".
However, I get the error:
Array arguments to COUNTIFS are of different size.
And that would count all "C"s, not only the ones I need.
A simple solution would be to add a column after the group name that counts absences, and then use UNIQUE() over the group names and absence count to get a unique list. eg.
Alternately, to do this without the extra column, first create a unique list of group names with =UNIQUE(Groups).
Then add a named range called GroupAttendance that includes the ranges of both Groups and Attendance.
Lastly, fill the column next to the group names with:
=COUNTIF(FILTER(UNIQUE(GroupAttendance), UNIQUE(Groups) = A2), "C")
eg.

Compute subranks in spreadsheet column in combination with ArrayFormula (Google Sheets)

I'm trying to find the inverse rank within categories using an ArrayFormula. Let's suppose a sheet containing
A B C
---------- -----
1 0.14 2
1 0.26 3
1 0.12 1
2 0.62 2
2 0.43 1
2 0.99 3
Columns A:B are input data, with an unknown number of useful rows filled-in manually. A is the classifier categories, B is the actual measurements.
Column C is the inverse ranking of B values, grouped by A. This can be computed for a single cell, and copied to the rest, with e.g.:
=1+COUNTIFS($B$2:$B,"<" & $B2, $A$2:$A, "=" & $A2)
However, if I try to use ArrayFormula:
=ARRAYFORMULA(1+COUNTIFS($B$2:$B,"<" & $B2:$B, $A$2:$A, "=" & $A2:$A))
It only computes one row, instead of filling all the data range.
A solution using COUNT(FILTER(...)) instead of COUNTIFS fails likewise.
I want to avoid copy/pasting the formula since the rows may grow in the future and forgetting to copy again could cause obscure miscalculations. Hence I would be glad for help with a solution using ArrayFormula.
Thanks.
I don't see a solution with array formulas available in Sheets. Here is an array solution with a custom function, =inverserank(A:B). The function, given below, should be entered in Script Editor (Tools > Script Editor). See Custom Functions in Google Sheets.
function inverserank(arr) {
arr = arr.filter(function(r) {
return r[0] != "";
});
return arr.map(function(r1) {
return arr.reduce(function(rank, r2) {
return rank += (r2[0] == r1[0] && r2[1] < r1[1]);
}, 1);
});
}
Explanation: the double array of values in A:B is
filtered, to get rid of empty rows (where A entry is blank)
mapped, by the function that takes every row r1 and then
reduces the array, counting each row (r2) only if it has the same category and smaller value than r1. It returns the count plus 1, so the smallest element gets rank 1.
No tie-breaking is implemented: for example, if there are two smallest elements, they both get rank 1, and there is no rank 2; the next smallest element gets rank 3.
Well this does give an answer, but I had to go through a fairly complicated manoeuvre to find it:
=ArrayFormula(iferror(VLOOKUP(row(A2:A),{sort({row(A2:A),A2:B},2,1,3,1),row(A2:A)},4,false)-rank(A2:A,A2:A,true),""))
So
Sort cols A and B with their row numbers.
Use a lookup to find where those sorted row numbers now are: their position gives the rank of that row in the original data plus 1 (3,4,2,6,5,7).
Return the new row number.
Subtract the rank obtained just by ranking on column A (1,1,1,4,4,4) to get the rank within each group.
In the particular case where the classifiers (col A) are whole numbers and the measurements (col B) are fractions, you could just add the two columns and use rank:
=ArrayFormula(iferror(rank(A2:A+B2:B,if(A2:A<>"",A2:A+B2:B),true)-rank(A2:A,A2:A,true)+1,""))
My version of an array formula, it works when column A contains text:
=ARRAYFORMULA(RANK(ARRAY_CONSTRAIN(VLOOKUP(A1:A,{UNIQUE(FILTER(A1:A,A1:A<>"")),ROW(INDIRECT("a1:a"&COUNTUNIQUE(A1:A)))},2,)*1000+B1:B,COUNTA(A1:A),1),ARRAY_CONSTRAIN(VLOOKUP(A1:A,{UNIQUE(FILTER(A1:A,A1:A<>"")),ROW(INDIRECT("a1:a"&COUNTUNIQUE(A1:A)))},2,)*1000+B1:B,COUNTA(A1:A),1),1) - COUNTIF(A1:A,"<"&OFFSET(A1,,,COUNTA(A1:A))))

Generate a list of all unique values of a multi-column range and give the values a rating according to how many times they appear in the last X cols

As the title says.
I have a range like this:
A B C
------ ------ ------
duck fish dog
rat duck cat
dog bear bear
What I want is to get a single-column list of all the unique values in the range, and assign them a rating (or tier) according to the number of times they have appeared in the last X columns (more columns are constantly added to the right side).
For example, let's say:
Tier 0: hasn't appeared in the last 2 columns.
Tier 1: has appeared once in the last 2 columns.
Tier 2: has appeared twice in the last 2 columns.
So the results should be:
Name Tier
------ ------
duck 1
rat 0
dog 1
fish 1
bear 2
cat 1
I was able to generate a list of unique values by using:
=ArrayFormula(UNIQUE(TRANSPOSE(SPLIT(CONCATENATE(B2:ZZ9&CHAR(9)),CHAR(9)))))
But it's the second part that I am not sure exactly how to achieve. Can this be done through Google Sheets commands or will I have to resort to scripting?
Sorry, my knowledge is not enough to build an array-formula but I can explain how I get it per cell and then expanded a range from it.
Part 1: count the number of nonempty columns (assuming that if column has something on the second row, then it's filled.
COUNTA( FILTER( Sheet1!$B$2:$Z$2 , NOT( ISBLANK( Sheet1!$B$2:$Z$2 ) ) ) )
Part 2: build a range for the last two filled columns:
OFFSET(Sheet1!$A$2, 0, COUNTA( ... )-1, 99, 2)
Part 3: use COUNTIF to count how many values of "bear" we meet there (here we can pass a cell-reference instead) :
COUNTIF(OFFSET( ... ), "bear")
I built a sample spreadsheet that gets the results, here's the link (I know external links are bad, but there's no other choice to show the reproducible example).
Sheet1 contains the data, Sheet2 contains the counts.
I suggest using both script and the formula.
Normalize the data
Script is the easiest way to normalize data. It will convert your columns into single column data:
/**
* converts columns into one column.
*
* #param {data} input the range.
* #return Column number, Row number, Value.
* #customfunction
*/
function normalizeData(data) {
var normalData = [];
var line = [];
var dataLine = [];
// headers
dataLine.push('Row');
dataLine.push('Column');
dataLine.push('Data');
normalData.push(dataLine);
// write data
for (var i = 0; i < data.length; i++) {
line = data[i];
for (var j = 0; j < line.length; j++) {
dataLine = [];
dataLine.push(i + 1);
dataLine.push(j + 1);
dataLine.push(line[j]);
normalData.push(dataLine);
}
}
return normalData;
}
Test it:
Go to the script editor: Tools → Editor (or in Chrome browser: [Alt → T → E])
After pasting this code into the script editor, use it as simple formula: =normalizeData(data!A2:C4)
You will get the resulting table:
Row Column Data
1 1 duck
1 2 fish
1 3 dog
2 1 rat
2 2 duck
2 3 cat
3 1 dog
3 2 bear
3 3 bear
Then use it to make further calculations. There are a couple of ways to do it. One way is to use extra column with criteria, in column D paste this formula:
=ARRAYFORMULA((B2:B>1)*1)
it will check if column number is bigger then 1 and return ones and zeros.
Then make simple query formula:
=QUERY({A:D},"select Col3, sum(Col4) where Col1 > 0 group by Col3")
and get the desired output.

Formula for conditionally summing a range

What I am trying to achieve is shown below. I am sure that there is a formula (or a few) that I can use to calculate it so I don't have to hardcode it for each column total. Perhaps it's a combination of =SUM and =SUMIF but I'm not sure how to build it with a #. Pseudocode of the solution to a single column is provided after the photo.
FUNCTION getTotal (range)
let total = 0
FOR i = 0 TO range.total DO
IF range[i].hasContents DO
total += "# amount"
ENDIF
ENDFOR
return total
ENDFUNCTION
PS: I am actually doing this on Google Spreadsheets so an excel only solution would NOT be helpful (i.e. don't give me a VBA script)
You mentioned SUMIF and I can't see what would not suit with:
=SUMIF(C1:C12,"X",$B1:$B12)
copied across, assuming # is in B1 and Column B is summed with =SUM(B2:B12).

Resources