Wie übergebe ich einen Bereich an eine benutzerdefinierte Funktion in Google Spreadsheets?

44

Ich möchte eine Funktion erstellen, die einen Bereich umfasst ... so etwas wie:

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

Eine Zelle würde darauf verweisen mit:

=myFunction(A1:A4)

Das Problem ist, dass der Parameter bei diesem Versuch nur die Werte an die Funktion weitergibt. Daher kann ich keine der Range-Methoden verwenden, wie z getColumn(). Wenn ich das versuche, wird der folgende Fehler angezeigt:

Fehler: TypeError: Die Funktion getColumn in Objekt 1,2,3 kann nicht gefunden werden.

Wie kann ich einen tatsächlichen Bereich anstatt nur die Werte an eine meiner benutzerdefinierten Funktionen senden?

Sinnvoll
quelle

Antworten:

22

Also habe ich lange und intensiv nach einer guten Antwort gesucht und hier ist, was ich gefunden habe:

  1. Ein unveränderter Bereichsparameter übergibt die Werte der Zellen im Bereich, nicht den Bereich selbst (wie allgemein erklärt), z. B .: wenn Sie dies tun=myFunction(a1:a2)

  2. Um einen Bereich in Ihrer Funktion zu verwenden, müssen Sie zuerst den Bereich als Zeichenfolge übergeben (z. B. =myFunction("a1:a2")) und ihn dann in einen Bereich mit dem folgenden Code in der Funktion umwandeln:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Wenn Sie die Vorteile einer Bereichsübergabe nutzen möchten (z. B. das intelligente Kopieren von Bereichsreferenzen in andere Zellen) UND diese als Zeichenfolge übergeben möchten, müssen Sie eine Standardfunktion verwenden, die einen Bereich als Parameter akzeptiert und a ausgibt Zeichenfolge, die Sie verwenden können. Ich beschreibe beide Möglichkeiten, die ich unten gefunden habe, aber ich bevorzuge die zweite.

    Für alle Google-Tabellen: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Für die neuen Google Sheets: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

Nathan Hanna
quelle
Können Sie näher erläutern, was es bedeutet, "Bereichsverweise auf andere Zellen intelligent zu kopieren"?
Emerson Farrugia
Lebensretter. Es funktioniert, danke.
Jithin Pavithran
@EmersonFarrugia Ich beziehe mich auf die Fähigkeit von Google Sheets, relative Zellreferenzen automatisch zu aktualisieren, wenn Sie Funktionen von einer Zelle in eine andere kopieren
Nathan Hanna,
9

rangewird als 2D-Array von Javascript behandelt. Mit können Sie die Anzahl der Zeilen range.lengthund mit die Anzahl der Spalten ermitteln range[0].length. Wenn Sie den Wert von Zeile zu erhalten rund Spalte cVerwendung: range[r][c].

Lipis
quelle
Ich versuche herauszufinden, was die erste Spalte im Bereich ist. Zum Beispiel möchte ich im Bereich C1: F1 wissen, dass der Bereich in Spalte C beginnt.
Sinnvoller
@Senseful Wenn Sie Ihre Funktion in einer Zelle wie: verwenden =myFunction(C1:F1), wird in der Funktion range[0][0]der Wert von C1, range[0][1]der Wert von D1, range[0][2]der Wert von E1usw. zurückgegeben. Ist das klar?
Lipis
Ich glaube, ich erkläre mich nicht klar ... sieh dir deine Antwort auf diese Frage an . Sie haben empfohlen, dass ich verwende =weightedAverage(B3:D3,$B$2:$D$2), ich möchte die Funktion fehlersicherer machen, indem ich das zweite Argument nicht senden muss (dh ich möchte es als aufrufen =weightedAverage(B3:D3)), und dann muss der Code automatisch wissen, dass der Bereich bei B beginnt und bei endet D, also bekommt es die entsprechenden Werte aus der 2. Reihe. Hinweis: Der Wert "2" kann fest codiert werden, "B" sollte jedoch nicht fest codiert werden.
Sinnvolle
1
@Senseful Im Allgemeinen bin ich gegen hardcodiertes Zeug und ich denke, es ist klarer, wenn der gewichtete Durchschnitt zwei Parameter benötigt. Wenn Sie sich mit hartem Code befassen, können Sie viele andere Dinge tun. Ich kann jetzt nicht herausfinden, wie wir das Baus einer gegebenen Situation herausholen können range. Ich empfehle Ihnen, die Kurzanleitung ( goo.gl/hm0xT ) zu lesen , um eine Vorstellung davon zu bekommen, wie Sie mit Bereichen und Zellen spielen können.
Lipis
6

