Como passar um intervalo para uma função personalizada nas Planilhas do Google?

44

Eu quero criar uma função que leva em um intervalo ... algo como:

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

Uma célula faria referência a ela usando:

=myFunction(A1:A4)

O problema é que, quando tento fazer isso, parece que o parâmetro está apenas passando os valores para a função. Portanto, não posso usar nenhum dos métodos Range , como getColumn(). Quando tento fazer isso, ele dá o seguinte erro:

error: TypeError: Não foi possível encontrar a função getColumn no objeto 1,2,3.

Como posso enviar um intervalo real em vez de apenas os valores para uma das minhas funções personalizadas?

Sensível
fonte

Respostas:

22

Então, eu procurei longa e duramente por uma boa resposta para isso e aqui está o que eu encontrei:

  1. um parâmetro de intervalo não modificado passa nos valores das células no intervalo, não no próprio intervalo (como Gergely explicou) ex: quando você faz isso=myFunction(a1:a2)

  2. Para usar um intervalo em sua função, você precisa primeiro passar o intervalo como uma string (ex:) =myFunction("a1:a2")e transformá-lo em um intervalo com o seguinte código dentro da função:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Por fim, se você deseja os benefícios de passar um intervalo (como copiar inteligentemente as referências de intervalo para outras células) E deseja passá-lo como uma sequência, você deve usar uma função padrão que aceite um intervalo como parâmetro e produza um string que você pode usar. Detalho as duas maneiras que encontrei abaixo, mas prefiro a 2ª.

    Para todas as planilhas do Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Para as novas Planilhas Google: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

Nathan Hanna
fonte
Você pode elaborar o que significa "copiar inteligentemente as referências de intervalo para outras células", por favor?
Emerson Farrugia
Lifesaver. Funciona, obrigado.
Jithin Pavithran
@EmersonFarrugia Estou me referindo à capacidade das folhas do google atualizarem automaticamente as referências relativas de células quando você copia funções de uma célula para outra
Nathan Hanna
9

rangeé tratado como o array 2D do javascript. Você pode obter o número de linhas com range.lengthe o número de colunas com range[0].length. Se você deseja obter o valor da linha re coluna de cuso: range[r][c].

Lipis
fonte
Estou tentando descobrir qual é a primeira coluna no intervalo. Por exemplo, no intervalo C1: F1, quero saber que o intervalo começa na coluna C.
Sensuous
@Senseful Se você usar sua função em uma célula como:, =myFunction(C1:F1)então dentro da função range[0][0]retornará o valor de C1, range[0][1]retornará o valor de D1, range[0][2]retornará o valor de E1, etc. Isso está claro?
Lipis
Acho que não estou me explicando claramente ... veja a sua resposta a esta pergunta . Você recomendou que eu usasse =weightedAverage(B3:D3,$B$2:$D$2), gostaria de tornar a função mais à prova de erro por não precisar enviar o segundo argumento (ou seja, eu quero chamá-lo como =weightedAverage(B3:D3)) e, em seguida, fazer com que o código saiba automaticamente que o intervalo começa em B e termina em D, para obter os valores correspondentes da 2ª linha. Nota: o valor "2" pode ser codificado, mas "B" não deve ser codificado.
Sensuous
1
@Senseful Em geral, sou contra coisas codificadas, e acho mais claro se a média ponderada levaria dois parâmetros. Portanto, se você está prestes a codificar, há muitas outras coisas que você poderia fazer. Não consigo encontrar agora como podemos tirar Bproveito de um dado range. Sugiro que você siga o guia de primeiros passos ( goo.gl/hm0xT ), se ainda não o fez, para ter uma idéia de como você pode brincar com intervalos e células.
Lipis
6

Ao passar Rangepara uma função de planilha do Google, a estrutura é executada paramRange.getValues()implicitamente e sua função recebe os valores no intervalo como uma matriz bidimensional de cadeias, números ou objetos (como Date). O Rangeobjeto não é passado para sua função de planilha personalizada.

A TYPEOF()função abaixo mostra quais tipos de dados você recebe como parâmetro. A fórmula

=TYPEOF(A1:A4)

chamará o script assim:

função calcularCell () {
  var resultCell = SpreadsheetApp.getActiveSheet (). getActiveCell ();
  var paramRange = SpreadsheetApp.getActiveSheet (). getRange ('A1: A4');
  var paramValues ​​= paramRange.getValues ​​();

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
função TYPEOF (valor) {
  if (tipo de valor! == 'objeto')
    retornar tipo de valor;

  var details = '';
  if (value instanceof Array) {
    detalhes + = '[' + value.length + ']';
    if (value [0] instanceof Array)
      detalhes + = '[' + valor [0]. comprimento + ']';
  }

  var className = value.constructor.name;
  retornar className + detalhes;
}
Gregorius
fonte
3

Como alternativa, inspirada no comentário do @Lipis, você pode chamar sua função com as coordenadas de linha / coluna como parâmetros adicionais:

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

Nesse formulário, a fórmula pode ser facilmente copiada (ou arrastada) para outras células e as referências de célula / intervalo serão ajustadas automaticamente.

Sua função de script precisaria ser atualizada da seguinte maneira:

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

Note que a getRangefunção recebe startRow, startColumne, em seguida, o número de linhas e número de colunas - não terminar linha e final da coluna. Assim, a aritmética.

Vidar S. Ramdal
fonte
Essa é a resposta exata que eu espero. FILEIRA E COLUNA.
Jianwu Chen 23/06
3

Isso pode ser feito . Pode-se obter uma referência ao intervalo passado analisando a fórmula na célula ativa, que é a célula que contém a fórmula. Isso pressupõe que a função personalizada seja usada por si só e não como parte de uma expressão mais complexa: por exemplo =myfunction(A1:C3), não =sqrt(4+myfunction(A1:C3)).

Esta função retorna o primeiro índice da coluna do intervalo passado.

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;
}

