Как передать диапазон в пользовательскую функцию в таблицах Google?

44

Я хочу создать функцию, которая принимает диапазон ... что-то вроде:

function myFunction(range) {
  var firstColumn = range.getColumn();
  // loop over the range
}

Ячейка будет ссылаться на него, используя:

=myFunction(A1:A4)

Проблема в том, что когда я пытаюсь сделать это, параметр кажется, что он только передает значения в функцию. Таким образом, я не могу использовать любой из методов Range, таких как getColumn(). Когда я пытаюсь это сделать, выдается следующая ошибка:

error: TypeError: Невозможно найти функцию getColumn в объекте 1,2,3.

Как я могу отправить фактический диапазон, а не только значения одной из моих пользовательских функций?

Senseful
источник

Ответы:

23

Так что я искал долго и упорно за хороший ответ на это, и вот что я нашел:

  1. неизмененный параметр диапазона передает значения ячеек в диапазоне, а не сам диапазон (как объяснил Гергели), например: когда вы делаете это=myFunction(a1:a2)

  2. чтобы использовать диапазон в вашей функции, вам нужно сначала передать диапазон в виде строки (например:) =myFunction("a1:a2"), а затем превратить его в диапазон со следующим кодом внутри функции:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Наконец, если вам нужны преимущества передачи диапазона (например, интеллектуальное копирование ссылок на диапазоны в другие ячейки) И вы хотите передать его в виде строки, вы должны использовать функцию по умолчанию, которая принимает диапазон в качестве параметра и выводит Строка, которую вы можете использовать. Я подробно описываю оба способа, которые я нашел ниже, но я предпочитаю второй.

    Для всех таблиц Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Для новых Google Sheets: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

Натан Ханна
источник
Не могли бы вы пояснить, что означает «интеллектуальное копирование ссылок на диапазоны в другие ячейки»?
Эмерсон Фарругия
1
Спасатель. Это работает, спасибо.
Джитин Павитран
@EmersonFarrugia Я имею в виду возможность для листов Google автоматически обновлять относительные ссылки на ячейки при копировании функций из одной ячейки в другую
Натан Ханна
9

rangeтрактуется как 2d массив javascript Вы можете получить количество строк с range.lengthи количество столбцов с range[0].length. Если вы хотите получить значение из строки rи столбца cиспользования: range[r][c].

Lipis
источник
Я пытаюсь выяснить, что является первым столбцом в диапазоне. Например, в диапазоне C1: F1 я хочу знать, что диапазон начинается со столбца C.
Смысл
@Senseful Если вы будете использовать свою функцию в такой ячейке, как:, =myFunction(C1:F1)то внутри функции range[0][0]вернет значение C1, range[0][1]вернет значение D1, range[0][2]вернет значение E1и т. Д. Это ясно?
Липис
Я думаю, что я не объясняю себя ясно ... посмотрите на ваш ответ на этот вопрос . Вы рекомендовали, чтобы я использовал =weightedAverage(B3:D3,$B$2:$D$2), я хотел бы сделать функцию более защищенной от ошибок, не отправляя 2-й аргумент (т.е. я хочу вызвать его как =weightedAverage(B3:D3)), а затем чтобы код автоматически знал, что диапазон начинается с B и заканчивается на D, поэтому он получает соответствующие значения из 2-го ряда. Примечание: значение «2» может быть жестко закодировано, но «B» не должно быть жестко закодировано.
Чувствительный
1
@Senseful В общем, я против жестко закодированных вещей, и я думаю, что будет понятнее, если средневзвешенное значение будет принимать два параметра. Так что, если вы собираетесь писать жесткий код, есть много других вещей, которые вы могли бы сделать. Я не могу сейчас найти, как мы можем получить Bот данного range. Я бы посоветовал вам ознакомиться с руководством по началу работы ( goo.gl/hm0xT ), если вы этого еще не сделали, чтобы понять, как вы можете играть с диапазонами и ячейками.
Липис
6

