¿Cómo pasar un rango a una función personalizada en las hojas de cálculo de Google?

44

Quiero crear una función que tome un rango ... algo como:

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

Una celda lo referenciaría usando:

=myFunction(A1:A4)

El problema es que cuando trato de hacer esto, el parámetro parece que solo está pasando los valores a la función. Por lo tanto, no puedo usar ninguno de los métodos Range como getColumn(). Cuando intento hacerlo, aparece el siguiente error:

error: TypeError: no se puede encontrar la función getColumn en el objeto 1,2,3.

¿Cómo puedo enviar un rango real en lugar de solo los valores a una de mis funciones personalizadas?

Sensato
fuente

Respuestas:

22

Así que he buscado mucho para encontrar una buena respuesta a esto y esto es lo que he encontrado:

  1. un parámetro de rango no modificado pasa los valores de las celdas en el rango, no el rango en sí mismo (como explicó Gergely), por ejemplo: cuando haces esto=myFunction(a1:a2)

  2. Para usar un rango en su función, primero debe pasar el rango como una cadena (ej . =myFunction("a1:a2"):), luego convertirlo en un rango con el siguiente código dentro de la función:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Finalmente, si desea los beneficios de pasar un rango (como copiar inteligentemente las referencias de rango a otras celdas) Y desea pasarlo como una cadena, debe usar una función predeterminada que acepte un rango como parámetro y genere un cadena que puedes usar. Detallo ambas formas que he encontrado a continuación, pero prefiero la segunda.

    Para todas las hojas de cálculo de google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Para las nuevas hojas de cálculo de Google: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

Nathan Hanna
fuente
¿Puede explicar qué significa "copiar inteligentemente referencias de rango a otras celdas", por favor?
Emerson Farrugia
Salvador de la vida. Funciona, gracias.
Jithin Pavithran
@EmersonFarrugia Me refiero a la capacidad de las hojas de Google de actualizar automáticamente las referencias de celda relativas cuando copia funciones de una celda a otra
Nathan Hanna
9

rangese trata como la matriz 2d de javascript. Puede obtener el número de filas con range.lengthy el número de columnas con range[0].length. Si desea obtener el valor de la fila ry la columna de cuso: range[r][c].

Lipis
fuente
Estoy tratando de averiguar cuál es la primera columna del rango. Por ejemplo, en el rango C1: F1, quiero saber que el rango comienza en la columna C.
Senseful
@Senseful Si usará su función en una celda como:, =myFunction(C1:F1)entonces dentro de la función range[0][0]devolverá el valor de C1, range[0][1]devolverá el valor de D1, range[0][2]devolverá el valor de E1, etc. ¿Está claro?
Lipis
Creo que no me estoy explicando claramente ... mira tu respuesta a esta pregunta . Me recomendó que usara =weightedAverage(B3:D3,$B$2:$D$2), me gustaría hacer que la función sea más a prueba de errores al no tener que enviar el segundo argumento (es decir, quiero llamarlo como =weightedAverage(B3:D3)), y luego hacer que el código sepa automáticamente que el rango comienza en B y termina en D, por lo que obtiene los valores correspondientes de la segunda fila. Nota: el valor "2" se puede codificar, pero "B" no se debe codificar.
Sentido
1
@Senseful En general, estoy en contra de las cosas codificadas, y creo que es más claro si el promedio ponderado tomaría dos parámetros. Entonces, si está a punto de codificar, hay muchas otras cosas que podría hacer. No puedo encontrar en este momento cómo podemos sacar Bde un hecho range. Te sugiero que revises la guía de inicio ( goo.gl/hm0xT ), si aún no lo has hecho, para tener una idea de cómo puedes jugar con rangos y celdas.
Lipis
6

Al pasar un Rangea una función de hoja de cálculo de Google, el marco se ejecuta paramRange.getValues()implícitamente y su función recibe los valores en el Rango como una matriz bidimensional de cadenas, números u objetos (como Date). El Rangeobjeto no se pasa a su función de hoja de cálculo personalizada.

La TYPEOF()siguiente función le indicará qué tipo de datos recibe como parámetro. La formula

=TYPEOF(A1:A4)

llamará al script así:

