1

編集:コードを変更して、名前 (A1 表記) で範囲を提供する可能性を含めるRangeようにしました。AdamL によるアイデア (以下の回答を参照)。


一部のスプレッドシートでは、行または列を並べ替える必要があります。ユーザーにこれを手動で行うように要求するのはあまり良くありません。したがって、メニューでスクリプトを実行する適切なコマンドを作成することは、合理的な解決策のように思われました。

奇妙なことに、行/列を並べ替える関数 (組み込みまたは他の人が作成した関数) を見つけることができませんでした。それで私は自分で書いてから、それを公開することを考えました. しかし、JavaScript と Google Apps Script の経験が少ないので、他の人にこの機能をチェックしてもらいたいと思いました。また、いくつか質問があります。それでは、行きましょう。


// Parameters:
// - ranges An Array with ranges which contents are to be permuted.
//          All the ranges must have the same size. They do not have to be
//          vectors (rows or columns) and can be of any size. They may come from
//          different sheets.
//          Every element of the array must be either a Range object or a string
//          naming the range in A1 notation (with or without sheet name).
// - permutation An Array with 0-based indexes determining desired permutation
//               of the ranges. i-th element of this array says to which range
//               should the contents of i-th range be moved.
// - temp A range of the same size as the ranges in "ranges". It is used to
//        temporarily store some ranges while permuting them. Thus the initial
//        contents of this range will be overwritten and its contents on exit is
//        unspecified. Yet if there is nothing to be moved ("ranges" has less
//        than 2 elements or all ranges are already on their proper places) this
//        range will not be used at all.
//        It is advised to make this range hidden so the "garbage" doesn't
//        bother user.
//        This can be either a Range object or a string naming the range in A1
//        notation (with or without sheet name) - just as with the "ranges".
// - sheet An optional Sheet object used to resolve range names without sheet
//         name. If none is provided active sheet is used. Note however that it
//         may cause issues if user changes the active sheet while the script is
//         running. Thus if you specify ranges by name without sheet names you
//         should provide this argument.
//
// Return Value:
// None.
//
// This function aims at minimizing moves of the ranges. It does at most n+m
// moves where n is the number of permuted ranges while m is the number of
// cycles within the permutation. For n > 0 m is at least 1 and at most n. Yet
// trivial 1-element cycles are handled without any moving (as there is nothing
// to be moved) so m is at most floor(n/2).
//
// For example to shift columns A, B and C by 1 in a cycle (with a temp in
// column D) do following:
//
// permuteRanges(
//   ["A1:A", "B1:B", "C1:C"],
//   [1, 2, 0],
//   "D1:D",
//   SpreadsheetApp.getActiveSheet()
// );
function permuteRanges(ranges, permutation, temp, sheet) {
  // indexes[i] says which range (index of ranges element) should be moved to
  // i-th position.
  var indexes = new Array(permutation.length);
  for(var i = 0; i < permutation.length; ++i)
    indexes[permutation[i]] = i;

  // Generating the above array is linear in time and requires creation of a
  // separate array.

  // Yet this allows us to save on moving ranges by moving most of them to their
  // final location with only one operation. (We need only one additional move
  // to a temporary location per each non-trivial cycle.)


  // Range extraction infrastructure.

  // This is used to store reference sheet once it will be needed (if it will be
  // needed). The reference sheet is used to resolve ranges provided by string
  // rather than by Range object.
  var realSheet;
  // This is used to store Range objects extracted from "ranges" on
  // corresponding indexes. It is also used to store Range object corresponding
  // to "temp" (on string index named "temp").
  var realRanges;

  // Auxiliary function which for given index obtains a Range object
  // corresponding to ranges[index] (or to temp if index is "temp").
  // This allows us to be more flexible with what can be provided as a range. So
  // we accept both direct Range objects and strings which are interpreted as
  // range names in A1 notation (for the Sheet.getRange function).
  function getRealRange(index) {
    // If realRanges wasn't yet created (this must be the first call to this
    // function then) create it.
    if(!realRanges) {
      realRanges = new Array(ranges.length);
    }

    // If we haven't yet obtained the Range do it now.
    if(!realRanges[index]) {
      var range;

      // Obtain provided range depending on whether index is "temp" or an index.
      var providedRange;
      if(index === "temp") {
        providedRange = temp;
      } else {
        providedRange = ranges[index];
      }

      // If corresponding "ranges" element is a string we have to obtain the
      // range from a Sheet...
      if(typeof providedRange === "string") {
        // ...so we have to first get the Sheet itself...
        if(!realSheet) {
          // ...if none was provided by the caller get currently active one. Yet
          // note that we do this only once.
          if(!sheet) {
            realSheet = SpreadsheetApp.getActiveSheet();
          } else {
            realSheet = sheet;
          }
        }
        range = realSheet.getRange(providedRange);
      } else {
        // But if the corresponding "ranges" element is not a string then assume
        // it is a Range object and use it directly.
        range = providedRange;
      }

      // Store the Range for future use. Each range is used twice (first as a
      // source and then as a target) except the temp range which is used twice
      // per cycle.
      realRanges[index] = range;
    }

    // We already have the expected Range so just return it.
    return realRanges[index];
  }


  // Now finally move the ranges.

  for(var i = 0; i < ranges.length; ++i) {
    // If the range is already on its place (because it was from the start or we
    // already moved it in some previous cycle) then don't do anything.
    // Checking this should save us a lot trouble since after all we are moving
    // ranges in a spreadsheet, not just swapping integers.
    if(indexes[i] == i) {
      continue;
    }

    // Now we will deal with (non-trivial) cycle of which the first element is
    // i-th. We will move the i-th range to temp. Then we will move the range
    // which must go on the (now empty) i-th position. And iterate the process
    // until we reach end of the cycle by getting to position on which the i-th
    // range (now in temp) should be moved.
    // Each time we move a range we mark it in indexes (by writing n on n-th
    // index) so that if the outer for loop reaches that index it will not do
    // anything more with it.

    getRealRange(i).moveTo(getRealRange("temp"));

    var j = i;
    while(indexes[j] != i) {
      getRealRange(indexes[j]).moveTo(getRealRange(j));

      // Swap index[j] and j itself.
      var old = indexes[j];
      indexes[j] = j;
      j = old;
    }

    getRealRange("temp").moveTo(getRealRange(j));
    // No need to swap since j will not be used anymore. Just write to indexes.
    indexes[j] = j;
  }
}

