Come passare un intervallo in una funzione personalizzata in Fogli di lavoro Google?


44

Voglio creare una funzione che includa un intervallo ... qualcosa del tipo:

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

Una cella lo farebbe riferimento usando:

=myFunction(A1:A4)

Il problema è che quando provo a fare questo, il parametro sembra passare solo i valori alla funzione. Pertanto, non posso usare nessuno dei metodi Range come getColumn(). Quando provo a farlo, viene visualizzato il seguente errore:

errore: TypeError: impossibile trovare la funzione getColumn nell'oggetto 1,2,3.

Come posso inviare un intervallo effettivo anziché solo i valori a una delle mie funzioni personalizzate?

Risposte:


23

Quindi ho cercato a lungo e duramente una buona risposta a questo ed ecco quello che ho trovato:

  1. un parametro di intervallo non modificato passa nei valori delle celle nell'intervallo, non nell'intervallo stesso (come spiegato Gergely) es: quando lo fai=myFunction(a1:a2)

  2. per usare un intervallo nella tua funzione devi prima passare l'intervallo come stringa (es:) =myFunction("a1:a2"), quindi trasformarlo in un intervallo con il seguente codice all'interno della funzione:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Infine, se desideri i vantaggi del passaggio in un intervallo (come la copia intelligente dei riferimenti di intervallo ad altre celle) E vuoi passarlo come stringa, devi utilizzare una funzione predefinita che accetta un intervallo come parametro e genera un stringa che puoi usare. Descrivo in dettaglio entrambi i modi che ho trovato di seguito, ma preferisco il 2 °.

    Per tutti i fogli di calcolo di Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Per i nuovi Fogli Google: =myFunction(CELL("address",A1)&":"&CELL("address",A2));


Puoi approfondire cosa significa "copiare in modo intelligente i riferimenti di intervallo ad altre celle", per favore?
Emerson Farrugia,

1
Salvavita. Funziona, grazie.
Jithin Pavithran,

@EmersonFarrugia Mi riferisco alla possibilità per i fogli di Google di aggiornare automaticamente i riferimenti di cella relativi quando copi le funzioni da una cella all'altra
Nathan Hanna,

9

rangeviene trattato come un array 2d di javascript. È possibile ottenere il numero di righe con range.lengthe il numero di colonne con range[0].length. Se si desidera ottenere il valore della riga re la colonna cuso: range[r][c].


Sto cercando di scoprire qual è la prima colonna dell'intervallo. Ad esempio, nell'intervallo C1: F1, voglio sapere che l'intervallo inizia nella colonna C.
Senseful

@Senseful Se utilizzerai la tua funzione in una cella come:, =myFunction(C1:F1)allora all'interno della funzione range[0][0]restituirà il valore di C1, range[0][1]restituirà il valore di D1, range[0][2]restituirà il valore di E1, ecc. È chiaro?
Lipis,

Penso di non spiegarmi chiaramente ... guarda la tua risposta a questa domanda . Mi hai raccomandato di utilizzare =weightedAverage(B3:D3,$B$2:$D$2), vorrei rendere la funzione più a prova di errore non dovendo inviare il 2 ° argomento (cioè voglio chiamarlo come =weightedAverage(B3:D3)), quindi fare in modo che il codice sappia automaticamente che l'intervallo inizia da B e termina alle D, quindi ottiene i valori corrispondenti dalla seconda riga. Nota: il valore "2" può essere codificato, ma "B" non deve essere codificato.
Sensuale

1
@Senseful In generale sono contrario alle cose hardcoded e penso che sia più chiaro se la media ponderata richiederebbe due parametri. Quindi, se stai per scrivere un codice, ci sono molte altre cose che potresti fare. Non riesco a trovare ora come possiamo ottenere il Bmassimo da un dato range. Ti suggerirei di consultare la guida introduttiva ( goo.gl/hm0xT ), se non l'hai già fatto, per avere un'idea di come puoi giocare con intervalli e celle.
Lipis,

6

Quando si passa Rangea una funzione di foglio di calcolo di Google, il framework viene eseguito in modo paramRange.getValues()implicito e la funzione riceve i valori nell'intervallo come una matrice bidimensionale di stringhe, numeri o oggetti (come Date). L' Rangeoggetto non viene passato alla funzione del foglio di calcolo personalizzato.

La TYPEOF()funzione seguente ti dirà che tipo di dati ricevi come parametro. La formula

=TYPEOF(A1:A4)

chiamerà lo script in questo modo:

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

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (ResultValue);
}
funzione TYPEOF (valore) {
  if (typeof value! == 'oggetto')
    restituisce typeof value;

  var details = '';
  if (value instanceof Array) {
    dettagli + = '[' + value.length + ']';
    if (valore [0] instanceof Array)
      dettagli + = '[' + value [0] .length + ']';
  }

  var className = value.constructor.name;
  return className + dettagli;
}

3

In alternativa, ispirato al commento di @Lipis, puoi chiamare la tua funzione con le coordinate riga / colonna come parametri aggiuntivi:

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

In questo modulo, la formula può essere facilmente copiata (o trascinata) in altre celle e i riferimenti di cella / intervallo verranno regolati automaticamente.

La tua funzione Script dovrebbe essere aggiornata come tale:

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

Si noti che la getRangefunzione prende startRow, startColumne quindi il numero di righe, e il numero di colonne - non esaurisce riga e fine colonna. Quindi, l'aritmetica.


Questa è la risposta esatta che mi aspetto. FILA e COLONNA.
Jianwu Chen il

3