fonte
Eu acho que este post também responder a seguinte pergunta no Stack Overflow : passar referências de células para funções de planilha
Rubén
2

Estendi a excelente idéia na resposta do usuário79865 , para fazê-lo funcionar em mais casos e com vários argumentos sendo passados ​​para a função personalizada.

Para usá-lo, copie o código abaixo e, em seguida, na sua chamada de função personalizada GetParamRanges(), passando o nome da sua função:

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;
}

Aqui está um exemplo para retornar a cor de uma célula:

/**
* 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();
}

Aqui está o código e mais algumas explicações:

/**
* 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
}
Ian Viney
fonte
1
nada mal, mas por favor, declare que isso faz da suposição de que uma função de citação é usada apenas uma vez em uma fórmula. Fi eu usei forumals como: = 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 Que, pelo que entendi, não funcionará dessa maneira. Se resolvêssemos que seria capaz de lidar com isso, seria ótimo.
haemse 12/09
1

Distingo duas funções personalizadas diferentes na planilha do Google, via Script do Google Apps:

  1. Um que pede uma API; SpreadsheetApp, ( exemplo )
  2. Um que não faz chamadas ( exemplo )

A primeira função é capaz de fazer quase qualquer coisa. Além de chamar o serviço de planilha, ele também pode acessar o GmailApp ou qualquer serviço disponibilizado pelo Google. As chamadas à API desacelerarão o processo. Um intervalo pode ser passado ou recuperado através da função para acessar todo o método disponível.

A segunda função é limitada apenas à "Planilha". Aqui é possível usar o JavaScript para refazer os dados. Essas funções são normalmente muito rápidas. Um intervalo passado não passa de uma matriz 2D que contém valores.


Na sua pergunta, você começa chamando o .getColumn()método. Isso indica claramente que você precisa de uma função personalizada do tipo 1, conforme descrito acima.

Veja a resposta a seguir sobre como definir a planilha e a planilha, para criar uma função personalizada.

Jacob Jan Tuinstra
fonte
1

Eu estava trabalhando nisso há alguns meses e criei um kludge muito simples: crie uma nova planilha com o nome de cada célula como seu conteúdo: a célula A1 pode se parecer com:

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

Nomeie a planilha "Ref". Então, quando você precisar de uma referência a uma célula como uma sequência, em vez do conteúdo, use:

= some_new_function('Ref'!C45)

Obviamente, você precisará verificar se a função recebeu uma string (uma célula) ou uma matriz 1D ou 2D. Se você obtiver uma matriz, ela terá todos os endereços das células como cadeias, mas a partir da primeira célula e a largura e altura, você poderá descobrir o que precisa.

HardScale
fonte
0

Estive trabalhando nisso a manhã toda e vendo evidências do que discutem as não respostas acima. Sensuous e eu quero o endereço da célula passada, não o valor. E a resposta é muito fácil. Isso não pode ser feito.

Encontrei algumas soluções alternativas que dependem de descobrir qual célula contém a fórmula. É difícil dizer se isso teria ajudado o Senseful acima. Então o que eu estava fazendo?

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

A coluna H é de Sophia (Vitórias - PrevWins) / (Perdas - PrevLosses)

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

Mas não sabemos em que linha Sophia apareceu anteriormente.
Tudo isso pode ser feito usando o VLOOKUP se você codificar A como a coluna de nome. Após o VLOOKUP, eu estava obtendo alguns #NA (nome não encontrado) e # DIV0 (denominador zero) e o envolvi com = IF (IF (...)) para mostrar um texto mais palatável nessas condições. Agora eu tinha uma expressão monstruosamente grande que era pesada e insustentável. Então, eu queria expansão de macro (não existe) ou funções personalizadas.

Mas quando eu criei um auxiliar SubtractPrevValue (cell), ele estava recebendo "7" em vez de B5. Não há uma maneira interna de obter objetos de célula ou intervalo dos argumentos passados.
Se eu fizer o usuário digitar manualmente o nome da célula entre aspas duplas, poderá fazê-lo ... SubtractPrevValue ("B5"). Mas isso realmente isquiotibiais copiar / colar e características relativas da célula.

Mas então eu encontrei uma solução alternativa.

SpreadsheetApp.getActiveRange () É A CÉLULA que contém a fórmula. Isso é tudo que eu realmente precisava saber. O número da linha. A função a seguir pega um número de coluna NUMERIC e subtrai a ocorrência anterior nessa coluna.

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
}

Mas então eu descobri que isso é realmente muito lento. Atrasos de um e dois segundos enquanto exibe "Pensando ..." Então, estou de volta à fórmula da planilha maciçamente ilegível e impossível de manter.

Mark C
fonte
0

Em vez de fornecer o objeto "Intervalo", os desenvolvedores do Google transmitem praticamente um tipo de dados aleatório. Então, desenvolvi a macro a seguir para imprimir o valor da entrada.

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;
}
Nome real
fonte
0

Há minha compilação de respostas anteriores

**
 *
 * @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;
  },[]);
}

Ele pega a fórmula atual e verifica se é um intervalo.

=GFR(A1:A5;1;C1:C2;D1&D2) retorna

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

Para a composição completa, o TC pode chamar algo assim

var range = as.getRange(a1Notation);
var column = range.getColumn();
contributorpw
fonte
-1

Crie a função e passe o intervalo como argumento:

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;
}

Uso na planilha:

=fn(A1:A10)

Ele mostrará o valor como sum(A1:A10).

นาย เอกชัย บุญ ทิ สา
fonte