参与者列表

时间:2017-03-18 14:17:54

标签: javascript algorithm sorting google-apps-script

问题

在我们的团队办理登机手续后,我们会与(n)参与者进行协调会议,讨论各种(唯一命名的)主题。

目前,人们试图自我组织,我们不是最大化会议的并发性,和/或尽快释放团队成员重返工作岗位。

我们列出了Google Spreadsheet中签到的所有协调会议清单:

+-----------------------------------------------------------+
|  Meetings                                                 |
+-----------------------------------------------------------+
|  Topic A  |  Topic B  |  Topic C  |  Topic D  |  Topic E  |
+-----------------------------------------------------------+
|  Billy    |   JD      |  David    |   Tania   |   JB      |
|  Mel      |   Rowan   |  Emily    |   David   |   Fred    |
|  Tracey   |   Mike    |           |   Mike    |   Tania   |
|  JB       |   Aaron   |           |   Fred    |           |
|  Luke     |   Billy   |           |           |           |
|  Aaron    |           |           |           |           |
|  Michael  |           |           |           |           |
+-----------------------------------------------------------+

作为一个起点,我有(当然效率很低)代码迭代每次会议并返回没有参与者冲突的对数组。

来自main()的示例输出以上输入数据(不要担心所有代码如下):

[["Topic A","Topic C"],["Topic A","Topic D"],["Topic B","Topic C"],["Topic B","Topic E"],["Topic C","Topic E"]]


我需要什么

理想情况下,我需要一个解决方案,它可以返回大多数会议所要求的数组,以便立即运行。 使用从{{1}返回的对数组或者使用电子表格中的数据作为输入的完全其他方法。

在我的示例中,它将采用以下形式:

main()

然后我会在Google电子表格中按颜色对这些进行分组,以便工作人员看到。

我试图创建一些代码([[Topic B, Topic C, Topic E],[Topic A, Topic D]])来减少基于互斥对的数组,但这是完全错误的。例如:该代码输出:

main2()

...这是不正确的,因为A和B显然有冲突。 (A和C,B和E当然会连续罚款)。


我的当前代码

[["Topic A","Topic C","Topic B","Topic E"],"Topic D"]


最后

我正在阅读的有关理解的一些途径如下。我的代表太低了,无法链接这些。有人可能会认为这是一个追求的方向吗?


感谢您花费任何时间。


更新1

对于以下内容,解决方案应正确将会议顺序设置为:

1)W,X,Z
2)V,Y

function onOpen () {
 var ui = SpreadsheetApp.getUi();
  ui.createMenu('Meeting Tools')
  .addItem('Compute Optimal Meeting Order', 'main')
  .addToUi();
}

