Skip to content

Instantly share code, notes, and snippets.

@motsu0
Last active January 6, 2022 10:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save motsu0/381b8b93c9b38565b8eff1c78731f1ac to your computer and use it in GitHub Desktop.
Save motsu0/381b8b93c9b38565b8eff1c78731f1ac to your computer and use it in GitHub Desktop.
#input-name{
display: block;
width: 80%;
min-width: 80%;
max-width: 80%;
height: 150px;
margin: 0 auto;
}
#sel-times{
display: block;
margin: 16px auto;
}
#bt-play{
display: block;
margin: 20px auto;
cursor: pointer;
}
#bt-reset{
display: block;
margin: 20px 0 20px auto;
border: none;
background-color: #fff;
color: #2C9400;
cursor: pointer;
}
.res-unit{
padding: 0 8px;
margin: 8px 0;
border: 1px solid #aaa;
border-radius: 5px;
}
.res-title{
padding: 8px;
border-bottom: 1px solid #aaa;
}
.res-body{
padding: 8px;
margin-top: 4px;
}
.person{
display: inline-block;
padding: 2px 8px;
margin: 4px;
border: 1px solid #71AB09;
}
/* */
.loading-guage-outer{
box-sizing: border-box;
width: 50%;
height: 40px;
margin: 0 auto;
position: relative;
border: 1px solid #333;
}
.loading-text,#loading-guage{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.loading-text{
display: flex;
justify-content: center;
align-items: center;
z-index: 2;
}
#loading-guage{
z-index: 1;
width: 0;
background-color: #C8E6C9;
}
/* */
#msg-box{
box-sizing: border-box;
width: 100%;
position: fixed;
bottom: 10px;
left: 0;
z-index: 1000;
text-align: center;
pointer-events: none;
}
#message{
display: inline-block;
box-sizing: border-box;
max-width: 90%;
padding: 10px 20px;
border: 2px solid #BF360C;
background-color: #FBE9E7;
color: #BF360C;
word-break: break-all;
opacity: 1;
pointer-events: auto;
}
#message.off{
opacity: 0;
transition: opacity 1s;
pointer-events: none;
}
.invisivle{
visibility: hidden;
}
<textarea id="input-name" placeholder="Aさん
Bさん
Cさん
Dさん
Eさん
Fさん
Gさん
Hさん
">
Aさん
Bさん
Cさん
Dさん
Eさん
Fさん
Gさん
Hさん
</textarea>
<select id="sel-times">
<option value="infinite">同じ組み合わせのペアを許可する</option>
<option value="once">同じ組み合わせのペアを許可しない</option>
</select>
<button id="bt-play">ペア分けを決める</button>
<button id="bt-reset">リセット</button>
<div id="loading-area" class="hide">
<div class="loading-guage-outer">
<div class="loading-text">ペアを作成中</div>
<div id="loading-guage"></div>
</div>
</div>
<div id="output"></div>
<div id="msg-box">
<div id="message" class="off">データを保存しました。</div>
</div>
class nowLoading{
constructor(){
this.area = document.getElementById('loading-area');
this.guage = document.getElementById('loading-guage');
}
start(){
this.guage.style.width = 0;
this.area.classList.remove('hide');
}
change(n){
this.guage.style.width = n+'%';
}
stop(){
this.area.classList.add('hide');
}
}
const worker_name = 'path/to/make-pair-worker2.js';
const input_name = document.getElementById('input-name');
const output = document.getElementById('output');
const sel_times = document.getElementById('sel-times');
const bt_play = document.getElementById('bt-play');
const bt_reset = document.getElementById('bt-reset');
const message = document.getElementById('message');
let worker;
let pair_list;
let res_index = 0;
//
input_name.addEventListener('input',shokika);
sel_times.addEventListener('change',shokika);
bt_play.addEventListener('click',chooseFunc);
bt_reset.addEventListener('click',shokika);
const nowloading = new nowLoading();
//
function shokika(){
if(worker!==undefined) worker.terminate();
output.textContent = '';
pair_list = [];
res_index = 0;
nowloading.stop();
bt_play.classList.remove('invisible');
}
function chooseFunc(){
//稼働開始
bt_play.classList.add('invisible');
nowloading.start();
//
if(sel_times.value=='infinite'){
makeFree();
}else{
if(res_index==0){
makePairSet();
}else{
outputPairSet();
}
}
}
function makePairSet(){
//名前セット作成
const name_list = input_name.value.split(/\r\n|\r|\n/).map(v=>v.replace(/^[\s ]*$/,'')).filter(v=>v!='').map((v,i)=>{return {id:i,name:v}});
if(name_list.length<2){
nowloading.stop();
msgBox('ペア分けに必要な人数に足りません。')
return;
}
// ペア分け 最大数まで作成 workerで並列処理
worker = new Worker(worker_name);
worker.addEventListener('message',e=>{
if(e.data.end){
pair_list = e.data.list;
shuffle(pair_list);
outputPairSet();
}else{
nowloading.change(e.data.progress*100);
}
});
worker.postMessage(name_list);
}
function makeFree(){
//名前セット作成
const name_list = input_name.value.split(/\r\n|\r|\n/).map(v=>v.replace(/^[\s ]*$/,'')).filter(v=>v!='');
if(name_list.length<2){
nowloading.stop();
msgBox('ペア分けに必要な人数に足りません。')
return;
}
shuffle(name_list);
const rec = [];
pair_list = [];
while(name_list.length>1){
rec.push(name_list.splice(0,2));
if(name_list.length==1){
rec.push([name_list.shift()]);
}
}
pair_list.push(rec);
outputPairSet();
}
function outputPairSet(){
//上限
const res_max = pair_list.length;
if(sel_times.value!='infinite'&&res_index+1>res_max){
nowloading.stop();
msgBox('可能な組み合わせがありません。');
return;
}
//出力
//要素作成
const res_unit = document.createElement('div');
res_unit.classList.add('res-unit');
const res_title = document.createElement('div');
res_title.classList.add('res-title');
res_title.textContent = `ペア分け結果(${res_index+1}回目)`;
const res_body = document.createElement('div');
res_body.classList.add('res-body');
const rec_id = (()=>{
if(sel_times.value=='infinite'){
return 0;
}else{
return res_index;
}
})();
for(let p=0;p<pair_list[rec_id].length;p++){
pair_list[rec_id][p].forEach((p,i)=>{
if(i==1){
const and = document.createTextNode('&');
res_body.appendChild(and);
}
const person = document.createElement('span');
person.classList.add('person');
person.textContent = p;
res_body.appendChild(person);
});
if(p<pair_list[rec_id].length-1) res_body.appendChild(document.createElement('br'));
}
res_index++;
//組み立て
res_unit.appendChild(res_title);
res_unit.appendChild(res_body);
output.prepend(res_unit);
//後処理
nowloading.stop();
bt_play.classList.remove('invisible');
}
function randomMN(m,n){
const d = Math.max(m,n)-Math.min(m,n);
return Math.floor(Math.random()*(d+1)+Math.min(m,n));
}
function shuffle(array){
for(let i=array.length-1;i>=1;i--){
const j = randomMN(0,i);
[array[j],array[i]] = [array[i],array[j]];
}
}
function msgBox(str){
message.textContent = str;
message.classList.remove('off');
setTimeout(()=>{
message.classList.add('off');
},1000);
}
self.addEventListener('message',e=>{
self.postMessage({end:true,list:combi(e.data)});
});
function combi(array){
const list = [];
if(array.length%2==1) array.push({id:array.length,name:''});
//組み合わせ総数計算
const len_max = calcLen(array);
//組み合わせ列挙
sub_combi(array,[]);
//並び替え
sort();
//重複削除
return delSame(list);
function calcLen(arr){
const n = Math.ceil(arr.length/2)*2;
let res = 1;
for(let i=0;2*i+1<n;i++){
res *= (2*i+1);
}
return res;
}
function sub_combi(arr,rec){
const item1 = arr.shift();
arr.forEach((v,i)=>{
const item2 = arr[i];
const new_array = arr.slice(0,i).concat(arr.slice(i+1));
const record = rec.slice();
record.push([item1,item2].filter(val=>val.name!=''));
if(new_array.length<2){
list.push(record);
self.postMessage({end:false,progress:list.length/len_max});
}else{
sub_combi(new_array,record);
}
});
}
function sort(){
for(let r=0;r<list.length;r++){
for(let p=0;p<list[r].length;p++){
list[r][p].sort((a,b)=>{
return a.id - b.id;
});
}
list[r].sort((pair_a,pair_b)=>{
if(pair_a.length==1){
return 1;
}else if(pair_b.length==1){
return -1;
}else if(pair_a[0].id!=pair_b[0].id){
return pair_a[0].id - pair_b[0].id;
}else{
return pair_a[1].id - pair_b[1].id;
}
});
}
list.sort((rec_a,rec_b)=>{
for(let p=0;p<rec_b.length;p++){
for(let d=0;d<rec_b[p].length;d++){
if(rec_a[p][d].id!=rec_b[p][d].id){
return rec_a[p][d].id - rec_b[p][d].id;
}
}
}
return 1;
});
}
function delSame(list){
const data_list = [];
const already = {};
list.forEach(rec=>{
const id_set = [];
for(let p=0;p<rec.length;p++){
const pair_id = rec[p].map(v=>v.id).join('-');
if(already[pair_id]){
return;
}
id_set.push(pair_id);
}
id_set.forEach(id=>{
already[id] = true;
});
data_list.push(rec.map(pair=>pair.map(v=>v.name)));
});
return data_list;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment