Bài 4: Vòng Lặp For

bài lần trước mình đã giới thiệu đến các bạn cách để copy và di chuyển dữ liệu giữa 2 sheet khác nhau. Còn lần này chúng ta sẽ học cách sử dụng vòng lặp, với mục đích hiện tại là: copy dải ô từ một sheet sang nhiều sheet khác cùng lúc. Đây chỉ là ví dụ do mình đặt ra, còn trong thực tế các bạn có thể vận dụng vòng lặp For để giải quyết nhiều nhu cầu khác.

Clip hướng dẫn

Nội dung

  • Vòng lặp trong App Script
  • Cấu trúc vòng lặp For
  • Ví dụ
  • Code
  • Giải thích
  • [QUAN TRỌNG] Khi nào nên dùng và không nên dùng vòng lặp For?

Tóm tắt

  • Vòng lặp For dùng để…
  • Yếu điểm: …

Vòng lặp trong App Script

Ngắn gọn mà nói, vòng lặp For giúp chúng ta thực hiện những hành động có tính chật lặp đi lặp lại nhiều lần.

Khi ứng dụng App Script vào Google Sheet để xử lí các ô hay dải ô, chắc chắn sẽ có những công việc giống nhau, lặp lại nhiều lần. Thế nên ta có thể dùng vòng lặp For để tiết kiệm thời gian lập trình, cùng như chạy chương trình.

Cấu trúc vòng lặp For

  • Giá trị đầu: thường thì ta dùng biến i là biến chạy trong vòng lặp, và khai báo biến i ở ngay trong vòng lặp (xem ví dụ)
  • Điều kiện cho giá trị cuối: trong vòng lặp, ta cho i chạy từ giá trị đầu đến giá trị cuối và sử dụng điều kiện để giới hạn i (xem ví dụ). Tuy nhiên điều kiện không nhất thiết phải liên quan đến i
  • i tăng / i giảm: nếu đây là vòng lặp tăng dần, ta dùng i++, tức là sau khi kết thúc 1 lần chạy, thì i sẽ tăng lên 1 đơn vị. Còn nếu đây là vòng lặp giảm dần thì dùng i–, ngược lại với i++ (xem clip để rõ về i–)

Ví dụ

Dùng vòng lặp for để copy dải ô A1:D6 đến vị trí F8:I14 trong các sheet: sheet1, sheet2, sheet3, sheet4 cùng lúc

Đầu tiên, mình có dải ô A1:D6, và mình muốn copy dải ô này đến vị trí F8:I14 trong các sheet: sheet1, sheet2, sheet3, và sheet4 cùng lúc.

Theo cách làm mà không dùng vòng lặp For thì bạn phải làm như sau:

  • Sử dụng 4 biến để khai báo cả 4 sheet từ sheet1 -> sheet4
  • Sử dụng hàm copyTo 4 lần để copy từ sheet0 đến các sheet trên

Nếu làm vậy thì chắc chắn là sẽ mất nhiều thời gian. Chúng ta dễ dàng thấy rằng việc khai báo cũng như sử dụng 4 lần hàm copyTo là công việc được lặp đi lặp lại nhiều lần. Thế nên ta hoàn toàn có thể áp dụng vòng lặp ở đây.

Code

Giải thích

Biến range dùng để lấy vị trí dải ô A1:D6

Trong vòng lặp, mình cho i chạy từ 1 đến khi i <= 4 vì ở đây chúng ta cần copy đến các sheet từ sheet1 -> sheet4

Khi chạy vòng lặp, biến sheetI cập nhật giá trị của i, nghĩa là:

  • Khi i = 1, sheetI sẽ thành ss.getSheets()[1]
  • Khi i = 2, sheetI sẽ thành ss.getSheets()[2]
  • Khi i = 3, sheetI sẽ thành ss.getSheets()[3]
  • Khi i = 4, sheetI sẽ thành ss.getSheets()[4]

Còn biến dichI thì sẽ lấy dải ô F8:I14 trong các sheet từ sheet1 -> sheet4 mỗi khi i tăng lên 1 đơn vị

[QUAN TRỌNG] Khi nào nên dùng và không nên dùng vòng lặp For?

Thật ra, sau khi các bạn đã biết được cách sử dụng vòng lặp For, thì mình hi vọng các bạn có thể đọc được những dòng này, tại vì nó thực sự quan trọng.