function main() {
  var ss = SpreadsheetApp.getActive();
  var sss = ss.getActiveSheet();
  //var sss = ss.getSheetByName("Sheet3");
  var data = sss.getDataRange().getValues();

  var ui = SpreadsheetApp.getUi();

  var meetings = {};
  var pairs = [];
  var objKey;

  // Structuring data to obj
  for (var x=0; x < sss.getLastColumn(); x++) {
    for (var y=1; y < data.length; y++) {
      if (y==1) {
        objKey = data[y][x];
        meetings[objKey] = [];
      }
      else if (data[y][x]) {
        meetings[objKey].push(data[y][x]);
      }
    }
  }

  var keys = Object.keys(meetings).sort();
  var loc;

  // Starting with "A"
  for (var m in meetings) {
    if (!meetings.hasOwnProperty(m)) continue;

    Logger.log("-----------------------------");
    Logger.log("SCANNING:  " + m);
    Logger.log("-----------------------------");

    loc = keys.indexOf(m);

    // check A, B, C, D, E
    for (var m2 = 0; m2 < keys.length; m2++) {

      var pointer = (m2 + loc) % keys.length;      
      // DO NOT CHECK SAME MEETING
      if (keys[pointer] == m) {
    Logger.log("||||||||| WE ARE COMPARING OURSELVES: (" + keys[pointer] + "/"     + m + "). SKIPPING!");
        continue;
      }

      // FOR EACH PARTICIPANT OF CURRENT MEETING
      for (var p = 0; p < meetings[m].length; p++) {

        Logger.log("");
        Logger.log("  >>> COMPARING " + keys[pointer]);
        Logger.log("  >>> >>> " + meetings[m][p] + " " + (p+1) +"/"+meetings[m].    length);

          // SEEK THE LOCATION OF THE MEETING PARTICIPANT IN THE NEXT MEETING
          var testIndex = meetings[keys[pointer]].indexOf(meetings[m][p]);

          // IF THE MEETING PARTICIPANT IS ALSO IN THE NEXT MEETING
          if (testIndex > -1) {

        Logger.log("We have a match! Meeting "+ m + " has participant " + meetings[m][p] + " which unfortunately clashes with meeting " + keys[    pointer]);
        Logger.log("Therefore Meeting " + m + " cannot run at the same time as     " + keys[pointer]);
        Logger.log("We need to bail out of compairing against: " + keys[pointer    ]);

            // WE BAIL OUT AS WE HAVE A CLASH
            break; 
          }
      // IF THE MEETING PARTICIPANT IS NOT IN THE NEXT MEETING AND WE HAVE     CHECKED EVERYONE. WE SHOULD BE GOOD.
          else if (testIndex == -1 && p+1 == meetings[m].length) {
            Logger.log("This looks to be clear!!! Adding to pair group.");
            pairs.push([m,keys[pointer]]);
          }
      }//loop
    } //loop
  } //obj_loop

  // Logger.log("FINAL TALLY: " + JSON.stringify(pairs));
     Logger.log("FINAL TALLY (CLEANED): " + JSON.stringify(removeRepeats(pairs.sort())    ));

  // ui.alert(JSON.stringify(removeRepeats(pairs.sort())));
  // ss.toast(JSON.stringify(removeRepeats(pairs.sort())));

  // debugger;
  return removeRepeats(pairs.sort());
}


function main2(array) {

  // DEBUG
  //  array = [["A","C"],["A","D"],["B","C"],["B","E"],["C","E"]];
  //  array = [["A","C"],["A","D"],["B","C"],["B","E"],["C","E"]];
  array = main();

  var holdingArr = [];
  for (var i = array.length - 1; i >= 1; i--) {

    //DEBUG
    //var pair = ["Z","X"];
    var pair = array[i];

    if (arrayPairCheck([array[0]], pair)) {
      holdingArr.push(pair);
      array.pop(i);
    } 
    else {
      array[0] = array[0].concat(array[i]);
      array.pop(i);
    }
  } //loop

  if (holdingArr && holdingArr.length > 0) {
    for (var j=0; j < holdingArr.length; j++) {
      var checkIndex = holdingMeetingExistsInPair(array[0],holdingArr[j]);
      if (checkIndex !== false) {
        array.push(holdingArr[j][checkIndex]);
      }
    } //loop
  }

  // DEBUG
  debugger;
  Logger.log(JSON.stringify(array));
}


function holdingMeetingExistsInPair(arrayFirstIndexGroup,holdingArrPair) {
  if (arrayFirstIndexGroup && arrayFirstIndexGroup.length > 0) {
    if (arrayFirstIndexGroup.indexOf(holdingArrPair[0]) === -1) {
      return 0;
    }
    if (arrayFirstIndexGroup.indexOf(holdingArrPair[1]) === -1) {
      return 1;
    }
  }
  return false;
}


function arrayPairCheck(array,pair) {

  // DEBUG
  // array = [["A","C"],["A","D"],["B","C"],["B","E"],["C","E"]];
  // pair = ["Z","X"];

  var seen = false;

  if (array && array.length > 0) {
    for (var i=0; i < array.length; i++) {
      array[i].map(function(item,item2) {
        if (item === pair[0] || item === pair[1]) {
          seen = true;
          return true;
        }
      });
    } //loop
  }

  if (seen) { return true; } else { return false; }
}

