Bagaimana cara melewatkan rentang ke fungsi kustom di Google Spreadsheets?

44

Saya ingin membuat fungsi yang mencakup ... sesuatu seperti:

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

Sel akan merujuknya menggunakan:

=myFunction(A1:A4)

Masalahnya adalah ketika saya mencoba melakukan ini, parameternya sepertinya hanya meneruskan nilai ke fungsi. Jadi, saya tidak bisa menggunakan salah satu metode Range seperti getColumn(). Ketika saya mencoba melakukannya, itu memberikan kesalahan berikut:

kesalahan: TypeError: Tidak dapat menemukan fungsi getColumn di objek 1,2,3.

Bagaimana saya bisa mengirim rentang yang sebenarnya daripada hanya nilai-nilai ke salah satu fungsi kustom saya?

Masuk akal
sumber

Jawaban:

22

Jadi saya sudah mencari panjang dan keras untuk jawaban yang bagus untuk ini dan inilah yang saya temukan:

  1. parameter rentang yang tidak dimodifikasi melewati nilai sel dalam rentang tersebut, bukan rentang itu sendiri (seperti yang dijelaskan Gergely) misalnya: saat Anda melakukan ini=myFunction(a1:a2)

  2. untuk menggunakan rentang dalam fungsi Anda, Anda harus terlebih dahulu melewati rentang sebagai string (mis .:) =myFunction("a1:a2"), lalu mengubahnya menjadi rentang dengan kode berikut di dalam fungsi:

    Function myFunction(pRange){
      var sheet = SpreadsheetApp.getActiveSpreadsheet();
      var range = sheet.getRange(pRange);
    }
    
  3. Akhirnya, jika Anda ingin manfaat melintas dalam rentang (seperti menyalin referensi rentang cerdas ke sel lain) DAN Anda ingin meneruskannya sebagai string, Anda harus menggunakan fungsi default yang menerima rentang sebagai parameter dan menghasilkan string yang bisa Anda gunakan. Saya merinci kedua cara yang saya temukan di bawah, tetapi saya lebih suka yang ke-2.

    Untuk semua spreadsheet Google: =myFunction(ADDRESS(row(A1),COLUMN(A1),4)&":"&ADDRESS(row(A2),COLUMN(A2),4));

    Untuk Google Sheets baru: =myFunction(CELL("address",A1)&":"&CELL("address",A2));

Nathan Hanna
sumber
Bisakah Anda menguraikan apa yang dimaksud dengan "secara cerdas menyalin referensi rentang ke sel lain"?
Emerson Farrugia
Penyelamat. Berhasil, terima kasih.
Jithin Pavithran
@EmersonFarrugia Saya merujuk pada kemampuan lembar google untuk memperbarui referensi sel relatif secara otomatis saat Anda menyalin fungsi dari satu sel ke sel lainnya
Nathan Hanna
9

rangediperlakukan sebagai array 2d javascript. Anda bisa mendapatkan jumlah baris range.lengthdan jumlah kolom range[0].length. Jika Anda ingin mendapatkan nilai dari baris rdan kolom cdigunakan: range[r][c].

Lipis
sumber
Saya mencoba mencari tahu apa kolom pertama dalam rentang tersebut. Misalnya, dalam rentang C1: F1, saya ingin tahu bahwa rentang dimulai pada kolom C.
Senseful
@Senseful Jika Anda akan menggunakan fungsi Anda dalam sel seperti:, =myFunction(C1:F1)maka di dalam fungsi range[0][0]akan mengembalikan nilai C1, range[0][1]akan mengembalikan nilai D1, range[0][2]akan mengembalikan nilai E1, dll. Apakah itu jelas?
Lipis
Saya pikir saya tidak menjelaskan diri saya dengan jelas ... lihat jawaban Anda untuk pertanyaan ini . Anda merekomendasikan yang saya gunakan =weightedAverage(B3:D3,$B$2:$D$2), saya ingin membuat fungsi lebih banyak bukti kesalahan dengan tidak harus mengirim argumen ke-2 (yaitu saya ingin menyebutnya sebagai =weightedAverage(B3:D3)), dan kemudian minta kode secara otomatis tahu bahwa rentang dimulai pada B dan berakhir pada D, sehingga mendapat nilai yang sesuai dari baris ke-2. Catatan: nilai "2" dapat hardcoded, tetapi "B" tidak boleh hardcoded.
Senseful
1
@Senseful Secara umum saya menentang hal-hal yang dikodekan, dan saya pikir itu lebih jelas jika rata-rata tertimbang akan mengambil dua params. Jadi jika Anda hendak kode keras maka ada banyak hal lain yang bisa Anda lakukan. Saya tidak dapat menemukan sekarang bagaimana kita bisa mendapatkan yang Bdiberikan range. Saya menyarankan Anda untuk membaca panduan memulai ( goo.gl/hm0xT ), jika Anda belum melakukannya, untuk mendapatkan ide tentang bagaimana Anda bisa bermain dengan rentang dan sel.
Lipis
6

