How can I convert this function into an array function? - google-sheets

So I have this function here that runs through T2:T:
=IF($D$29<$N2,"", AVERAGE(INDIRECT("P"&IF($N2<11, 2,$N2-5)&":P"&$N2+5)))
Column P is a list of numbers starting at row 2. Column N is an index(goes up by 1 each row) which starts at row 2 and ends where P ends + 14, and D29 is just a number. In my current situation P ends at row 11 and N ends at row 25. And I'm trying to change it into an array formula so that when I add new rows it updates automatically. So after changing it I got this:
=ARRAYFORMULA(IF($D$29<$N2:N,"", AVERAGE(INDIRECT("P"&IF($N2:N<11, 2,$N2:N-5)&":P"&$N2:N+5))))
However, it is not functioning properly. It still occupies the same amount of rows, but each row is the same value. The value of the first row originally. How can I fix this problem? Thanks!

The problem here is that ARRAYFORMULA doesn't work with AVERAGE.
But you could always use javascript.
Open up the script editor and paste in this code.
function avg(nums, d) {
var r = [],
i, j, start, end, avg, count;
for(i = 0; i < nums.length; i++) {
if(d <= i) r.push([""]);
else {
if(i < 10) start = 0;
else start = i - 5;
end = i + 4;
avg = 0, count = 0;
for(j = start; j <= end; j++) {
if(nums[j]) {
avg += nums[j][0];
count++;
}
}
r.push([avg / count]);
}
}
return r;
}
Save it, go back to your spreadsheet and put this formula in any cell =avg(P2:P11, D29)

Related

Calculate sum of row but its initial row number and row count

Let's say I have a column of numbers:
1
2
3
4
5
6
7
8
Is there a formula that can calculate sum of numbers starting from n-th row and adding to the sum k numbers, for example start from 4th row and add 3 numbers down the row, i.e. PartialSum(4, 3) would be 4 + 5 + 6 = 15
BTW I can't use App Script as now it has some type of error Error code RESOURCE_EXHAUSTED. and in general I have had issue of stabile work with App Script before too.
As Tanaike mentioned, the error code when using Google Apps Script was just a temporary bug that seems to be solved at this moment.
Now, I can think of 2 possible solutions for this using custom functions:
Solution 1
If your data follows a specific numeric order one by one just like the example provided in the post, you may want to consider using the following code:
function PartialSum(n, k) {
let sum = n;
for(let i=1; i<k; i++)
{
sum = sum + n + i;
}
return sum;
}
Solution 2
If your data does not follow any particular order and you just want to sum a specific number of rows that follow the row you select, then you can use:
function PartialSum(n, k) {
let ss = SpreadsheetApp.getActiveSheet();
let r = ss.getRange(n, 1); // Set column 1 as default (change it as needed)
let sum = n;
for(let i=1; i<k; i++)
{
let val = ss.getRange(n + i, 1).getValue();
sum = sum + val;
}
return sum;
}
Result:
References:
Custom Functions in Google Sheets
Formula:
= SUM( OFFSEET( initialCellName, 0, 0, numberOfElementsInColumn, 0) )
Example add 7 elements starting from A5 cell:
= SUM( OFFSEET( A5, 0, 0, 7, 0) )

How can I generate a unique, predictable, repeatable, non sequential alphanumeric identifier?