Questo può essere fatto . Si può ottenere un riferimento all'intervallo passato analizzando la formula nella cella attiva, che è la cella contenente la formula. Questo presuppone che la funzione personalizzata sia utilizzata da sola, e non come parte di un'espressione più complessa: ad esempio =myfunction(A1:C3), no =sqrt(4+myfunction(A1:C3)).

Questa funzione restituisce il primo indice di colonna dell'intervallo passato.

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

Penso che questo post risponda anche alla seguente domanda su Stack Overflow : passare i riferimenti di cella alle funzioni del foglio di calcolo
Rubén

2

Ho esteso l'idea eccellente nella risposta di user79865 , per farlo funzionare per più casi e con più argomenti passati alla funzione personalizzata.

Per usarlo, copia il codice qui sotto, quindi nella tua chiamata di funzione personalizzata in GetParamRanges()questo modo, passando il nome della tua funzione:

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

Ecco un esempio per restituire il colore di una cella:

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

Ecco il codice e qualche altra spiegazione:

/**
* 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
non è affatto male, ma ti preghiamo di affermare che ciò fa supporre che una funzione di custion sia usata una sola volta in una formula. Fi Devo usare i forum come: = 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 Che da come ho capito, non funzionerà in questo modo. Se lo risolviamo, sarà in grado di gestirlo sarebbe fantastico.
Avere il

1

Distinguo due diverse funzioni personalizzate in Google Spreadsheet, tramite Google Apps Script:

  1. Uno che richiede un'API; SpreadsheetApp, ( esempio )
  2. Uno che non effettua alcuna chiamata, ( esempio )

La prima funzione è in grado di fare quasi tutto. Oltre a chiamare il servizio Spreadsheet, può chiamare GmailApp o qualsiasi servizio reso disponibile da Google. Le chiamate API rallenteranno il processo. Un intervallo può essere passato o recuperato attraverso la funzione per accedere a tutti i metodi disponibili.

La seconda funzione è limitata al "Foglio di calcolo". Qui è possibile utilizzare JavaScript per rielaborare i dati. Queste funzioni sono normalmente molto veloci. Un intervallo passato, non è altro che un array contenente valori 2D.


Nella tua domanda, inizi chiamando il .getColumn()metodo. Ciò indica chiaramente che è necessaria una funzione personalizzata di tipo 1, come descritto sopra.

Vedere la seguente risposta su come impostare il foglio di calcolo e il foglio per creare una funzione personalizzata.


1

Ci stavo lavorando alcuni mesi fa e ho trovato un kludge molto semplice: creare un nuovo foglio con il nome di ogni cella come contenuto: La cella A1 potrebbe apparire come:

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

Denominare il foglio "Rif". Quindi, quando hai bisogno di un riferimento a una cella come stringa anziché al contenuto, usi:

= some_new_function('Ref'!C45)

Ovviamente, dovrai verificare se alla funzione viene passata una stringa (una cella) o un array 1D o 2D. Se ottieni una matrice, avrà tutti gli indirizzi di cella come stringhe, ma dalla prima cella e dalla larghezza e altezza, puoi capire di cosa hai bisogno.


0

Ci ho lavorato tutta la mattina e ho visto prove di ciò di cui discutono le mancate risposte di cui sopra. Senseful e entrambi vogliamo l'INDIRIZZO della cella passata, non il valore. E la risposta è molto semplice. Non si può fare.

Ho trovato alcune soluzioni alternative che si basano sul capire quale cella contiene la formula. È difficile dire se questo avrebbe aiutato Senseful sopra. Quindi cosa stavo facendo?

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 colonna H è Sophia (Vittorie - PrevWins) / (Perdite - PrevLosses)

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

Ma non sappiamo in quale riga sia apparso Sophia in precedenza.
Questo può essere fatto usando VLOOKUP se si codifica A come colonna del nome. Dopo VLOOKUP, stavo ottenendo un po 'di #NA (nome non trovato) e # DIV0 (denominatore zero) e lo avvolgevo con = IF (IF (...)) per mostrare un testo più appetibile in queste condizioni. Ora avevo un'espressione mostruosamente grande che era ingombrante e non mantenibile. Quindi volevo espansione macro (non esiste) o funzioni personalizzate.

Ma quando ho creato un aiutante SubtractPrevValue (cell), stava ricevendo "7" invece di B5. Non esiste un modo integrato per ottenere oggetti cella o intervallo dagli argomenti passati.
Se faccio inserire manualmente il nome della cella tra virgolette doppie, allora posso farlo ... SubtractPrevValue ("B5"). Ma questo davvero i muscoli posteriori della coscia copiano / incollano e le relative caratteristiche delle celle.

Ma poi ho trovato una soluzione alternativa.

SpreadsheetApp.getActiveRange () È LA CELLA che contiene la formula. Questo è tutto ciò che dovevo davvero sapere. Il numero di riga La seguente funzione prende un numero di colonna NUMERICO e sottrae la ricorrenza precedente in quella colonna.

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
}

Ma poi ho scoperto che questo è davvero VERAMENTE lento. Ritardi di uno e due secondi mentre mostra "Pensare ..." Quindi sono tornato alla formula del foglio di lavoro enormemente illeggibile e non mantenibile.


0

Invece di fornire l'oggetto "Range", gli sviluppatori di Google trasmettono praticamente un tipo di dati casuale. Quindi, ho sviluppato la seguente macro per stampare il valore dell'input.

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

C'è la mia raccolta di risposte precedenti

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

Prende la formula corrente e controlla se è un intervallo.

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

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

Per la composizione completa, il TC può chiamare qualcosa del genere

var range = as.getRange(a1Notation);
var column = range.getColumn();

-1

Crea funzione e passa intervallo come argomento:

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

Utilizzo nel foglio di calcolo:

=fn(A1:A10)

Mostrerà valore come sum(A1:A10).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.