Wenn Sie eine Rangean eine Google-Tabellenkalkulationsfunktion übergeben, wird das Framework paramRange.getValues()implizit ausgeführt und Ihre Funktion empfängt die Werte im Bereich als zweidimensionales Array von Zeichenfolgen, Zahlen oder Objekten (wie Date). Das RangeObjekt wird nicht an Ihre benutzerdefinierte Tabellenkalkulationsfunktion übergeben.

Die folgende TYPEOF()Funktion gibt Auskunft darüber, welche Art von Daten Sie als Parameter erhalten. Die Formel

=TYPEOF(A1:A4)

Ruft das Skript folgendermaßen auf:

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

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
Funktion TYPEOF (Wert) {
  if (Werttyp! == 'Objekt')
    Rückgabewert;

  var details = '';
  if (Wert Instanz des Arrays) {
    details + = '[' + value.length + ']';
    if (Wert [0] Instanz des Arrays)
      details + = '[' + value [0] .length + ']';
  }

  var className = value.constructor.name;
  return className + details;
}
Gregorius
quelle
3

Alternativ können Sie, inspiriert durch den Kommentar von @Lipis, Ihre Funktion mit den Zeilen- / Spaltenkoordinaten als zusätzlichen Parametern aufrufen:

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

In diesem Formular kann die Formel einfach in andere Zellen kopiert (oder gezogen) werden, und die Zell- / Bereichsreferenzen werden automatisch angepasst.

Ihre Skriptfunktion müsste als solche aktualisiert werden:

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

Man beachte , daß die getRangeFunktion übernimmt startRow, startColumnund dann Anzahl der Zeilen und Anzahl der Spalten - nicht beenden Reihe und Ende Spalte. Also die Arithmetik.

Vidar S. Ramdal
quelle
Das ist die genaue Antwort, die ich erwarte. Zeile und Spalte.
Jianwu Chen
3

Das kann gemacht werden . Sie können einen Verweis auf den übergebenen Bereich erhalten, indem Sie die Formel in der aktiven Zelle analysieren, die die Formel enthält. Dies setzt voraus, dass die benutzerdefinierte Funktion für sich allein und nicht als Teil eines komplexeren Ausdrucks verwendet wird: z . B. =myfunction(A1:C3)nicht =sqrt(4+myfunction(A1:C3)).

Diese Funktion gibt den ersten Spaltenindex des übergebenen Bereichs zurück.

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

quelle
Ich denke, dass dieser Beitrag auch die folgende Frage zum Stapelüberlauf beantwortet : Übergeben von Zellreferenzen an Tabellenkalkulationsfunktionen
Rubén
2

Ich habe die ausgezeichnete Idee in der Antwort von user79865 erweitert , damit sie für mehr Fälle funktioniert und mehrere Argumente an die benutzerdefinierte Funktion übergeben werden.

Um es zu verwenden, kopieren Sie den folgenden Code und übergeben Sie ihm in Ihrem benutzerdefinierten Funktionsaufruf GetParamRanges()wie folgt Ihren Funktionsnamen:

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

Hier ist ein Beispiel, um die Farbe einer Zelle zurückzugeben:

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

Hier ist der Code und einige weitere Erklärungen:

/**
* 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
quelle
1
Gar nicht schlecht, aber bitte geben Sie an, dass dies die Annahme macht, dass eine Custion-Funktion in einer Formel nur einmal verwendet wird. Ich muss Foren verwenden wie: = 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 Was nach meinem Verständnis nicht so funktioniert. Wenn wir es lösen könnten, wäre es großartig.
haemse
1

Ich unterscheide zwei verschiedene benutzerdefinierte Funktionen in Google Spreadsheet über Google Apps Script:

  1. Eine, die eine API erfordert; SpreadsheetApp, ( Beispiel )
  2. Eine, die keine Anrufe tätigt ( Beispiel )

Die erste Funktion kann fast alles. Abgesehen vom Aufrufen des Tabellenkalkulationsdienstes kann er auf die Google Mail-App oder einen von Google bereitgestellten Dienst zugreifen. API-Aufrufe verlangsamen den Prozess. Ein Bereich kann weitergegeben oder über die Funktion abgerufen werden, um auf alle verfügbaren Methoden zuzugreifen.

Die zweite Funktion ist nur auf die "Tabellenkalkulation" beschränkt. Hier kann man mit JavaScript die Daten überarbeiten. Diese Funktionen sind normalerweise sehr schnell. Ein übergebener Bereich ist nichts anderes als ein 2D-Array, das Werte enthält.


In Ihrer Frage rufen Sie zunächst die .getColumn()Methode auf. Dies weist eindeutig darauf hin, dass Sie eine benutzerdefinierte Funktion vom Typ 1 benötigen, wie oben beschrieben.

In der folgenden Antwort erfahren Sie, wie Sie die Tabellenkalkulation und das Tabellenblatt festlegen, um eine benutzerdefinierte Funktion zu erstellen.

Jacob Jan Tuinstra
quelle
1

Ich habe vor ein paar Monaten daran gearbeitet und mir einen sehr einfachen Kludge ausgedacht: Erstelle ein neues Blatt mit dem Namen jeder Zelle als Inhalt: Zelle A1 könnte so aussehen:

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

Nennen Sie das Blatt "Ref". Wenn Sie anstelle des Inhalts einen Verweis auf eine Zelle als Zeichenfolge benötigen, verwenden Sie Folgendes:

= some_new_function('Ref'!C45)

Natürlich müssen Sie prüfen, ob der Funktion eine Zeichenfolge (eine Zelle) oder ein 1D- oder 2D-Array übergeben wird. Wenn Sie ein Array erhalten, enthält es alle Zelladressen als Zeichenfolgen, aber anhand der ersten Zelle sowie der Breite und Höhe können Sie herausfinden, was Sie benötigen.

HardScale
quelle
0

Ich habe den ganzen Morgen daran gearbeitet und Beweise dafür gesehen, worüber die obigen Nicht-Antworten sprechen. Senseful und ich wollen beide die ADRESSE der übergebenen Zelle, nicht den Wert. Und die Antwort ist sehr einfach. Das geht nicht.

Ich habe einige Problemumgehungen gefunden, die darauf beruhen, herauszufinden, in welcher Zelle die Formel enthalten ist. Es ist schwer zu sagen, ob dies Senseful weiter oben geholfen hätte. Also, was habe ich gemacht?

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

Spalte H ist Sophias (Siege - PrevWins) / (Verluste - PrevLosses)

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

Wir wissen jedoch nicht, in welcher Zeile Sophia zuvor aufgetreten ist.
Dies kann alles mit VLOOKUP geschehen, wenn Sie A als Namensspalte fest codieren. Nach VLOOKUP wurden #NA (Name nicht gefunden) und # DIV0 (Nenner Null) abgerufen und mit = IF (IF (...)) umhüllt, um unter diesen Bedingungen einen ansehnlicheren Text anzuzeigen. Jetzt hatte ich einen ungeheuer großen Gesichtsausdruck, der unhandlich und unhaltbar war. Also wollte ich eine Makro-Erweiterung (existiert nicht) oder benutzerdefinierte Funktionen.

Aber als ich einen Helfer SubtractPrevValue (Zelle) machte, erhielt er "7" anstelle von B5. Es gibt keine integrierte Möglichkeit, Zellen- oder Bereichsobjekte aus den übergebenen Argumenten abzurufen.
Wenn der Benutzer den Zellennamen in doppelten Anführungszeichen von Hand eingibt, kann ich dies tun ... SubtractPrevValue ("B5"). Aber das erschwert das Kopieren / Einfügen und die relativen Zellmerkmale.

Aber dann habe ich einen Workaround gefunden.

SpreadsheetApp.getActiveRange () IST DIE ZELLE, die die Formel enthält. Das ist alles, was ich wirklich wissen musste. Die Zeilennummer. Die folgende Funktion verwendet eine NUMERIC-Spaltennummer und subtrahiert das vorherige Vorkommen in dieser Spalte.

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
}

Aber dann entdeckte ich, dass dies wirklich sehr langsam ist. Eine und zwei Sekunden Verzögerung, während "Denken ..." angezeigt wird. Ich komme also zurück zu der massiv unleserlichen, nicht zu wartenden Arbeitsblattformel.

Mark C
quelle
0

Anstatt das "Range" -Objekt bereitzustellen, übergeben die Google-Entwickler so ziemlich einen zufälligen Datentyp. Deshalb habe ich das folgende Makro entwickelt, um den Wert der Eingabe auszudrucken.

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;
}
Echter Name
quelle
0

Es gibt meine Zusammenstellung früherer Antworten

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

Es wird die aktuelle Formel verwendet und geprüft, ob es sich um einen Bereich handelt.

=GFR(A1:A5;1;C1:C2;D1&D2) kehrt zurück

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

Für die vollständige Komposition kann der TC so etwas nennen

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

Erstellen Sie eine Funktion und übergeben Sie den Bereich als Argument:

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

Verwendung in der Tabelle:

=fn(A1:A10)

Es wird Wert als anzeigen sum(A1:A10).

นาย นาย บุญ ทิ ทิ
quelle