При передаче Rangeфункции электронной таблицы Google платформа выполняется paramRange.getValues()неявно, и ваша функция получает значения в диапазоне в виде двумерного массива строк, чисел или объектов (например Date). RangeОбъект не передается вашей пользовательской функции электронной таблицы.

TYPEOF()Ниже функция покажет вам , какие данные вы получаете в качестве параметра. Формула

=TYPEOF(A1:A4)

вызовем скрипт так:

function executeCell () {
  var resultCell = SpreadsheetApp.getActiveSheet (). getActiveCell ();
  var paramRange = SpreadsheetApp.getActiveSheet (). getRange ('A1: A4');
  var paramValues ​​= paramRange.getValues ​​();

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
функция TYPEOF (значение) {
  if (typeof value! == 'object')
    возвращаемое значение typeof;

  var details = '';
  if (значение instanceof Array) {
    детали + = '[' + value.length + ']';
    if (значение [0] instanceof Array)
      детали + = '[' + значение [0] .length + ']';
  }

  var className = value.constructor.name;
  вернуть className + подробности;
}
Грегориус
источник
3

В качестве альтернативы, вдохновленной комментарием @Lipis, вы можете вызвать вашу функцию с координатами строки / столбца в качестве дополнительных параметров:

=myFunction(B1:C2; ROW(B1); COLUMN(B1); ROW(C2); COLUMN(C2))

В этой форме формулу можно легко скопировать (или перетащить) в другие ячейки, и ссылки на ячейки / диапазоны будут автоматически скорректированы.

Ваша функция Script должна быть обновлена ​​следующим образом:

function myFunction(rangeValues, startRow, startColumn, endRow, endColumn) {
  var range = SpreadsheetApp.getActiveSheet().getRange(startRow, startColumn, endRow - startRow + 1, endColumn - startColumn + 1);
  return "Range: " + range.getA1Notation();
}

Обратите внимание, что getRangeфункция принимает startRow, startColumnа затем количество строк и количество столбцов - не конец строки и конец столбца. Итак, арифметика.

Видар С. Рамдал
источник
Это точный ответ, который я ожидаю. РЯД И КОЛОННА.
Джиану Чен
3

Это можно сделать . Можно получить ссылку на переданный диапазон, анализируя формулу в активной ячейке, которая является ячейкой, содержащей формулу. Это делает предположение, что пользовательская функция используется сама по себе, а не как часть более сложного выражения: например =myfunction(A1:C3), нет =sqrt(4+myfunction(A1:C3)).

Эта функция возвращает индекс первого столбца переданного диапазона.

function myfunction(reference) {
  var sheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var args = formula.match(/=\w+\((.*)\)/i)[1].split('!');
  try {
    if (args.length == 1) {
      var range = sheet.getRange(args[0]);
    }
    else {
      sheet = ss.getSheetByName(args[0].replace(/'/g, ''));
      range = sheet.getRange(args[1]);
    }
  }
  catch(e) {
    throw new Error(args.join('!') + ' is not a valid range');
  }

  // everything so far was only range extraction
  // the specific logic of the function begins here

  var firstColumn = range.getColumn();  // or whatever you want to do with the range
  return firstColumn;
}

источник
Я думаю, что этот пост также ответит на следующий вопрос о переполнении стека : передача ссылок на ячейки в функции электронных таблиц
Рубен
2

Я продлил прекрасную идею в user79865 игрового ответа , чтобы заставить его работать на более случаи и несколько аргументов, передаваемые в пользовательской функцию.

Чтобы использовать его, скопируйте приведенный ниже код, а затем в свой вызов пользовательской функции, GetParamRanges()например, передав ему имя своей функции:

function CustomFunc(ref1, ref2) {

  var ranges = GetParamRanges("CustomFunc"); // substitute your function name here

  // ranges[0] contains the range object for ref1 (or null)
  // ranges[1] contains the range object for ref2 (or null)

  ... do what you want

  return what_you_want;
}

Вот пример, чтобы вернуть цвет ячейки:

/**
* Returns the background color of a cell
*
* @param {cell_ref} The address of the cell
* @return The color of the cell
* @customfunction
*/
function GetColor(ref) {

  return GetParamRanges("GetColor")[0].getBackground();
}

Вот код и еще несколько объяснений:

/**
* Returns an array of the range object(s) referenced by the parameters in a call to a custom function from a cell
* The array will have an entry for each parameter. If the parameter was a reference to a cell or range then 
* its array element will contain the corresponding range object, otherwise it will be null.
*
* Limitations:
* - A range is returned only if a parameter expression is a single reference.
*   For example,=CustomFunc(A1+A2) would not return a range.
* - The parameter expressions in the cell formula may not contain commas or brackets.
*   For example, =CustomFunc(A1:A3,ATAN2(4,3),B:E) would not parse correctly.
* - The custom function may not appear more than once in the cell formula.
* - Sheet names may not contain commas, quotes or closing brackets.
* - The cell formula may contain white space around the commas separating the custom function parameters, or after
*   the custom function name, but not elsewhere within the custom function invocation.
* - There may be other limitations.
* 
* Examples:
*   Cell formula: =CUSTOMFUNC($A$1)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: range object for cell A1 in the sheet containing the formula
*
*   Cell formula: =CUSTOMFUNC(3, 'Expenses'!B7)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell B7 in sheet Expenses
* 
*   Cell formula: =sqrt(4+myfunction(A1:C3))
*   Usage:        var ranges = GetParamRanges("MyFunction");
*   Result:       ranges[0]: range object for cells A1 through C3 in the sheet containing the formula
*
*   Cell formula: =CustomFunc(A1+A2, A1, A2)
*   Usage:        var ranges = GetParamRanges("CustomFunc");
*   Result:       ranges[0]: null
*                 ranges[1]: range object for cell A1 in the sheet containing the formula
*                 ranges[2]: range object for cell A2 in the sheet containing the formula
*
* @param {funcName} The name of the custom function (string, case-insensitive)
* @return The range(s) referenced by the parameters to the call
*/
function GetParamRanges(funcName) {
  var ourSheet = SpreadsheetApp.getActiveSheet();
  var formula = SpreadsheetApp.getActiveRange().getFormula();
  var re = new RegExp(".+" + funcName + "\\s*\\((.*?)\\)","i");
  var ranges=[]; // array of results

  try {
    var args = formula.match(re)[1].split(/\s*,\s*/) // arguments to custom function, separated by commas
    // if there are no args it fails and drops out here

    for (var i=0; i<args.length; i++) {
      var arg=args[i].split('!'); // see if arg has a sheet name
      try {
        if (arg.length == 1) { // if there's no sheet name then use the whole arg as the range definition
          ranges[i] = ourSheet.getRange(arg[0]);
        }
        else { // if there's a sheet name, use it (after removing quotes around it)
          var sheetName=arg[0].replace(/'/g, '');
          var otherSheet=SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetName);
          ranges[i] = otherSheet.getRange(arg[1]);
        }
      }
      catch(e) { // assume it couldn't be identified as a range for whatever reason
        ranges[i]=null;
      }
    }
  }
  catch(e) {}

  return ranges
}
Ян Виней
источник
1
совсем неплохо, но, пожалуйста, отметьте, что это делает предположение, что функция сгорания используется только один раз в формуле. Fi, я получил использовать формулы, такие как: = CountCcolor (B5: B38, $ A40) / 2 + CountCcolor (B5: B38, $ A41) / 2 + CountCcolor (B5: B38, $ A42) / 2 + CountCcolor (B5: B38 , $ A43) / 2 + CountCcolor (B5: B38, $ A44) / 2 Что, насколько я понимаю, не будет работать таким образом. Если бы мы решили, что смогли бы справиться с этим, было бы здорово.
Хемс
1

Я различаю две разные пользовательские функции в электронной таблице Google с помощью скрипта Google Apps:

  1. Тот, который требует API; SpreadsheetApp, ( пример )
  2. Тот, который не делает никаких звонков, ( пример )

Первая функция способна сделать практически все. Помимо вызова службы электронных таблиц, она может вызывать GmailApp или любую службу, предоставляемую Google. Вызовы API замедляют процесс. Диапазон может быть передан или получен через функцию, чтобы получить доступ ко всем доступным методам.

Вторая функция ограничена только «электронной таблицей». Здесь можно использовать JavaScript для обработки данных. Эти функции обычно очень быстрые. Переданный диапазон - это не более чем 2D-массив, содержащий значения.


В своем вопросе вы начинаете с вызова .getColumn()метода. Это ясно указывает на то, что вам нужна пользовательская функция типа 1, как описано выше.

См. Следующий ответ о том, как настроить электронную таблицу и лист, чтобы создать пользовательскую функцию.

Джейкоб Ян Туинстра
источник
1

Я работал над этим несколько месяцев назад и придумал очень простой ключ: создайте новый лист с именем каждой ячейки в качестве содержимого: ячейка A1 может выглядеть так:

= arrayformula(cell("address",a1:z500))

Назовите лист «Реф». Затем, когда вам нужна ссылка на ячейку в виде строки вместо содержимого, вы используете:

= some_new_function('Ref'!C45)

Конечно, вам нужно проверить, передается ли функции строка (одна ячейка) или 1D или 2D Array. Если вы получите массив, он будет иметь все адреса ячеек в виде строк, но из первой ячейки, а также по ширине и высоте вы можете определить, что вам нужно.

HardScale
источник
0

Я работал над этим все утро и видел доказательства того, что обсуждают вышеупомянутые не ответы. Смысл и я оба хотим АДРЕС переданной ячейки, а не значение. И ответ очень прост. Это не может быть сделано.

Я нашел некоторые обходные пути, которые основаны на выяснении, в какой ячейке содержится формула. Трудно сказать, помогло ли это Senseful выше. Так что я делал?

The data.

     [A]      [B]       [C]       [D]     [E]     [F]       [H]
[1] Name      Wins     Losses    Shots   Points   Fouls   Most recent
                                                          WL Ratio
[2] Sophia     4         2         15      7       1         0
[3] Gloria     11        3         11      6       0         0
[4] Rene       2         0         4       0       0         0
[5] Sophia     7         4         18      9       1         1.5

Колонка H - это София (Победы - Предыдущие победы) / (Потери - Предыдущие потери)

(7 - 4) / (4 - 2) = 1,5

Но мы не знаем, в какой строке появилась София. Все
это можно сделать с помощью VLOOKUP, если вы жестко закодировали A в качестве столбца имени. После VLOOKUP я получил несколько #NA (имя не найдено) и # DIV0 (нулевой знаменатель) и заключил их в = IF (IF (...)), чтобы показать более приемлемый текст в этих условиях. Теперь у меня было чудовищно большое выражение, которое было громоздким и неприемлемым. Поэтому я хотел расширения макроса (не существует) или пользовательских функций.

Но когда я сделал вспомогательный SubtractPrevValue (ячейка), он получал «7» вместо B5. Не существует встроенного способа получить объекты ячейки или диапазона из переданных аргументов.
Если я заставлю пользователя вручную ввести имя ячейки в двойных кавычках, тогда я смогу сделать это ... SubtractPrevValue ("B5"). Но это действительно копирование / вставка подколенного сухожилия и относительные особенности ячейки.

Но потом я нашел обходной путь.

SpreadsheetApp.getActiveRange () - это клетка, которая содержит формулу. Это все, что мне действительно нужно было знать. Номер строки. Следующая функция берет числовой номер столбца и вычитает предыдущее вхождение в этот столбец.

function SubtractPrevValue(colNum) 
{
  var curCell = SpreadsheetApp.getActiveRange();
  var curSheet = curCell.getSheet();
  var curRowIdx = curCell.getRowIndex();
  var name = curSheet.getRange(curRowIdx, 1).getValue();  // name to match
  var curVal =  curSheet.getRange(curRowIdx, colNum).getValue();

  var foundRowIdx = -1;
  for (var i=curRowIdx-1;i>1;i--)
  { 
    if (curSheet.getRange(i, 2).getValue() == name)
    {
      return curVal - curSheet.getRange(i, colNum).getValue();
    }
  }
  return curVal;  //default if no previous found
}

Но потом я обнаружил, что это действительно очень медленно. Задержка на одну и две секунды, когда отображается «Мышление ...» Так что я вернулся к совершенно неразборчивой, не поддерживаемой формуле листа.

Марк С
источник
0

Вместо предоставления объекта «Диапазон» разработчики Google передают в основном случайный тип данных. Итак, я разработал следующий макрос, чтобы распечатать значение ввода.

function tp_detail(arg)
{
    var details = '';

    try
    {
        if(typeof arg == 'undefined')
           return details += 'empty';

        if (typeof arg !== 'object')
        {
            if (typeof arg == 'undefined')
                return details += 'empty';

            if (arg.map)
            {
                var rv = 'map: {';
                var count = 1;
                for (var a in arg)
                {
                    rv += '[' + count + '] ' + tp_detail(a);
                    count = count + 1;
                }
                rv += '}; '
                return rv;
            }
            return (typeof arg) + '(\'' + arg + '\')';
        }


        if (arg instanceof Array)
        {
            details += 'arr[' + arg.length + ']: {';
            for (var i = 0; i < arg.length; i++)
            {
                details += '[' + i + '] ' + tp_detail(arg[i]) + ';';
            }
            details += '} ';
        }
        else
        {

            var className = arg.constructor.name;
            return className + '(' + arg + ')';
        }
    }
    catch (e)
    {
        var details = '';
        details = 'err : ' + e;
    }


    return details;
}
Настоящее имя
источник
0

Есть моя подборка предыдущих ответов

**
 *
 * @customfunction
 **/
function GFR(){
  var ar = SpreadsheetApp.getActiveRange();
  var as = SpreadsheetApp.getActive();
  return ar.getFormula()
    .replace(/=gfr\((.*?)\)/i, '$1')
    .split(/[,;]/)
    .reduce(function(p, a1Notation){
      try{
        var range = as.getRange(a1Notation);
        p.push(Utilities.formatString(
          'Is "%s" a Range? %s', 
          a1Notation,
          !!range.getDisplayValues
        ));
      } catch(err){
        p.push([a1Notation + ' ' + err.message]);
      }
    return p;
  },[]);
}

Он принимает текущую формулу и проверяет, является ли это Range.

=GFR(A1:A5;1;C1:C2;D1&D2) возвращается

|Is "A1:A5" a Range? true|
|1 Range not found       |
|Is "C1:C2" a Range? true|
|D1&D2 Range not found   |

Для полного состава ТС можно назвать примерно так

var range = as.getRange(a1Notation);
var column = range.getColumn();
contributorpw
источник
-1

Создайте функцию и передайте диапазон в качестве аргумента:

function fn(input) {
    var cell = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0].getRange(SpreadsheetApp.getActiveRange().getFormula().match(/=\w+\((.*)\)/i)[1].split('!'));
    // above ... cell is "range", can convert to array?
    var sum=0;
    for (var i in cell.getValues() ){
        sum = sum + Number(cell.getValues()[i]);
    }  
    return sum;
}

Использование в таблице:

=fn(A1:A10)

Это покажет значение как sum(A1:A10).

นาย เอกชัย บุญ ทิ สา
источник