Gg / 如何在Google Forms 隨機抽取題目(選擇題)

文章索引

Google Forms除了可以用來做問卷之外,也可以用來做測驗

今天嘗試利用一些折衷的方法來達成在Google Forms隨機抽取題庫來製作測驗表單

主要參考並修改自網路上的教學

以Google Sheet題庫產生Google Forms隨機測驗卷

【Google表單教學】自動選擇題題庫產生器 可選範圍+答案類型+讀入工作表+固定網址+選項亂數

整體架構分為2個部分

A. Google Apps Scritp-程式部分

B. Google spreadsheets-題庫部分


先說明B. Google spreadsheets-題庫部分

由4個工作表構成:表單、隨機試題、智產權是非題、工作倫理選擇題

 

智產權是非題、工作倫理選擇題:是題庫,

存放Google Forms新增選擇題需要的內容:B-題型、C-題目內容、D-題目說明、F-I選項、J-M選項答案

 

其中,選項答案是對應選項,1 為true-正確答案、2 為 false-錯誤答案

所以選擇題型可以是2選1、4選1或4選多…n選m

只需要調整A.程式裡有關「選項」的設計就可以了

表單:是A.程式讀取的試題資料來源

從隨機試題複製值而來的

隨機試題:將A.程式產生的隨機數字逐一寫入A欄裡

 

B-M欄使用VLOOKUP函式抓取題庫裡的資料

原先參考的範例是只有一個紀錄試題的工作表,在A欄用randbetween函式來產生隨機數字

但是這個只要工作表有任何增刪編輯的動作,都會觸發函式執行

導致在Google Forms產生的題目跟工作表上的試題無法對應

所以,我改成用程式(在工作表-隨機試題)輸入隨機數字,再把試題複製到另一個工作表-表單-內貼上

多了這個複製貼上,是希望程式讀取的是純資料型態的內容


A. Google Apps Scritp-程式部分

由1個主程式makeExam2 + 3個子程式randomRange2()、randomRange22()、copyRange2()在B.題庫產生隨機試題

randomRange2()、randomRange22()

其實可以合併,是用來產生不重複的隨機數字,然後寫入B.工作表-隨機試題,配合VLOOKUP函式抓取資料

產生不重複的隨機數字是參考 [筆記][JavaScript]用Math.random()取得亂數的技巧

利用 indexOf()函數來進行判斷

copyRange2()

將B.工作表-隨機試題的內容複製到工作表-表單 內

這邊有個很奇怪的問題

如果單獨執行這個程式,工作表-隨機試題的內容都可以順利複製到工作表-表單 內

但是在主程式makeExam2()裡執行,如果沒有宣告或者執行 sRandom.getRange(1,1,21,13).getValues(); 

從D12開始就會貼到舊的資料 

奇怪的點在於 sRandom.getRange(1,1,21,13).getValues() 在整個程式裡根本沒有用到

makeExam2()

將B.題庫裡的隨機試題-資料寫入指定的Google Forms

因為是直接從Google Apps Script連結到Google Forms

所以要用openById()

接下來是表單的基本設定

var form = FormApp.openById('********************************************'); //連結表單 
form.setIsQuiz(true); //表單設定成測驗 
form.setCollectEmail(true);//蒐集電子信箱 
form.setTitle("隨機抽取20題測驗"); 
form.setDescription("選20題來測驗");

setIsQuiz(true)、setCollectEmail(true) 其實只要設定一次就可以了

也可以在表單內設定

setTitle()、setDescription()是設定表單名稱跟說明

比較需要留意的是根據題型不同分為是非題(2選1)、選擇題(4選1、4選多)

這些在Google Forms裡都是多選題

都是利用addMultipleChoiceItem()來新增題目

參考範例的程式碼有很多沒用到的變數

我沒有刪除,只是用//註記起來

題型選項有2類,是非題 跟 選擇題

一個有2個選項、一個有4個選項

因為在設定選項的時候就有所差別 setChoices( [createChoice( value, isCorrect )] )

所以,我拆成兩個部份來處理

由於是修改自教學範例,所以有些變數的使用會怪怪的

像是在設定題目內容時,用了3種變數data[i][*]、choice[*]、cc*

但這都是單一題目內容的名稱/內容、選項、選項答案

mc.setTitle(data[i][2]); //題目名稱/內容 
mc.setHelpText(data[i][3]); //題目說明 
mc.setRequired(true); //是否必填 
mc.setPoints(10); //設定分數 
mc.setChoices([mc.createChoice(choice[0], cc1),mc.createChoice(choice[1], cc2)]);

完整的程式碼如下,細節都已經註解在裡面,所以就不再重複說明了

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//
//****************************************** 目前版本
//
//function doGet(e) {//部署成網頁應用程式
  //自動產生題庫
//  return makeExam2(1);
//}