Theo mình biết, vòng lặp For có mặt trong hầu hết tất cả ngôn ngữ lập trình (nếu không muốn nói là tất cả). Java Script cũng không ngoại lệ. Nó giúp tiết kiệm thời gian và nhiều thứ khác. Tuy nhiên, vòng lặp có 1 nhược điểm sau đây, ảnh hưởng trực tiếp khi chúng ta ứng dụng vào Google Sheet.

Đó là nó mất nhiều thời gian để chạy chương trình. Lỗi không phải là do bản thân vòng lặp For, mà vì khi kết hợp với các câu lệnh Spreadsheet App Script, nó vô tình tăng thời gian xử lí ô và dải ô. Nói đơn giản như ở ví dụ trên. Khi chúng ta cho i chạy từ 1 đến 4, tức là 4 lần, cộng thêm việc sử dụng hàm getSheets và getRange, thì nó cứ phải lấy vị trí của các sheet và dải ô đến 8 lần (2×4=8).

Đối với đoạn code chỉ có 8 lần lấy sheet hay dải ô, thì không có gì đáng nói. Nhưng khi áp dụng vào khoảng 100 dải ô hay 100 ô, tức là getRange 100 lần, thì sẽ bắt đầu nảy sinh vấn đề về thời gian. Chương trình của các bạn lúc đó sẽ chạy rất chậm. Còn với dữ liệu lớn hơn, vài trăm đến vài ngàn hay vài chục ngàn thì thôi khỏi nói rồi, App Script sẽ tự động dừng sau 6 phút.

Tóm lại

Khi kết hợp với các hàm của Spreadsheet App Script, ta chỉ nên dùng For cho vòng lặp nhỏ (vài chục). Còn với dữ liệu lớn, có nhiều ô, dải ô cần getRange, getValue,… nhiều lần, thì không nên.