// http://stackoverflow.com/questions/27734661/javascript-arrays-ab-ba
function removeRepeats(list) {
    var i;
    var b = [];
    var _c = [];

    for (i = 0; i < list.length; i++) {
        var a = list[i].sort();
        var stra = a.join("-");

        if(_c.indexOf(stra) === -1) {
            b.push(a);
            _c.push(stra);
        }
    }

    return b;
}

1 个答案:

答案 0 :(得分:1)

这肯定是一个有趣的问题。我的方法是使用矩阵运算来确定哪些主题不会发生冲突。 第一步是创建一个矩阵来表示没有碰撞的对,类似于你的方法。

因此对于此示例数据集

+-----------------------------------------------------------+
|  Meetings                                                 |
+-----------------------------------------------------------+
|  Topic A  |  Topic B  |  Topic C  |  Topic D  |  Topic E  |
+-----------------------------------------------------------+
|  Billy    |   JD      |  David    |   Tania   |   JB      |
|  Mel      |   Rowan   |  Emily    |   David   |   Fred    |
|  Tracey   |   Mike    |           |   Mike    |   Tania   |
|  JB       |   Aaron   |           |   Fred    |           |
|  Luke     |   Billy   |           |           |           |
|  Aaron    |           |           |           |           |
|  Michael  |           |           |           |           |
+-----------------------------------------------------------+

我们可以说主题A没有与主题C和主题D发生冲突,我们可以在矩阵/数组中表示它,如下所示:

+---------------------------------------------------------------+
        |Topic A|Topic B |Topic C|Topic D|Topic E|
Topic A |1      |0       |1      |1      |0      |

然而,主题C和主题D确实发生了冲突,我们将在一分钟内完成。 同样,我们填写剩余的矩阵,如此

Unique Pair Matrix
        TopicA TopicB TopicC TopicD TopicE
TopicA  1      0      1      1      0   
TopicB  0      1      1      0      1   
TopicC  1      1      1      0      1   
TopicD  1      0      0      1      0   
TopicE  0      1      1      0      1   

正如您可以从此表主题中注意到的,C行的0对应于主题D列,表示冲突。另外,请注意对角线设置为1,因为相同的会议不能相互冲突(对于下一步,我们需要它为1)。

接下来,只需将矩阵相乘,得到一个如下所示的矩阵。 理想情况下,您将转置矩阵然后相乘,在这种情况下,您不需要(方阵)

Number of Unique Pair Matrix
        Topic A Topic B Topic C Topic D Topic E
Topic A 3       1       2       2       1
Topic B 1       3       3       0       3
Topic C 2       3       4       1       3
Topic D 2       0       1       2       0
Topic E 1       3       3       0       3

此矩阵表示会议不会彼此冲突的次数。因此,对于会议A和C,他们不会发生冲突两次,一次是从第1行计算而另一次是第3行。同样,对于主题&#34; D&#34;和&#34; A&#34;,我们得到一个值2.但是A的总唯一对(用对角线值表示)是3,这意味着会议C和D有冲突,因为它们只与A形成两次唯一对

在第2行中,您会注意到,主题B,C,E共有3对独特的对,这代表了可以一起举行的会议次数最多以及可以举行哪些会议。

使用此功能,您可以优先处理最多会议次数,然后再进行剩余会议。 以下代码就是这样:

function onOpen () {
 var ui = SpreadsheetApp.getUi();
  ui.createMenu('Meeting Tools')
  .addItem('Compute Optimal Meeting Order', 'Jmain')
  .addToUi();
}
function Jmain() {
  var ss = SpreadsheetApp.getActive();
  var sss = ss.getActiveSheet();
  var sss = ss.getSheetByName("Sheet2");
  var data = sss.getRange(2,1,sss.getLastRow()-1,sss.getLastColumn()).getValues();
 //This the matrix that will hold data for unique pairs
  var shadowRel = []
  var headers = data[0].slice()
  data = transpose(data)
  var shadowRel = []
  for (var i = 0 ; i < data.length ; i ++){
    shadowRel[i] = headers.slice()                                            //Each Row is set Headers values to begin with
    for (var j = 1 ; j < data[i].length ; j++){
      var found = false
     for(var k = 0 ; k < data.length ; k++){

       if(k != i) {
         if (data[k].indexOf(data[i][j]) != -1 && data[i][j] != ""){
             //
            shadowRel[i][k] = 0                                                   // Whenver there is a clash the martix is set to zero
            found = true
         }  
       }
     }
    }
  }

 shadowRel = mMutliply (shadowRel)              //Matrix Multiplication
 sss = ss.getSheetByName("Sheet3")                
 sss.getRange(2,2,shadowRel.length,shadowRel[0].length).setValues(shadowRel)        //Set the values of multiplied martix in sheet 3
 var ui = SpreadsheetApp.getUi()

 var meetingOrder = mostNoMeetings(shadowRel,headers)
 Logger.log("Meeting Order: ")
 Logger.log(meetingOrder)
 var prompt = "Meeting Order: "
 for (i = 0 ;i <meetingOrder.length; i++){
 prompt += "\n"+(i+1)+") "+ meetingOrder[i]
 }
 ui.alert(prompt)
}

// Transpose the data so as to check match in each row. 
function transpose(arr) {
        return Object.keys(arr[0]).map(function (c) {
            return arr.map(function (r) {
                return r[c];
            });
        });
    }

function mMutliply (arr){                           //Number of row and column needs to be equal
  var mMutRes = []
  for (var i = 0; i < arr.length ; i++)
    for(var j = 0; j<arr[0].length ; j++){
      if(arr[i][j] !== 0)
         arr[i][j] = 1                    // Remaining Text is converted to 1, has no class

    }
  var ss = SpreadsheetApp.getActive()
  var sss = ss.getSheetByName("Sheet3")
  sss.getRange(arr.length+5,2,arr.length,arr[0].length).setValues(arr)  //Set Value of unique pair to sheet 3
  for (var i = 0; i < arr.length ; i++){
     mMutRes[i] = []
    for(var j = 0; j<arr[0].length ; j++){
      var sumProd = 0
      for(var k = 0 ; k < arr.length ; k ++)
      {
        sumProd += (arr[k][j] * arr[i][k])

      }
      mMutRes[i][j] = sumProd

    }
  }
  return mMutRes 
}

function mostNoMeetings(shadowRel,headers){
  var UsedIndex = []
  var MaxColIndex = []
  var counter = 0
  var MeetingGroups = []
  for(var maxNo = shadowRel.length ; maxNo > 0 ; maxNo--) {        //Look for most repeated pair, max possible is the numbers of topics/meetings at same time
  var maxFound = false
  for (var i = 0 ; i < shadowRel.length; i++){
    for(var j= 0; j< shadowRel[0].length; j++){
      if(i != j && UsedIndex.indexOf(i) == -1 && UsedIndex.indexOf(j) == -1)   // Eliminate row / column corresponding to topics/Meetings already scheduled. 
        if(shadowRel[i][j] == maxNo){
          MaxColIndex[counter] = j
          counter++
          maxFound = true

        }
    }
    if(maxFound){
    var temp =  []
    temp.push(headers[i])
    for (var k in MaxColIndex){
      if(temp.length < maxNo)
       temp = temp.concat(headers[MaxColIndex[k]])
      else
       MaxColIndex.splice(k)
    }
    MaxColIndex[counter] = i
    MeetingGroups.push(temp)
    UsedIndex = UsedIndex.concat(MaxColIndex)
    //Logger.log(UsedIndex)
    MaxColIndex = []
    counter = 0
    maxFound = false
    }
   }
  }
  // Incase any are remaining meetings that didnt form pairs which have to be held independently
  for ( i = 0 ; i < shadowRel.length; i++){
    if(i != j && UsedIndex.indexOf(i) == -1 && UsedIndex.indexOf(j) == -1){
     MeetingGroups.push(headers[i]) 
    }
  }

  return MeetingGroups
}

最后,确保您的数据在表2中,而表3为空,上述矩阵的输出和乘法矩阵将发布到表3,以便直观地显示发生的情况。

希望这有帮助!