I have to generate identifiers composed of four alphanumerical characters, e.g. B41F.
I have the following requirements:
Each identifier must be unique (there is no central location to lookup existing identifiers)
The identifier must not be obviously sequential (e.g. 1A01, 1A02)
It must be predictable
It must be repeatable using solely the identifier index (on two different environment, the Nth identifier generated, which has index N, must be the same)
The problem is generic to any language. My implementation will be done in dart.
I think this could be done with a PRNG and some LUT, but I could not find any implementation or pseudo-code that respects requirement 4) without replaying the whole sequence. Also, some PRNG implementation have a random component that is not guaranteed to be repeatable over library update.
How can I achieve this? I'm looking for pseudo-code, code or hints.
You should not use a PRNG when identifiers must be unique. RNGs do not promise uniqueness. Some might have a long period before they repeat, but that's at their full bit-range, reducing it to a smaller number may cause conflicts earlier.
Your identifiers are really just numbers in base 36, so you need something like shuffle(index).toRadixString(36) to generate it.
The tricky bit is the shuffle function which must be a permutations of the numbers 0..36^4-1, one which looks random (non-sequential), but can be computed (efficiently?) for any input.
Since 36^4 is not a power of 2, most of the easy bit-shuffles likely won't work.
If you can live with 32^4 numbers only (2^20 ~ 1M) it might be easier.
Then you can also choose to drop O, I, 0 and 1 from the result, which might make it easier to read.
In that case, I'd do something primitive (not cryptographically secure at all), like:
// Represent 20-bit numbers
String represent(int index) {
RangeError.checkValueInInterval(index, 0, 0xFFFFF, "index");
var digits = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
return "${digits[(index >> 15) & 31]}${digits[(index >> 10) & 31]}"
"${digits[(index >> 5) & 31]}${digits[index & 31]}";
}
// Completely naive number shuffler for 20-bit numbers.
// All numbers made up on the spot.
int shuffle(int index) {
RangeError.checkValueInInterval(index, 0, 0xFFFFF, "index");
index ^= 0x35712;
index ^= index << 15;
index ^= index << 4;
index ^= index << 12;
index ^= index << 7;
index ^= index << 17;
return index & 0xFFFFF; // 20 bit only.
}
If you really want the full 36^4 range to be used, I'd probably do something like the shuffle, but in base-six arithmetic. Maybe:
String represent(int index) =>
RangeError.checkValueInInterval(index, 0, 1679615, "index")
.toRadixString(36).toUpperCase();
int shuffle(int index) {
RangeError.checkValueInInterval(index, 0, 1679615, "index");
const seed = [1, 4, 2, 5, 0, 3, 1, 4]; // seed.
var digits = List<int>.filled(8, 0);
for (var i = 0; i < 8; i++) {
digits[i] = index.remainder(6);
index = index ~/ 6;
}
void shiftAdd(List<int> source, int shift, int times) {
for (var n = digits.length - 1 - shift; n >= 0; n--) {
digits[shift + n] = (digits[shift + n] + source[n] * times).remainder(6);
}
}
shiftAdd(seed, 0, 1);
shiftAdd(digits, 3, 2);
shiftAdd(digits, 5, 1);
shiftAdd(digits, 2, 5);
var result = 0;
for (var i = digits.length - 1; i >= 0; i--) {
result = result * 6 + digits[i];
}
return result;
}
Again, this is something I made up on the spot, it "shuffles", but does not promise anything about the properties of the result, other than that they don't look sequential.

Dart: how to convert a column letter into number

Currently using Dart with gsheets_api, which don't seem to have a function to convert column letters to numbers (column index)
As an example , this is what I use with AppScript (input: column letter, output: column index number):
function Column_Nu_to_Letter(column_nu)
{
var temp, letter = '';
while (column_nu > 0)
{
temp = (column_nu - 1) % 26;
letter = String.fromCharCode(temp + 65) + letter;
column_nu = (column_nu - temp - 1) / 26;
}
return letter;
};
This is the code I came up for Dart, it works, but I am sure there is a more elegant or correct way to do it.
String colLetter = 'L'; //Column 'L' as example
int c = "A".codeUnitAt(0);
int end = "Z".codeUnitAt(0);
int counter = 1;
while (c <= end) {
//print(String.fromCharCode(c));
if(colLetter == String.fromCharCode(c)){
print('Conversion $colLetter = $counter');
}
counter++;
c++;
}
// this output L = 12
Do you have any suggestions on how to improve this code?
First we need to agree on the meaning of the letters.
I believe the traditional approach is "A" is 1, "Z" is 26, "AA" is 27, "AZ" is 52, "BA" is 53, etc.
Then I'd probably go with something like these functions for converting:
int lettersToIndex(String letters) {
var result = 0;
for (var i = 0; i < letters.length; i++) {
result = result * 26 + (letters.codeUnitAt(i) & 0x1f);
}
return result;
}
String indexToLetters(int index) {
if (index <= 0) throw RangeError.range(index, 1, null, "index");
const _letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (index < 27) return _letters[index - 1];
var letters = <String>[];
do {
index -= 1;
letters.add(_letters[index.remainder(26)]);
index ~/= 26;
} while (index > 0);
return letters.reversed.join("");
}
The former function doesn't validate that the input only contains letters, but it works correctly for strings containing only letters (and it ignores case as a bonus).
The latter does check that the index is greater than zero.
A simplified version base on Irn's answer
int lettersToIndex(String letters) =>
letters.codeUnits.fold(0, (v, e) => v * 26 + (e & 0x1f));
String indexToLetters(int index) {
var letters = '';
do {
final r = index % 26;
letters = '${String.fromCharCode(64 + r)}$letters';
index = (index - r) ~/ 26;
} while (index > 0);
return letters;
}

