0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

時間割自動生成

Last updated at Posted at 2024-12-28

概要
スプレッドシートと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)
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?