función CalculateCell () {
  var resultCell = SpreadsheetApp.getActiveSheet (). getActiveCell ();
  var paramRange = SpreadsheetApp.getActiveSheet (). getRange ('A1: A4');
  var paramValues ​​= paramRange.getValues ​​();

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
función TYPEOF (valor) {
  if (tipo de valor! == 'objeto')
    tipo de valor de retorno;

  detalles de var = '';
  if (valor de instancia de matriz) {
    detalles + = '[' + valor.length + ']';
    if (valor [0] instancia de matriz)
      detalles + = '[' + valor [0] .length + ']';
  }

  var className = value.constructor.name;
  devolver className + detalles;
}
Gregorio
fuente
3

Como alternativa, inspirado en el comentario de @Lipis, puede llamar a su función con las coordenadas de fila / columna como parámetros adicionales:

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

De esta forma, la fórmula se puede copiar (o arrastrar) fácilmente a otras celdas, y las referencias de celda / rango se ajustarán automáticamente.

Su función de Script debería actualizarse como tal:

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

Tenga en cuenta que la getRangefunción toma startRow, startColumny luego el número de filas y el número de columnas - no termina fila y final de la columna. Así, la aritmética.

Vidar S. Ramdal
fuente
Esa es la respuesta exacta que espero. Fila y columna.
Jianwu Chen
3

Esto se puede hacer . Se puede obtener una referencia al rango aprobado al analizar la fórmula en la celda activa, que es la celda que contiene la fórmula. Esto supone que la función personalizada se usa por sí sola y no como parte de una expresión más compleja: por ejemplo =myfunction(A1:C3), no =sqrt(4+myfunction(A1:C3)).

Esta función devuelve el índice de la primera columna del rango pasado.

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

fuente
Creo que esta publicación también responde la siguiente pregunta sobre Stack Overflow : pasar referencias de celda a funciones de hoja de cálculo
Rubén
2

He extendido la excelente idea en la respuesta del usuario 79865 , para que funcione para más casos y con múltiples argumentos pasados ​​a la función personalizada.

Para usarlo, copie el código a continuación y luego, en su función personalizada, llame GetParamRanges()así, pasándole el nombre de su función:

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

Aquí hay un ejemplo para devolver el color de una celda:

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

Aquí está el código y alguna explicación más:

/**
* 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
fuente
1
no está nada mal, pero por favor declare que esto supone que una función de custion solo se usa una vez en una fórmula. Fi pude usar 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, por lo que entiendo, no funcionará de esta manera. Si lo resolvemos, sería capaz de manejar esto, sería genial.
Haemse
1

Distingo dos funciones personalizadas diferentes en la hoja de cálculo de Google, a través de Google Apps Script:

  1. Uno que requiere una API; SpreadsheetApp, ( ejemplo )
  2. Uno que no hace ninguna llamada, ( ejemplo )

La primera función es capaz de hacer casi cualquier cosa. Además de llamar al servicio de hoja de cálculo, puede recurrir a GmailApp o cualquier servicio puesto a disposición por Google. Las llamadas a la API ralentizarán el proceso. Se puede pasar un rango o recuperarlo a través de la función para acceder a todos los métodos disponibles.

La segunda función se limita únicamente a la "Hoja de cálculo". Aquí se puede utilizar JavaScript para volver a trabajar los datos. Estas funciones son normalmente muy rápidas. Un rango pasado no es más que una matriz 2D que contiene valores.


En su pregunta, comienza llamando al .getColumn()método. Esto indica claramente que necesita una función personalizada de tipo 1, como se describió anteriormente.

Consulte la siguiente respuesta sobre cómo configurar la hoja de cálculo y la hoja para crear una función personalizada.

Jacob Jan Tuinstra
fuente
1

Estuve trabajando en esto hace unos meses y se me ocurrió un error muy simple: crear una nueva hoja con el nombre de cada celda como contenido: la celda A1 podría verse así:

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

Nombre de la hoja "Ref". Luego, cuando necesita una referencia a una celda como una cadena en lugar del contenido, usa:

= some_new_function('Ref'!C45)

Por supuesto, deberá verificar si la función pasa una cadena (una celda) o una matriz 1D o 2D. Si obtiene una matriz, tendrá todas las direcciones de las celdas como cadenas, pero desde la primera celda y el ancho y la altura, puede averiguar lo que necesita.

HardScale
fuente
0

He estado trabajando en esto toda la mañana y viendo evidencia de lo que discuten las no respuestas anteriores. Senseful y yo queremos la DIRECCIÓN de la celda pasada, no el valor. Y la respuesta es muy fácil. No se puede hacer.

Encontré algunas soluciones que dependen de averiguar qué celda contiene la fórmula. Es difícil decir si esto hubiera ayudado a Senseful arriba. Entonces, ¿qué estaba haciendo?

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

La columna H es Sophia (victorias - victorias previas) / (pérdidas - pérdidas previas)

(7 - 4) / (4 - 2) = 1.5

Pero no sabemos en qué fila apareció anteriormente Sophia.
Todo esto se puede hacer usando BUSCARV si codifica A como columna de nombre. Después de VLOOKUP, estaba obteniendo algunos #NA (nombre no encontrado) y # DIV0 (denominador cero) y lo envolví con = IF (IF (...)) para mostrar más texto aceptable en estas condiciones. Ahora tenía una expresión monstruosamente grande que era difícil de manejar e insostenible. Entonces quería expansión macro (no existe) o funciones personalizadas.

Pero cuando hice un SubtractPrevValue (celda) auxiliar, estaba recibiendo "7" en lugar de B5. No hay una forma integrada de obtener objetos de celda o rango de los argumentos pasados.
Si hago que el usuario ingrese manualmente el nombre de la celda entre comillas dobles, entonces puedo hacerlo ... SubtractPrevValue ("B5"). Pero eso realmente copia / pega los isquiotibiales y las características relativas de la celda.

Pero luego encontré una solución.

SpreadsheetApp.getActiveRange () ES LA CELDA que contiene la fórmula. Eso es todo lo que realmente necesitaba saber. El número de fila. La siguiente función toma un número de columna NUMÉRICO y resta la ocurrencia anterior en esa columna.

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
}

Pero luego descubrí que esto es REALMENTE lento. Retrasos de uno y dos segundos mientras muestra "Pensando ..." Así que vuelvo a la fórmula de la hoja de trabajo masivamente ilegible e imposible de mantener.

Mark C
fuente
0

En lugar de proporcionar el objeto "Rango", los desarrolladores de Google pasan casi un tipo de datos aleatorio. Entonces, desarrollé la siguiente macro para imprimir el valor de la 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;
}
Nombre real
fuente
0

Ahí está mi compilación de respuestas 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;
  },[]);
}

Toma la fórmula actual y verifica si es un Rango.

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

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

Para la composición completa, el TC puede llamar a algo como esto

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

Crear función y pasar rango 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 en hoja de cálculo:

=fn(A1:A10)

Mostrará valor como sum(A1:A10).

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