//智產權是非題
function randomRange2(){
  var sAll = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('智產權是非題');
  var max = sAll.getDataRange().getNumRows()-1;
  var min = 1;
  var mm ;
  var nn=[];
  for (var i = 0; i <10 ; i++){
    mm = Math.floor(Math.random() * (max - min + 1)) + min;
    //console.log(nn);
    if(nn.indexOf(mm)>0){
        //如果有出現過就重跑一次迴圈
        i-=1;
        continue;
        }
        else{
        //沒出現過的話就寫進nn裡
        nn[i] = mm;
        };
  
  }
  console.log(nn);
  var sRandom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('隨機試題');
  for(var r= 0; r < nn.length; r++){
    sRandom.getRange(r+2,1).setValue(nn[r]);

  }
}
//
//工作倫理選擇題
function randomRange22(){
  var sAll = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('工作倫理選擇題');
  var max = sAll.getDataRange().getNumRows()-1;
  var min = 1;
  var mm ;
  var nn=[];
  for (var i = 0; i <10 ; i++){
    mm = Math.floor(Math.random() * (max - min + 1)) + min;
    //console.log(nn);
    if(nn.indexOf(mm)>0){
        //如果有出現過就重跑一次迴圈
        i-=1;
        continue;
        }
        else{
        //沒出現過的話就寫進nn裡
        nn[i] = mm;
        };
  
  }
  console.log(nn);
  var sRandom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('隨機試題');
  for(var r= 0; r < nn.length; r++){
    sRandom.getRange(r+12,1).setValue(nn[r]);

  }
}
//
//
function copyRange2(){
  var sRandom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('隨機試題');
  var sourceRange = sRandom.getRange(1,1,21,13);
  var targetSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單');
  console.log(sourceRange.getValues());
  sourceRange.copyValuesToRange(targetSheet,1,13,1,21);

}

