ダブルスの試合を2コートでまわすときにプレイヤーの重複のない組み合わせを探す

問題設定

ダブルスの試合をたくさんやることになりました。試合組はすでに決まっています。
2面を使って試合をし、2つとも終わった段階で次の2試合を始める方式とします。これを1ターンと呼びます。
全部で20試合あるので理論上10ターンで終わるはずです。しかし、当然1人が同一ターンの2試合に出ることはできません。
これを解消する必要がありますが、適当に組んでたらどうにもうまくいかなかったのでプログラムを書くことになりました。

制約

  • 試合ごとにかかる時間は一定ではないので、長い時間がかかる試合と短い時間がかかる試合が同じターンにあるとロスが大きいのですが、ここではかかる時間は一定であるとします
  • 連続した試合は当然避けたいものですが、この後のステップでターンごとに並び替えればよいのでここでは気にしないことにします

解法

gamesHash{Integer => Array<String>} で試合IDとプレイヤー名の配列が入っているものとします。
出力はなんでもいいのですが spreadsheet に食わせるので1ターンごとに1行で id_1,id_2 というCSV形式にしました。

方針自体は割と簡単で、とりあえず詰めてみて、制約違反が発覚したら1手戻してみるという形式です。
+ より push して pop すればよかった気はしなくもない。

全部で20ゲーム、ターン内の組み合わせは可換、10ターンの並び替えも可換なので組み合わせ的には 20!/(2^10)/10! ですかね。
手元で実行したら 0.17s でした。

def solve(games, table)
  if table.size == games.size
    return table
  end

  if table.size % 2 == 0
    games.each do |k, v|
      unless table.include?(k)
        ans = solve(games, table + [k])
        if ans
          return ans
        end
      end
    end
    nil
  else
    used_id = table[-1]
    used = games[used_id]

    games.each do |k, v|
      unless table.include?(k)
        if used.none? { |p| v.include?(p) }
          ans = solve(games, table + [k])
          if ans
            return ans
          end
        end
      end
    end

    return nil
  end
end

ans = solve(games, [])

ans.each_slice(2) do |a, b|
  puts "#{a},#{b}"
end

以下テスト用のデータです。

{
  "2": [
    "a",
    "b",
    "c",
    "d"
  ],
  "3": [
    "b",
    "e",
    "c",
    "f"
  ],
  "4": [
    "a",
    "g",
    "f",
    "d"
  ],
  "5": [
    "a",
    "h",
    "c",
    "i"
  ],
  "6": [
    "g",
    "e",
    "f",
    "j"
  ],
  "7": [
    "a",
    "k",
    "f",
    "i"
  ],
  "8": [
    "b",
    "k",
    "c",
    "l"
  ],
  "9": [
    "b",
    "h",
    "d",
    "j"
  ],
  "10": [
    "a",
    "e",
    "f",
    "l"
  ],
  "11": [
    "b",
    "m",
    "c",
    "j"
  ],
  "12": [
    "h",
    "g",
    "d",
    "l"
  ],
  "13": [
    "h",
    "e",
    "i",
    "l"
  ],
  "14": [
    "g",
    "k",
    "d",
    "n"
  ],
  "15": [
    "e",
    "k",
    "c",
    "n"
  ],
  "16": [
    "a",
    "m",
    "i",
    "j"
  ],
  "17": [
    "h",
    "k",
    "l",
    "j"
  ],
  "18": [
    "e",
    "m",
    "f",
    "n"
  ],
  "19": [
    "g",
    "m",
    "i",
    "n"
  ],
  "20": [
    "h",
    "m",
    "l",
    "n"
  ],
  "21": [
    "k",
    "m",
    "j",
    "n"
  ]
}