質問は次のとおりです。

  1. これは適切に実装されていますか?改善できますか?

  2. パラメータの検証はどうですか?やるべきですか?それらが無効な場合はどうすればよいですか?

  3. copyToまたはを使用するかどうかはわかりませんでしたmoveTomoveTo私がやろうとしていたことの方が私には思えたので、私は決めました。しかし、今考えてみると、おそらくcopyToもっと効率的だと思います。

  4. Rangeまた、移動元が常にクリアされるとは限らないことに気付きました。特にデバッガーの場合。

  5. 元に戻す/やり直しは、この機能の問題のようです。スプレッドシートでのすべてmoveToの操作は個別の操作であり (さらに悪いことに、テストしたときの Google ドキュメントの応答性が低かっただけかもしれません)、順列を元に戻す操作は 1 回ではありません。それについて何かできることはありますか?

  6. この関数について私が書いたドキュメントでは、異なるシートや異なるスプレッドシートでも機能すると主張しています。私は実際にそれをチェックしていません;)しかし、Google Apps Scriptのドキュメントはそれを否定していないようです. そのように機能しますか?


ここでそのような質問をするのが適切かどうかはわかりませんが (これは本当の質問ではないため)、Google Apps Script コミュニティ サポートが Stack Overflow に移行するため、他にどこで質問すればよいかわかりませんでした。

4

2 に答える 2

2

配列でやった方が実行速度の点で効率的だと思いませんか?

たとえば、これを試してください:(何が起こるかを示すためにログをどこにでも追加しました)(シートは255列に制限されていることにも注意してください...リストの長さに注意してください)

function permutation() {
var sh = SpreadsheetApp.getActiveSheet();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var lr = ss.getLastRow()
var lc=ss.getLastColumn();
var data = sh.getRange(1,1,lr,lc).getValues()
Logger.log(data)

var temp2= new Array();
var h=data.length
Logger.log(h)
var w=data[0].length
Logger.log(w)
for(nn=0;nn<w;++nn){
var temp1= new Array();
for (tt=0;tt<h;++tt){
  temp1.push(data[tt][nn])
  }
  temp2.push(temp1)
  }
Logger.log(temp2) 
Logger.log(temp2.length) 
Logger.log(temp2[0].length) 
sh.getRange(1,1,lr,lc).clear()
sh.getRange(1,1,lc,lr).setValues(temp2)
}

よろしく、 セルジュ

于 2012-05-09T14:24:25.360 に答える
1

Adam さん、Apps Script GPF での私の限られた経験から、get と set の呼び出しをできるだけ制限するのが最善であることを学びました (また、moveTo/copyTo を含めることもできます)。

範囲ではなく範囲名をパラメーターとして渡すほうがよいと思いますか (そのためには、シート名とスプレッドシート キーも渡すメカニズムが必要になる場合があります。異なるシート間で作業するという要件をサポートします)。 /spreadsheets)、そして簡単な "getRange's" は簡単な "moveTo's" と同様に避けることができます。

また、値のみを転送する場合は、一時的な範囲に移動するのではなく、それらの配列をスクリプト内の変数に割り当てて、後で正しい場所に「設定」できるようにすることをお勧めします。ただし、フォーマットや式をコピーする必要がある場合は、話は別です。

于 2012-05-09T02:43:30.170 に答える