//
function makeExam2(UserExam) {

 //
  randomRange2();
  Utilities.sleep(800);
//
  randomRange22();
  Utilities.sleep(800);
//
  copyRange2();
  Utilities.sleep(800);
//
 var ss = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單');
 
 //range = ss.getDataRange();             //取得有資料的範圍
 var range = ss.getRange("A1:M21");
 var data = range.getValues();              //取得資料陣列,每一列是一個題目
 //var numberRows = range.getNumRows();       //取得有資料的列數
 var numberRows = 21
 //var numberColumns = range.getNumColumns(); //取得有資料的欄數
 var firstRow = 1;
 
 var form = FormApp.openById('1qNz2Ml1vMVuRlmrneY0L5Hn8lPREzHVP_cEiqqD_Vvw');   //連結表單
 form.setIsQuiz(true);   //表單設定成測驗
 form.setCollectEmail(true);//蒐集電子信箱
 form.setTitle("隨機抽取20題測驗");
 form.setDescription("選20題來測驗");

 //console.log(form.isQuiz())
 
 // 下列是將表單既有回應清空且將原訂問題全部刪除
 form.deleteAllResponses();
 var oldItems = form.getItems().length;
 //
 //透過迴圈新增題目
 //
 for(var i=oldItems;i>0;i--){
  form.deleteItem(i-1); 
  }
 // 表單應無舊問題了
 //
 //
 //新增題目
 for(var i=0; i<numberRows; i++){
    var questionType = data[i][1];   //題目的第2欄是題型
    if (questionType==''){           
      continue;
    }
    //
    //是非題
    else if(questionType=='是非'){
      var rowLength = data[i].length;  //題目裡的項目數=欄位數
      var currentRow = firstRow+i;     //目前的列數/題目
      //var currentRangeValues = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getRange(currentRow,1,1,rowLength).getValues();  //取得目前的題目所有資料
      //console.log(currentRangeValues);
 
      //var getSheetRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getDataRange(); //取得題目所在的資料表 有資料的範圍
      //var numberOfColumnsSheet = getSheetRange.getNumColumns();                                          // 有資料的範圍 欄數
      //var numberOfOptionsInCurrentRow = numberOfColumnsSheet;
      //var lastColumnInRange = String.fromCharCode(64 + (numberOfColumnsSheet));                          // I      
      //console.log(lastColumnInRange);

      //var range_string = 'F' + currentRow + ":" + lastColumnInRange + currentRow;                        //取得選項範圍
      var range_string = 'F' + currentRow + ":" + "M" + currentRow;
      //console.log(range_string);

      var optionsArray = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getRange(range_string).getValues(); //取得選項範圍的資料

      //console.log(optionsArray);

      var choice = [optionsArray[0][0],optionsArray[0][1]];
      var correctness = [optionsArray[0][4],optionsArray[0][5]];
      //console.log(choice);
      //console.log(correctness);
      var cc1,cc2 ;
      if (correctness[0]==1){
        cc1 = true;                //正確 true
      }
      else{
        cc1 = false;               //錯誤 false
      };
      if (correctness[1]==1){
        cc2 = true;                //正確 true
      }
      else{
        cc2 = false;               //錯誤 false
      };
    
      var mc = form.addMultipleChoiceItem()
      mc.setTitle(data[i][2]);             //題目名稱/內容
      mc.setHelpText(data[i][3]);          //題目說明
      mc.setRequired(true);                //是否必填
      mc.setPoints(10);                    //設定分數
      mc.setChoices([mc.createChoice(choice[0], cc1),mc.createChoice(choice[1], cc2)]);
    }
    else if(questionType=='選擇'){
      var rowLength = data[i].length;  //題目裡的項目數=欄位數
      var currentRow = firstRow+i;     //目前的列數/題目
      var currentRangeValues = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getRange(currentRow,1,1,rowLength).getValues();  //取得目前的題目所有資料
      //console.log(currentRangeValues);
 
      //var getSheetRange = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getDataRange(); //取得題目所在的資料表 有資料的範圍
      //var numberOfColumnsSheet = getSheetRange.getNumColumns();                                          // 有資料的範圍 欄數
      //var numberOfOptionsInCurrentRow = numberOfColumnsSheet;
      //var lastColumnInRange = String.fromCharCode(64 + (numberOfColumnsSheet));                          // I      
      //console.log(lastColumnInRange);

      //var range_string = 'F' + currentRow + ":" + lastColumnInRange + currentRow;                        //取得選項範圍
      var range_string = 'F' + currentRow + ":" + "M" + currentRow;
      //console.log(range_string);

      var optionsArray = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('表單').getRange(range_string).getValues(); //取得選項範圍的資料

      //console.log(optionsArray);

      var choice = [optionsArray[0][0],optionsArray[0][1],optionsArray[0][2],optionsArray[0][3]];
      var correctness = [optionsArray[0][4],optionsArray[0][5],optionsArray[0][6],optionsArray[0][7]];
      //console.log(choice);
      //console.log(correctness);
      var cc1,cc2,cc3,cc4 ;
      if (correctness[0]==1){
        cc1 = true;                //正確 true
      }
      else{
        cc1 = false;               //錯誤 false
      };
      if (correctness[1]==1){
        cc2 = true;                //正確 true
      }
      else{
        cc2 = false;               //錯誤 false
      };
      if (correctness[2]==1){
        cc3 = true;                //正確 true
      }
      else{
        cc3 = false;               //錯誤 false
      };
      if (correctness[3]==1){
        cc4 = true;                //正確 true
      }
      else{
        cc4 = false;               //錯誤 false
      };

      var mc = form.addMultipleChoiceItem()
      mc.setTitle(data[i][2]);             //題目名稱/內容
      mc.setHelpText(data[i][3]);          //題目說明
      mc.setRequired(true);                //是否必填
      mc.setPoints(10);                    //設定分數
      mc.setChoices([mc.createChoice(choice[0], cc1),mc.createChoice(choice[1], cc2),mc.createChoice(choice[2], cc3),mc.createChoice(choice[3], cc4)]);
    }
    else{                    //****************第一列沒有符合的題型關鍵字  跳過
      continue;
    }
 } 
 //部署成網頁應用程式
 //if(UserExam==1){
 //  return HtmlService.createHtmlOutput("<center><p><font size='7'><a href=" + form.getPublishedUrl()+ ">點我開始進行測驗</a></font></p></center>");
 //}
      
}

 

備註:

用這個方式產生的隨機試題,有幾個地方需要留意

1.程式執行的時間需要將近1分鐘(20題),題目越多,需要的時間就會越久

2.紀錄表單的試算表,會一直紀錄之前的題目,不過試算表可以有1,000 萬個儲存格或18,278 個欄(欄ZZZ),所以應該是足夠的

3.雖然試算表的儲存空間應該是足夠的,但是最好在第一次產生表單的時候就開啟為測驗與蒐集電子信箱,這樣電子郵件地址、分數的資料就會一直在前面

4.可以部署成網頁應用程式,這樣每次執行就可以觸發程式,只不過要提醒使用者要有耐心等表單產生,不過不知道同時很多人使用會發生什麼事~~

function doGet(e) {//部署成網頁應用程式 //自動產生題庫 // 
                   return makeExam2(1);
}

makeExam2(UserExam)則改為帶參數的形式

程式內用判斷式來判斷是否由網頁應用程式觸發,是的話,在程式的尾端時,也就是前面產生表單的程序都跑完之後

將表單網址用HtmlService.createHtmlOutput()回傳到前端

//部署成網頁應用程式 
if(UserExam==1){ // 
       eturn HtmlService.createHtmlOutput("<center><p><font size='7'><a href=" + form.getPublishedUrl()+ ">點我開始進行測驗</a></font></p></center>"); 
}