概要
スプレッドシートとGASを利用した自動時間割生成ツール
機能
完全自動ツールと一部手動ツール
自動時間割
時間割生成
function generateTimetable() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const settingsSheet = ss.getSheetByName("Settings");
const timetableSheet = ss.getSheetByName("Timetable");
// 教科名と回数制限を取得
const subjects = settingsSheet.getRange(2, 1, settingsSheet.getLastRow() - 1, 1).getValues().flat();
const limits = settingsSheet.getRange(2, 2, settingsSheet.getLastRow() - 1, 1).getValues().flat();
const timetable = Array.from({ length: 6 }, () => Array(5).fill(""));
const count = {};
const specialSubject = "図工"; // 図工の教科名
subjects.forEach((subject, i) => count[subject] = limits[i]);
// クラブの内容をSettingsシートのA13から取得
const clubActivity = settingsSheet.getRange("A13").getValue();
// 先に水曜日6時間目をクラブ活動として配置
timetable[5][2] = clubActivity;
// ランダム配置用ヘルパー関数
function getRandomIndex(array) {
return Math.floor(Math.random() * array.length);
}
// 図工をランダムに配置(どの曜日でも可、奇数時間から2時間連続で配置)
for (let i = 0; i < count[specialSubject] / 2; i++) {
const availableDays = [0, 1, 2, 3, 4]; // 月曜から金曜まで
const randomDay = availableDays[getRandomIndex(availableDays)]; // ランダムに曜日を選択
const availablePeriods = [];
// 奇数時間(1時間目、3時間目、5時間目)のみ
for (let period = 0; period < 6; period += 2) {
if (period < 5 && timetable[period][randomDay] === "" && timetable[period + 1][randomDay] === "") {
availablePeriods.push(period);
}
}
if (availablePeriods.length > 0) {
const randomPeriod = availablePeriods[getRandomIndex(availablePeriods)];
timetable[randomPeriod][randomDay] = specialSubject;
timetable[randomPeriod + 1][randomDay] = specialSubject;
count[specialSubject] -= 2;
}
}
// 他の教科を配置
for (let day = 0; day < 5; day++) {
const usedSubjects = new Set(); // その曜日に既に配置された教科を記録
for (let period = 0; period < 6; period++) {
// 既に配置済みの場合、または水曜日6時間目の場合はスキップ
if (timetable[period][day] !== "" || (period === 5 && day === 2)) continue;
const availableSubjects = subjects.filter(subject =>
count[subject] > 0 &&
!usedSubjects.has(subject) // その曜日に既に配置されていない
);
// 配置可能な教科がない場合はスキップ
if (availableSubjects.length === 0) continue;
const randomIndex = getRandomIndex(availableSubjects);
const selectedSubject = availableSubjects[randomIndex];
timetable[period][day] = selectedSubject;
count[selectedSubject]--;
usedSubjects.add(selectedSubject); // その曜日の教科リストに追加
}
}
// 未配置の教科を強制的に配置
for (let period = 0; period < 6; period++) {
for (let day = 0; day < 5; day++) {
// クラブが配置された水曜日6時間目はスキップ
if (timetable[period][day] === "" && !(period === 5 && day === 2)) {
const remainingSubjects = subjects.filter(subject => count[subject] > 0);
if (remainingSubjects.length > 0) {
const randomIndex = getRandomIndex(remainingSubjects);
const selectedSubject = remainingSubjects[randomIndex];
timetable[period][day] = selectedSubject;
count[selectedSubject]--;
}
}
}
}
// 時間割をスプレッドシートに出力
timetableSheet.getRange(2, 2, 6, 5).setValues(timetable);
}
張り付け
function copyTimetableToExistingSheet() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const timetableSheet = ss.getSheetByName("Timetable");
const existingSheetName = "時間割";
// "時間割"シートを取得
let existingSheet = ss.getSheetByName(existingSheetName);
if (!existingSheet) {
// "時間割"シートが存在しない場合、新しいシートを作成
existingSheet = ss.insertSheet(existingSheetName);
}
// Timetableシートの範囲を取得
const range = timetableSheet.getDataRange();
const values = range.getValues();
// 既存シートのデータをクリア
existingSheet.clear();
// データを既存シートにコピー
existingSheet.getRange(1, 1, values.length, values[0].length).setValues(values);
// 完了メッセージをログに出力
Logger.log("時間割が既存のシートにコピーされました。");
}
追加メニュー
function onOpen() {
const ui = SpreadsheetApp.getUi(); // UIを取得
ui.createMenu('カスタムメニュー') // メニュー名を設定
.addItem('時間割を生成', 'generateTimetable') // メニュー項目と実行する関数
.addItem('既存シートに時間割をコピー', 'copyTimetableToExistingSheet') // メニュー項目と実行する関数
.addItem('簡単時間割', 'updateTimetableVertical') // メニュー項目と実行する関数
.addToUi(); // メニューをUIに追加
}
手入力時間割
function updateTimetableVertical() {
const subjectSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('手入力設定');
const timetableSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('手入力時間割');
// 1. 手入力設定シートのB2:B12の元の値を保存
const originalValues = subjectSheet.getRange("B2:B12").getValues();
// 2. 手入力時間割シートのJ2:J12を手入力設定シートのB2:B12に貼り付け
const manualInputRange = timetableSheet.getRange("J2:J12").getValues();
subjectSheet.getRange("B2:B12").setValues(manualInputRange);
// 3. 科目データを取得
const subjects = subjectSheet.getRange("A2:B12").getValues();
let randomSubjects = subjects.flatMap(([subject, count]) =>
Array.from({ length: count }, () => subject)
).sort(() => Math.random() - 0.5);
for (let col = 2; col <= 6; col++) { // 曜日ごとに処理
const usedSubjects = new Set(); // この曜日に使用された教科を記録
for (let row = 2; row <= 7; row++) { // 時間帯
const cell = timetableSheet.getRange(row, col);
const cellValue = cell.getValue();
if (!cellValue) { // セルが空の場合のみ処理
let assigned = false;
// 割り当て候補を探す
for (let i = 0; i < randomSubjects.length; i++) {
const subject = randomSubjects[i];
if (!usedSubjects.has(subject)) { // 同じ曜日に既に割り当てられていない教科を確認
cell.setValue(subject);
usedSubjects.add(subject); // この曜日で使用済みとして記録
randomSubjects.splice(i, 1); // 割り当てた教科をリストから削除
assigned = true;
break; // 割り当て完了したら次のセルへ
}
}
// 割り当てられなかった場合、ランダムに割り当て
if (!assigned && randomSubjects.length > 0) {
const subject = randomSubjects.shift(); // 残った教科をランダムに取得
cell.setValue(subject);
}
}
}
}
// 4. 手入力設定シートのB2:B12を元の値に戻す
subjectSheet.getRange("B2:B12").setValues(originalValues);
}
その他関数
コマ数分並べる(手入力設定シート C2:C13)
=iferror(TEXTJOIN(",", TRUE, TRANSPOSE(SPLIT(REPT(A2 & ",", B2), ","))),"")
すべての教科を繋げる(手入力設定シート E2)
=TEXTJOIN(",", TRUE, C2:C12)