Google sheets copy and paste row 120 times in the same sheet

So I have a sheet with about 800 rows. What I need to do is copy and paste each one 120 times. Instead of doing this manually I am hoping there is an automated way to do this in one shot. I have searched for a few things but everything I found had to do with copying the rows conditionally.
I don't need conditions. I just need all, each one of the 800 rows to be duplicated 120 times.
Any help or ideas is greatly appreciated.
Cheers
Modified #SpiderPig code to clone in a interleaved fashion
function duplicateRows() {
var sheet = SpreadsheetApp.getActiveSheet()
var numRows = sheet.getLastRow()
var numColumns = sheet.getLastColumn()
var numberOfClones = 120
for(var i = 0; i < numRows; i++) {
range = sheet.getRange((i*numberOfClones)+ 1, 1, 1, numColumns);
sheet.insertRows((i*numberOfClones)+ 2, numberOfClones-1)
range.copyTo(sheet.getRange((i*numberOfClones)+ 2 , 1, numberOfClones-1, numColumns));
}
}
Also instead of using a loop with range.copyTo(destination) to paste 120 times, you can expand the destination range to 120 rows. It will automatically paste the same value over the entire range.
So instead of this
for(var i = 1; i <= 120; i++) {
range.copyTo(sheet.getRange(numRows * i + 1, 1, numRows, numColumns));
}
you can do this once
range.copyTo(sheet.getRange(numRows * i + 1, 1, 120*numRows, numColumns));
Here is a script that will copy all the rows in the current sheet 120 times.
function duplicateRows() {
var sheet = SpreadsheetApp.getActiveSheet(),
numRows = sheet.getLastRow(),
numColumns = sheet.getLastColumn(),
range = sheet.getRange(1, 1, numRows, numColumns);
for(var i = 1; i <= 120; i++) {
range.copyTo(sheet.getRange(numRows * i + 1, 1, numRows, numColumns));
}
}

Dividing a value between non-equal rows in order to balance them

I have a spreadsheet that's structured like:
Section Total Incoming New Total
AK 56,445 2,655 59,100
AL 58,304 796 59,100
B 55,524 3,576 59,100
C 54,272 4,828 59,100
D 53,956 5,144 59,100
S 59,161 0 59,161
-
Generated Pts 16,999
I'm trying to automate the "Incoming" column. The goal of the sheet is to balance the Totals as closely as possible by distributing the Generated Pts between each row until no more points remain, ensuring that the lowest totals are always increased first so that higher values aren't increased while lower values exist.
Is this possible in a spreadsheet? Any suggestions on how this could be done?
I made an attempt at a custom function. Two parameters are passed: the range corresponding with your Total column, and the cell containing the generated pts. Then the Incoming array is returned.
function distribute(range, value) {
var indexedRange = range.map(function (e, index) {return [e[0], e[0], index];});
indexedRange.sort(function (a, b) {return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;});
var count = 0, i = 0, limit = indexedRange.length - 1;
while (count < value) {
indexedRange[i][0] ++;
i = i == limit || indexedRange[i][0] <= indexedRange[i + 1][0] ? 0 : i + 1;
count++;
}
indexedRange.sort(function (a, b) {return a[2] < b[2] ? -1 : a[2] > b[2] ? 1 : 0;});
return indexedRange.map(function (e) {return [e[0] - e[1]];});
}
It matches your expected results, but you might want to try it out on different data to check my logic is OK.

Resources