Saat meneruskan Rangeke fungsi Google spreadsheet, kerangka kerja dijalankan paramRange.getValues()secara implisit dan fungsi Anda menerima nilai dalam Rentang sebagai array 2 dimensi dari string, angka, atau objek (seperti Date). The Rangeobjek tidak dilewatkan ke fungsi spreadsheet kustom Anda.

The TYPEOF()fungsi di bawah ini akan memberitahu Anda apa jenis data yang Anda terima sebagai parameter. Rumus

=TYPEOF(A1:A4)

akan memanggil skrip seperti ini:

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

  var resultValue = TYPEOF (paramValues);

  resultCell.setValue (resultValue);
}
fungsi TYPEOF (nilai) {
  if (typeof value! == 'object')
    mengembalikan typeof nilai;

  var details = '';
  if (nilai instance dari Array) {
    detail + = '[' + value.length + ']';
    if (value [0] instance of Array)
      detail + = '[' + nilai [0] .panjang + ']';
  }

  var className = value.constructor.name;
  mengembalikan detail className +;
}
Gregorius
sumber
3

Sebagai alternatif, terinspirasi oleh komentar dari @Lipis, Anda dapat memanggil fungsi Anda dengan koordinat baris / kolom sebagai parameter tambahan:

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

Dalam formulir ini, rumus dapat dengan mudah disalin (atau diseret) ke sel lain, dan referensi sel / rentang akan disesuaikan secara otomatis.

Fungsi Script Anda perlu diperbarui seperti itu:

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

Perhatikan bahwa getRangefungsi mengambil startRow, startColumndan kemudian jumlah baris, dan jumlah kolom - tidak berakhir baris dan akhir kolom. Jadi, aritmatika.

Vidar S. Ramdal
sumber
Itulah jawaban tepat yang saya harapkan. ROW dan COLUMN.
Jianwu Chen
3

Ini bisa dilakukan . Orang bisa mendapatkan referensi ke rentang yang dilewati dengan menguraikan rumus dalam sel aktif, yang merupakan sel yang mengandung rumus. Ini membuat asumsi bahwa fungsi kustom digunakan sendiri, dan bukan sebagai bagian dari ekspresi yang lebih kompleks: misalnya =myfunction(A1:C3), tidak =sqrt(4+myfunction(A1:C3)).

Fungsi ini mengembalikan indeks kolom pertama dari rentang yang diteruskan.

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

sumber
Saya pikir posting ini juga menjawab pertanyaan berikut tentang Stack Overflow : meneruskan referensi sel ke fungsi spreadsheet
Rubén
2

Saya telah memperluas ide bagus dalam jawaban user79865 , untuk membuatnya berfungsi untuk lebih banyak kasus dan dengan beberapa argumen diteruskan ke fungsi kustom.

Untuk menggunakannya, salin kode di bawah ini, dan kemudian dalam panggilan fungsi khusus Anda GetParamRanges()seperti ini, berikan nama fungsi Anda:

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

Berikut ini contoh untuk mengembalikan warna sel:

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

Berikut kodenya dan beberapa penjelasan lainnya:

/**
* 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
sumber
1
tidak buruk sama sekali, tetapi tolong sebutkan bahwa ini membuat asumsi fungsi custion hanya digunakan sekali dalam formula. Fi Saya harus menggunakan forumals seperti: = CountCcolor (B5: B38, $ A40) / 2 + CountCcolor (B5: B38, $ A41) / 2 + CountCcolor (B5: B38, $ A42) / 2 + CountCcolor (B5: B38: B38: B38 , $ A43) / 2 + CountCcolor (B5: B38, $ A44) / 2 Dari mana saya memahaminya, tidak akan bekerja seperti ini. Jika kita menyelesaikannya, maka Beeng bisa menangani ini, itu akan bagus.
haemse
1

Saya membedakan dua fungsi khusus di Google Spreadsheet, melalui Google Apps Script:

  1. Yang memanggil API; SpreadsheetApp, ( contoh )
  2. Yang tidak melakukan panggilan, ( contoh )

Fungsi pertama mampu melakukan hampir semua hal. Selain memanggil layanan Spreadsheet, ia dapat memanggil GmailApp atau layanan apa pun yang disediakan oleh Google. Panggilan API akan memperlambat proses. Rentang dapat diteruskan, atau diambil melalui fungsi, untuk mengakses semua metode yang tersedia.

Fungsi kedua hanya terbatas pada "Spreadsheet". Di sini orang dapat menggunakan JavaScript untuk mengolah data. Fungsi-fungsi ini biasanya sangat cepat. Rentang yang dilewati, tidak lebih dari nilai array 2D.


Dalam pertanyaan Anda, Anda mulai dengan memanggil .getColumn()metode. Ini dengan jelas menunjukkan bahwa Anda memerlukan fungsi kustom tipe 1, seperti dijelaskan di atas.

Lihat jawaban berikut tentang cara mengatur spreadsheet dan sheet, untuk membuat fungsi kustom.

Jacob Jan Tuinstra
sumber
1

Saya sedang mengerjakan ini beberapa bulan yang lalu dan muncul dengan kludge yang sangat sederhana: buat lembar baru dengan nama setiap sel sebagai isinya: Sel A1 bisa terlihat seperti:

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

Beri nama sheet "Ref". Lalu ketika Anda membutuhkan referensi ke sel sebagai string alih-alih isinya, Anda menggunakan:

= some_new_function('Ref'!C45)

Tentu saja, Anda harus memeriksa apakah fungsi tersebut dilewati string (satu sel) atau 1D atau 2D Array. Jika Anda mendapatkan sebuah array, ia akan memiliki semua alamat sel sebagai string, tetapi dari sel pertama dan lebar dan tinggi, Anda bisa mengetahui apa yang Anda butuhkan.

HardScale
sumber
0

Saya telah mengerjakan hal ini sepanjang pagi dan melihat bukti dari apa yang dibahas di atas tanpa jawaban. Senseful dan saya sama-sama menginginkan ALAMAT dari sel yang lewat, bukan nilainya. Dan jawabannya sangat mudah. Itu tidak bisa dilakukan.

Saya menemukan beberapa solusi yang bergantung pada mencari tahu sel mana yang berisi formula. Sulit untuk mengatakan apakah ini akan membantu Senseful di atas. Jadi apa yang saya lakukan?

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

Kolom H adalah Sophia's (Wins - PrevWins) / (Losses - PrevLosses)

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

Tapi kita tidak tahu di mana baris Sophia sebelumnya muncul.
Ini semua bisa dilakukan dengan menggunakan VLOOKUP jika Anda hard-kode A sebagai kolom nama. Setelah VLOOKUP, saya mendapatkan beberapa #NA (nama tidak ditemukan) dan # DIV0 (penyebut nol) dan membungkusnya dengan = IF (IF (...)) untuk menampilkan teks yang lebih enak dalam kondisi ini. Sekarang saya memiliki ekspresi yang sangat besar yang sulit dan tidak dapat dipertahankan. Jadi saya ingin ekspansi makro (tidak ada), atau fungsi khusus.

Tetapi ketika saya membuat Helper SubtractPrevValue (sel), ia menerima "7" bukannya B5. Tidak ada cara bawaan untuk mendapatkan sel atau objek rentang dari argumen yang diteruskan.
Jika saya membuat tangan pengguna memasukkan nama sel dalam tanda kutip ganda maka saya bisa melakukannya ... SubtractPrevValue ("B5"). Tapi itu benar-benar paha belakang copy / paste dan fitur sel relatif.

Tetapi kemudian saya menemukan solusi.

SpreadsheetApp.getActiveRange () ADALAH SEL yang berisi rumus. Hanya itu yang perlu saya ketahui. Nomor baris Fungsi berikut mengambil nomor kolom NUMERIK dan mengurangi kejadian sebelumnya di kolom itu.

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
}

Tetapi kemudian saya menemukan bahwa ini benar-benar lambat. Satu dan dua penundaan kedua saat itu menampilkan "Berpikir ..." Jadi saya kembali ke rumus lembar kerja yang tidak terbaca secara besar-besaran dan tidak dapat dipertahankan.

Markus C
sumber
0

Alih-alih menyediakan objek "Rentang", Pengembang Google memberikan cukup banyak tipe data acak. Jadi, saya mengembangkan makro berikut untuk mencetak nilai 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;
}
Nama asli
sumber
0

Ada kompilasi saya dari jawaban sebelumnya

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

Dibutuhkan formula saat ini dan memeriksa apakah itu Range.

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

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

Untuk komposisi lengkap, TC dapat memanggil sesuatu seperti ini

var range = as.getRange(a1Notation);
var column = range.getColumn();
kontributorpw
sumber
-1

Buat fungsi dan lewati rentang sebagai argumen:

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

Penggunaan dalam spreadsheet:

=fn(A1:A10)

Ini akan menunjukkan nilai sebagai sum(A1:A10).

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