【问题标题】:Members of two groups of different size must meet each other (1v1, once)两组不同大小的成员必须互相见面(1v1,一次)
【发布时间】:2016-08-28 07:50:03
【问题描述】:

我最近一直在努力编写一种“快速约会风格”算法。基本目标是让一个组(男性)的每个成员在他们的餐桌上与另一组(女性)的每个成员会面一次。

条件是:

  1. 餐桌的数量等于女性的数量。
  2. 每个男人都被分配到一个女人坐的桌子上(1v1 对话)。
  3. 在下一轮中,每个人都被切换到他从未去过的另一张桌子上。
  4. 如果组的大小不同,则任何成员(男性或女性)都不得连续两轮暂停(缺少伙伴)。

当男性组的成员多于女性组时,就会出现困难,反之亦然。

例子:

var men = [
      'm1', 'm2', 'm3', 'm4', 'm5',
   ],

   women = [
      'w1', 'w2', 'w3'
   ];


┃ ROUND 1                       ┃ ROUND 2
┌─────────────┬─────────────┐   ┌─────────────┬─────────────┐
│     men     │    women    │   │     men     │    women    │
├─────────────┴─────────────┤   ├─────────────┴─────────────┤
│      m1    >>>    w1      │   │      m4    >>>    w1      │
│      m2    >>>    w2      │   │      m5    >>>    w2      │
│      m3    >>>    w3      │   │      m1    >>>    w3      │
│      m4          pause    │   │      m2          pause    │
│      m5          pause    │   │      m3          pause    │
└───────────────────────────┘   └───────────────────────────┘

┃ ROUND 3
┌─────────────┬─────────────┐
│     men     │    women    │
├─────────────┴─────────────┤
│      m2    >>>    w1      │
│      m3    >>>    w2      │
│      m4    >>>    w3      │
│      m5          pause    │
│      m1          pause    │
└───────────────────────────┘

因此,比赛是:

var results = [
    'm1' = [
        'w1', 'w3', null
    ],

    'm2' = [
        'w2', null, 'w1'
    ],

    'm3' = [
        'w3', null, 'w2'
    ],

    'm4' = [
        null, 'w1', 'w3'
    ],

    'm5' = [
        null, 'w2', null
    ],
];

到目前为止,尽管在本网站和其他网站上查看了类似的问题(参见下面的屏幕截图),但我尝试解决此问题的尝试均未成功。在这次尝试中,我跑了 15/15 轮,结果仍然有人暂停 2 轮或更多轮,等等。

* 截图中的名字是假的;由Faker生成


我当前(非工作)在 Javascript 中的尝试

我基本上有四个数组

  1. 一群人
  2. 女性群体
  3. 本轮可用表格的数组
  4. 每个男人的未满足女性的数组

var men_array                     = [
  'm1', 'm2', 'm3', 'm4', 'm5', 'm6', 'm7', 'm8', 'm9', 'm10'
];

var women_array                   = [
  'w1', 'w2', 'w3', 'w4', 'w5'
];

var available_tables_this_round   = [];
var unmet_women                   = [];

// Run round
function start_round(){
	console.log('START OF ROUND ----------------');

    // Set available tables this round
    // One table per woman
    available_tables_this_round = women_array;
  
    // Selected table
    var selected_table;
  
    // Finding table partner for each man
    men_array.forEach(function(item){
        var current_man = item;
        
        // Checking if this user has unmet women on record
        if(typeof unmet_women[current_man] == 'undefined'){
            // Unmet women array not set. Setting...
            unmet_women[current_man] = women_array;
        }
      
        // Loop through available tables to see which
        // of those the man can join (has not visited yet).
        for(var i = 0; i < available_tables_this_round.length; i++){
						var current_woman = available_tables_this_round[i];
            
            // If woman among the available has not been met
            if($.inArray(current_woman, available_tables_this_round) !== -1){
              	selected_table = current_woman;
                
                 // Removing table from those available this round
                 available_tables_this_round = $.grep(available_tables_this_round, function(value) {
                  return value != selected_table;  
                });
              
                // Removing woman from unmet by this man
                unmet_women[current_man] = $.grep(unmet_women[current_man], function(value) {
                  return value != selected_table;   
                });
                
                console.log(current_man, '>>>', selected_table);
              
              	// Exiting loop since we've found a match for the man (for this round!)
                break;
            }
        }        
    });
    
    console.log('END OF ROUND ----------------');
  
    
}

// Button handling
$(document).on('click', 'button#start_round_btn', start_round);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="start_round_btn">Start Round</button>

【问题讨论】:

  • 如果您添加一些代码以查看您当前的算法实现实际做了什么,这将很有用。
  • 感谢您的反馈;我会继续这样做:)
  • 添加了一些代码,可以让我了解我尝试解决这个问题的想法。非常感谢任何输入!

标签: javascript jquery algorithm


【解决方案1】:

这里的主要问题是确保男性和女性都不会连续两次暂停。

为确保不会发生这种情况,请考虑在女士们的桌子之间放置“暂停桌”,直到您有足够的桌子让男士坐下。这意味着在从 0 开始的表编号中,这些“暂停表”获得奇数位置索引。

当你让男人们从一张桌子循环到另一张桌子时,永远不会有一个男人连续两次被“暂停”,但他们会在每个女人回到他们第一个桌子之前遇到每个女人分配给。

使用这种方法也很明显,当男性人数是女性的两倍以上时,问题就无法解决。在那种情况下,一个人不可避免地会被依次暂停两次。

当男性人数较少时,可以使用类似的技巧:女性可能永远不会单独进行超过一轮,因此在这种情况下,使用相同的方法引入虚拟男性(“暂停座位”):让男性参与一行,并在这一行中注入这些“假人”,使他们最终以奇怪的索引结束。我将这排潜在的延长排称为“座位”。

如果您执行上述操作,您将拥有与桌子一样多的座位,并且为每一轮生成分配变得微不足道:只需在桌子上循环座位:每一轮座位移动到下一张桌子顺序,在最后一个表之后循环回到第一个表。

代码如下:

function getRounds(men, women) {
    // Exclude the cases where a solution is not possible
    if (men.length > women.length * 2) throw "not possible: too many men"; 
    if (women.length > men.length * 2) throw "not possible: too many women"; 
    // Create the tables:
    // Women get a fixed place, so the table name is the woman's name
    var tables = women.slice();
    // Create the seaters: they are the men
    var seaters = men.slice();
    // Which do we have fewer of? tables or seaters?
    var maxLen = Math.max(tables.length, seaters.length);
    var least = tables.length < maxLen ? tables : seaters;
    // The smallest of both arrays should get some dummies added to it.
    // We insert the dummies at odd indices: This is the main point of this algorithm
    for (var i = 0; least.length < maxLen; i++) least.splice(i*2+1, 0, 'pause');

    // Now everything is ready to produce the rounds. There are just as many
    // rounds as there are tables (including the "pause tables"):
    return tables.map( (_, round) =>    
        // Assign the men: each round shifted one place to the left (cycling):
        tables.map( (table, tid) => [seaters[(round+tid)%maxLen], table] )
    );
}

var result1 = getRounds(["John", "Tim", "Bob", "Alan", "Fred"], 
                        ["Lucy", "Sarah", "Helen"]);
console.log(result1);

var result2 = getRounds(["John", "Tim"],
                        ["Lucy", "Sarah", "Helen", "Joy"]);
console.log(result2);

【讨论】:

  • 感谢cmets的详细解释。它确实消除了问题的复杂性!
【解决方案2】:

您可以先暂停填充数组并保持数组的相同长度。然后,您可以使用数组的叉积与第一个数组的移位并保持第二个数组的值。

function getSeating(a1, a2) {
    function fill(a, l) {
        var p = l - a.length;
        a = a.slice();
        while (p) {
            a.splice(p, 0, 'pause' + p);
            p--;
        }
        return a;
    }

    var max = Math.max(a2.length, a1.length);

    a1 = fill(a1, max);
    a2 = fill(a2, max);
    return a1.map(function (_, i) {
        return a2.map(function (b, j) {
            return [a1[(i + j) % max], b];
        });
    });
}

console.log(getSeating(['m1', 'm2', 'm3', 'm4', 'm5'], ['w1', 'w2', 'w3']));
console.log(getSeating(['m1', 'm2', 'm3'], ['w1', 'w2', 'w3', 'w4', 'w5']));
console.log(getSeating(['m1', 'm2', 'm3', 'm4', 'm5'], ['w1', 'w2', 'w3', 'w4', 'w5']));

console.log(getSeating(['m1', 'm2', 'm3', 'm4'], ['w1', 'w2']));
console.log(getSeating(['m1', 'm2'], ['w1', 'w2', 'w3', 'w4']));
console.log(getSeating(['m1', 'm2', 'm3', 'm4'], ['w1', 'w2', 'w3', 'w4']));
.as-console-wrapper { max-height: 100% !important; top: 0; }

【讨论】:

  • 这是一个非常聪明的方法!经过一些测试,虽然我让一些人暂停了几轮。这是我用 10 名男性和 13 名女性测试的小提琴:jsfiddle.net/fhodge/v2e28tgn 例如,“w11”会暂停 3 轮左右。我正在寻找解决此问题的方法,但您肯定已经为我指明了正确的方向。谢谢!
  • 我改变了暂停位置的分布。
【解决方案3】:

你可以像这样创建一个数组表:

var tables = men.map((element, index) => index < women.length ? [element, women[index]] : [element, "pause"]);

获得:

[ [ 'm1', 'w1' ],
  [ 'm2', 'w2' ],
  [ 'm3', 'w3' ],
  [ 'm4', 'pause' ],
  [ 'm5', 'pause' ] 
]

因此,您只需要为“nouvelles rencontres”重新组合情侣

【讨论】:

  • 但这是生成一轮的代码。问题的核心实际上是如何生成 所有 轮而不分配任何人在连续两轮中“暂停”。
【解决方案4】:

好的,我喜欢这个问题。我的方法将处理到一个组的大小是另一个组的两倍。没有人会连续等待两次。我也很高兴有机会使用我的Array.prototype.rotate() 方法。

好吧,代码是;

Array.prototype.rotate = function(n){
	var len = this.length,
	    res = new Array(this.length);
	if (n % len === 0) return this.slice(); // no need to rotate just return a copy
	else for (var i = 0; i < len; i++) res[i] = this[(i + (len + n % len)) % len];
	return res;
};

function arrangeTables(matches){
  var meetings = [],
    tableCount = matches[0].length;
  for (var i = 0, len = matches.length; i < len; i++){
    meetings.push(matches.slice(0,tableCount).map((t,ix) => t[ix]));
    matches = matches.concat(matches.splice(0,tableCount).rotate(1));
  }
  return meetings;
}

var men = ['m1', 'm2', 'm3', 'm4', 'm5'],
  women = ['w1', 'w2', 'w3'],
    few = [],
   much = men.length > women.length ? (few = women,men) : (few = men,women), // this is needed for the nesting order of maps to get matches.
matches = much.map(m => few.map(f => [f,m]));
 result = arrangeTables(matches);
console.log(result);

好吧,我们在每一轮都有很多表中的性别,我们必须进行与超出的性别数量一样多的轮次。

首先我们找到所有可能的匹配项

matches = much.map(m => few.map(f => [f,m]))

返回我们

[ [ [ 'w1', 'm1' ], [ 'w2', 'm1' ], [ 'w3', 'm1' ] ],
  [ [ 'w1', 'm2' ], [ 'w2', 'm2' ], [ 'w3', 'm2' ] ],
  [ [ 'w1', 'm3' ], [ 'w2', 'm3' ], [ 'w3', 'm3' ] ],
  [ [ 'w1', 'm4' ], [ 'w2', 'm4' ], [ 'w3', 'm4' ] ],
  [ [ 'w1', 'm5' ], [ 'w2', 'm5' ], [ 'w3', 'm5' ] ] ]

然后我们转到arrangeTables() 函数。它是这样工作的

  1. 在第一个表中取多行(在本例中为 3 行)并选择 3 个会议 根据行索引并将它们放入数组中。

    meetings.push(matches.slice(0,tableCount).map((t,ix) =&gt; t[ix]));

    所以[["w1","m1"],["w2","m2"],["w3","m3"]] 是第一个被选中的 圆。

  2. 删除matches 的前三行。旋转删除的行 一次并将它们连接回matches。

    matches = matches.concat(matches.splice(0,tableCount).rotate(1));

    所以第二轮比赛会变成这样。

    [ [ [ 'w1', 'm4' ], [ 'w2', 'm4' ], [ 'w3', 'm4' ] ],
      [ [ 'w1', 'm5' ], [ 'w2', 'm5' ], [ 'w3', 'm5' ] ],
      [ [ 'w1', 'm2' ], [ 'w2', 'm2' ], [ 'w3', 'm2' ] ],
      [ [ 'w1', 'm3' ], [ 'w2', 'm3' ], [ 'w3', 'm3' ] ],
      [ [ 'w1', 'm1' ], [ 'w2', 'm1' ], [ 'w3', 'm1' ] ] ]
    
  3. 并多次重复此例程“性别数量过多”。 (本例为 5 个)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-23
    • 2017-05-30
    • 1970-01-01
    • 1970-01-01
    • 2017-09-20
    • 2018-04-11
    相关资源
    最近更新 更多