6 COMMENTS

  1. function sendEmails() {
    var sheet = SpreadsheetApp.getActiveSheet();
    var startRow = 2; // First row of data to process
    var numRows = 7; // Number of rows to process
    var dataRange = sheet.getRange(startRow, 1, numRows, 3)
    var data = dataRange.getValues();
    for (i in data) {
    var row = data[i];
    var emailAddress = row[1]; // Second column
    var message = row[2]; // Third column
    var subject = “My review notes”;
    MailApp.sendEmail(emailAddress, subject, message);
    }
    }

    Dear anh,

    Mình vừa xem qua một kiểu vòng lặp for…in trong một đoạn script dùng để gửi mail…và thật sự không hiểu cách dùng kiểu for này.

    For (i in data) nó sẽ lặp kiểu gì và i của từng vòng sẽ có giá trị ra sao?
    Sau đó lại có row = data[i] …

    Vậy liệu mình có thể hiểu là khi viết For (i in data), i sẽ bắt đầu chạy từ 0 cho đến số lượng phần tử mẹ (số dòng). Sau đó lấy các phần tử con trong mỗi phần tử mẹ (mỗi dòng) bằng câu lệnh

    var row = data[i];
    var emailAddress = row[1]; <- cái này có thể hiểu là emailAdress = data[0][1] cho vòng lặp đầu tiên ko ạ?

    Cám ơn anh

    • Chào bạn,

      Đó là 1 cách viết khác thôi ạ. Cách viết code này được khuyến khích sử dụng ở trên các trang hỗ trợ của Google Apps Sciprt do sự ngắn gọn của nó. Về cách hoạt động thì bạn đã hiểu đúng rồi. Nhưng còn điểm này thì chưa đúng:
      “var row = data[i];
      var emailAddress = row[1]; <- cái này có thể hiểu là emailAdress = data[0][1]"-> Khi i = 1 thì emailAdress = data[1] chứ ko phải data[0][1]
      Và bạn lưu ý ở đây data được gán là mảng 2 chiều để lấy giá trị Dải ô gồm nhiều hàng nhiều cột
      Nên data[i] sẽ là nguyên 1 dòng (row) ở bên google sheet luôn. Ý là data[i] = data[a,b], data[c,d], data[e,f]…

      Cảm ơn bạn đã quan tâm web!

      • Dear anh,

        Cám ơn anh đã hồi âm, nhưng không biết có nhầm lẫn gì không vì khi mình dùng Logger.log để xem ghi nhận của biến thì thấy khác.

        Row = data[i] đây mới là đoạn lấy toàn bộ dòng dữ liệu trên spreadsheet. Nghĩa là khi i = 0 thì logger đang ghi là toàn bộ dữ liệu của dòng đầu tiên trong vòng lặp.

        Sau đó, emailadress = row[1] thì là lấy dữ liệu cột thứ 2 của dòng đầu tiên. Vậy thì nó nên tương đương với data[0][1] mới đúng chứ anh nhỉ.

        Và cứ thế nó đi qua các dòng. Nên phiền a chỉ mình thêm.

        Rất cám ơn anh.

        • Chào bạn,

          Mình vừa kiểm tra lại bằng Logger.log và đúng như những gì bạn nói ở trên ạ.
          Tất nhiên là mỗi người một ý nhưng mà riêng mình thì thấy cách viết này tuy ngắn gọn nhưng khá khó để giải thích bằng lời nên không dùng nhiều lắm trong các bài chia sẻ.
          Nếu bạn thấy nó dễ hiểu hơn cách truyền thống thì cứ dùng thôi nhé. Ở một số trường hợp, nó không chỉ ngắn gọn mà còn có ưu thế xử lí dữ liệu nhanh. Lí do thì mình xin không nói ở đây, tại vì hơi khó với cả dài dòng để giải thích bằng lời ấy mà @@

          Thân.

  2. Chào anh,
    Anh có facebook hoặc zalo không ạ, em muốn hỏi anh 1 chút về google sheet ạ.
    1. Có cách nào để khi mở 1 file google sheet thì câc hàm không tự động chạy lại từ đầu không ? Vì em đang tạo 1 hàm getAddress để chuyển dữ liệu latitude và longitude thành tên địa chỉ qua google map API. Theo quy định thì tài khoản miễn phí đc gọi lệnh 25.000 lần/ngày. Vậy mà em mới có 300 dòng (tương ứng gọi 300 lệnh 1 ngày) mà đã báo vượt quá cho phép rồi. Em đang nghi là mỗi lần mở file, hàm lại tự động chạy lại từ dòng 2 tới dòng 300.
    2. Em muốn tạo vòng lặp để khi nhập dữ liệu dòng mới, ví dụ A10 và B10 có giá trị thì cột C10 cũng nhảy giá trị = getAddress(A10,B10), còn nếu ko có giá trị thì bỏ qua. Ý tưởng em như này:
    For i = 2 to last_row:
    C[i] = getAddress( A[i] , B[i] )
    nhưng em ko biết viết code google script thế nào. Bác hướng dẫn em được ko ạ ?
    Em muốn khi nhận thêm giá trị khai báo mới thì hàm này chỉ âp dụng với giá trị mới, còn các truy vấn google api trước thì ko chạy lại nữa (vì nếu truy vấn liên tục google api sẽ bị vượt 25.000 lần)
    Cảm ơn bác rất nhiều.

    • Chào bạn, cảm ơn bạn đã ghé thăm hocggsheet.com
      Mình xin trả lời các câu hỏi của bạn như sau:

      1. “Có cách nào để khi mở 1 file google sheet thì câc hàm không tự động chạy lại từ đầu không ?”
      Cái này thì còn tùy vào chương trình mà bạn viết sử dụng những hàm nào. Khi bạn nói đến con số 25000 thì mình thực sự không rõ 25000 này là gì. Nếu nó thực sự là 25000 lệnh / ngày thì nó càng không liên quan đến số dòng trong code. Giả sử như code của bạn có 300 dòng, nhưng với 1 vòng lặp for (i=1 -> 100) thì chỉ riêng dòng for thôi là đã chạy 100 lần = 100 lệnh rồi, chưa kể số dòng lệnh bên trong vòng lặp for và cứ thế nhân lên. Vậy nên mình tin rằng số dòng code không liên quan đến con số 25000 kia.
      Có thể tạm đoán là google giới hạn 25000 lệnh gọi map trong Google Map API. Mà code của bạn vượt quá con số này thì khả năng cao là bạn bị lỗi vòng lặp. Nhưng cũng không trừ khả năng là custom function tự chạy code như bạn nói, vì dĩ nhiên khi tải lại trang thì custom function sẽ phải chạy lại từ đầu. Hơi thiếu dữ kiện nên mình chưa giúp bạn được, sẽ tốt hơn nếu bạn chia sẻ code hoặc file ở đây.

      2.
      Bạn có thể dùng trigger onEdit() nhé.
      var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
      var activeCell = sheet.getActiveCell();
      var activeCellValue = sheet.getActiveCell().getValue();
      if ((activeCellValue != “”) && (activeCell.getColumn == 2)) {
      var row = activeCell.getRow();
      activeCell.offset(0,1).setFormula(“=getAddress(A” + row + “,B” + row + “)”);
      }

      3. Nếu cần thì bạn có thể trao đổi thêm với mình qua email [email protected]

      Thân

Leave a Reply