有许多元素(表示为复选框)具有一些不同的关系。例如:
编辑:答案中出现了3个问题,我想在此处定义:
问:什么是默认,禁止或需要?答:没有。如果 两个元素之间没有关系,它们可以独立行动 (只要与一个共同的元素没有关系就会说 否则)。
问:如果A禁止B,那么B会自动禁止A吗?答:是的。 猫(A)说,你不能用狗(B)。即使是狗 不关心猫,你不能把它们结合起来,因为猫 不喜欢它。
问:如果A需要B,那么B会自动需要A吗?答:不。如果你想 要读取stackoverflow(A),您需要浏览器(B)。但如果你想 要使用浏览器(B),您不需要stackoverflow(A)。
编辑:我想举一个更简单的例子。比方说,您可以使用复选框配置汽车。有一些规则。例如,如果选择黑色涂料,则不能选择白色内饰颜色(禁止)。如果您选择真皮座椅,您只能将座椅加热(需要)和皮革方向盘(需要)结合使用,但不能将其与电动座椅调节(禁止)结合使用。白色内饰(禁止)不允许座椅加热,而白色屋顶需要白色内饰。因此,即使没有定义,也不能有座椅加热的白色屋顶(由于与普通元件的关系而被禁止)。
因此,如果有人激活复选框A,则需要激活复选框B和C,同时需要禁用复选框D.由于A需要B和B不能与E组合,因此复选框E也需要被禁用。由于C需要F,因此需要激活F.由于F不能与G组合,因此G也需要停用。
反之亦然:如果有人激活E,那么B需要被停用,因为B不能与E组合。但D不需要激活,因为D需要E而E不需要需要D必然。
现在的重大问题是:
问题是递归。每一个行动都会导致更多的行为导致(可能)更多的行动。
以下逻辑应适用于该示例,“A”已激活:
关系的当前定义(可以更改):
var relations = {
'A': {
'B': 'needed',
'C': 'needed',
'D': 'prohibited'
},
'B': {
'E': 'prohibited'
},
'D': {
'E': 'needed'
},
'C': {
'F': 'needed'
},
'F': {
'G': 'prohibited'
},
'S': {
'B': 'prohibited'
},
'T': {
'D': 'prohibited'
},
'U': {
'S': 'needed'
}
}
目前的理论方法:
假设点击“A”:
foreach (relations['A'] as related, relation) {
if (relation === 'needed') {
// take action
activateRelated(related);
} else if (relation === 'prohibited') {
// take action
disableRelated(related);
}
}
但这只是第一次迭代。从理论上讲,这可能是一个在每个动作采取后递归调用自身的函数。但是,让我们说300个元素有很多关系,它循环无限。 好吧,如果采取一个动作,一个复选框被激活,它可以正常工作。在一个更现实的场景中,有30%到50%的复选框处于活动状态,关系检查需要全面上下。
第二个问题是:如果用户再次禁用复选框A,则需要再次检查所有关系 - 对于所有仍处于活动状态的复选框。
答案 0 :(得分:1)
编辑要求得到了改进
一个简单的递归不会做
var relations = {
'A': {
'B': 'needed',
'C': 'needed',
'D': 'prohibited'
},
'B': {
'E': 'prohibited'
},
'D': {
'E': 'needed'
},
'E':{/* added for simplicity */},
'C': {
'F': 'needed'
},
'F': {
'G': 'prohibited'
}
};
var tmp = {};
function checkRelations(start) {
for (var relation in relations[start]) {
if (!tmp.hasOwnProperty(relation)) {
tmp[relation] = {};
}
if (relations[start][relation] === 'needed') {
tmp[relation][start] = 'needed';
} else if (relations[start][relation] === 'prohibited') {
tmp[relation][start] = 'prohibited';
}
checkRelations(relation);
}
}
function run(obj) {
for (var e in obj) {
checkRelations(e);
}
}
run(relations);
JSON.stringify(tmp);
会得到这个结果:
{
'B': {
'A': 'needed',
'S': 'prohibited'
},
'E': {
'B': 'prohibited',
'D': 'needed'
},
'C': {
'A': 'needed'
},
'F': {
'C': 'needed'
},
'G': {
'F': 'prohibited'
},
'D': {
'A': 'prohibited',
'T': 'prohibited'
},
'S': {
'U': 'needed'
}
}
从第一个条目B
可以看出:您的数据库定义不明确。每个未定义的元素会发生什么?如果没有定义某些内容,是prohibited
还是“需要”,默认值是多少?如果'A'
需要'B'
,那意味着'B'
需要'A'
?
一旦你定义了你可以填充数据库的第一层(自动)并构建一个树(自动dito),如果你想要并安全地进行大量处理(O(n ^ 2))大量的记忆(O(n ^ 2))。
所有这一切都假设整个事物是一致的,并且在任何地方都没有无限循环!
默认值设置为'meh'
,第一轮是
function checkRelations(start) {
for (var relation in relations[start]) {
if (!relations.hasOwnProperty(relation)) {
relations[relation] = {
};
}
if (relations[start][relation] === 'needed') {
relations[relation][start] = 'meh';
} else if (relations[start][relation] === 'prohibited') {
relations[relation][start] = 'prohibited';
}
for (var r in relations) {
if (!relations[relation].hasOwnProperty(r) && relation != r) {
relations[relation][r] = 'meh';
}
}
}
}
function run(obj) {
for (var e in obj) {
// fill database up
checkRelations(e);
}
}
run(relations);
JSON.stringify(relations)
{
'A': {
'B': 'needed',
'C': 'needed',
'D': 'prohibited',
'E': 'meh',
'F': 'meh',
'S': 'meh',
'T': 'meh',
'U': 'meh',
'G': 'meh'
},
'B': {
'E': 'prohibited',
'A': 'meh',
'D': 'meh',
'C': 'meh',
'F': 'meh',
'S': 'prohibited',
'T': 'meh',
'U': 'meh',
'G': 'meh'
},
'E': {
'B': 'prohibited',
'A': 'meh',
'D': 'meh',
'C': 'meh',
'F': 'meh',
'S': 'meh',
'T': 'meh',
'U': 'meh',
'G': 'meh'
},
'D': {
'E': 'needed',
'A': 'prohibited',
'B': 'meh',
'C': 'meh',
'F': 'meh',
'S': 'meh',
'T': 'prohibited',
'U': 'meh',
'G': 'meh'
},
'C': {
'F': 'needed',
'A': 'meh',
'B': 'meh',
'E': 'meh',
'D': 'meh',
'S': 'meh',
'T': 'meh',
'U': 'meh',
'G': 'meh'
},
'F': {
'G': 'prohibited',
'A': 'meh',
'B': 'meh',
'E': 'meh',
'D': 'meh',
'C': 'meh',
'S': 'meh',
'T': 'meh',
'U': 'meh'
},
'S': {
'B': 'prohibited',
'A': 'meh',
'E': 'meh',
'D': 'meh',
'C': 'meh',
'F': 'meh',
'T': 'meh',
'U': 'meh',
'G': 'meh'
},
'T': {
'D': 'prohibited',
'A': 'meh',
'B': 'meh',
'E': 'meh',
'C': 'meh',
'F': 'meh',
'S': 'meh',
'U': 'meh',
'G': 'meh'
},
'U': {
'S': 'needed',
'A': 'meh',
'B': 'meh',
'E': 'meh',
'D': 'meh',
'C': 'meh',
'F': 'meh',
'T': 'meh',
'G': 'meh'
},
'G': {
'F': 'prohibited',
'A': 'meh',
'B': 'meh',
'E': 'meh',
'D': 'meh',
'C': 'meh',
'S': 'meh',
'T': 'meh',
'U': 'meh'
}
}
那已经很贵了。您可以展开树,但我会停在此处,按照路径'needed'
即时构建树。您应该能够使用第一个递归方法来执行此操作,如果您发现“needed
”,则默认值为'prohibited'
。
示例:
A - B(n) - C(n) - D(p)
| |
E(p) F(n)
|| |
S(p) G(p)
(一个酒吧是一个分支,两个酒吧在一片叶子里)
当然,根据默认值处理'meh'
部分。如果默认值为'meh'
,您甚至可以完全跳过'prohibited'
条目的构建,只留下'needed'
个条目。
剩下的就是
{
'A': {
'B': 'needed',
'C': 'needed'
},
'B': {
'nothing':0
},
'E': {
'nothing':0
},
'D': {
'E': 'needed'
},
'C': {
'F': 'needed'
},
'F': {
'nothing':0
},
'S': {
'nothing':0
},
'T': {
'nothing':0
},
'U': {
'S': 'needed'
},
'G': {
'nothing':0
}
}
并减少到最低限度:
{
'A': {
'B': 'needed',
'C': 'needed'
}
'D': {
'E': 'needed'
},
'C': {
'F': 'needed'
}
'U': {
'S': 'needed'
}
}
完整算法:如果需要,将所有条目设置为'prohibited'
,并遍历上面列出的由以下小脚本创建的小数据库
function checkRelations(start) {
for (var relation in relations[start]) {
if (relations[start][relation] === 'prohibited') {
delete relations[start][relation];
}
}
}
function isEmpty(obj) {
for(var p in obj) {
if(obj.hasOwnProperty(p)){
return false;
}
}
return true;
}
function run(obj) {
for (var e in obj) {
// fill database up
checkRelations(e);
// delete empty entries
if(isEmpty(relations[e])){
delete relations[e];
}
}
}
遍历最后一个的函数:
function followPath(start){
for (var relation in reduced[start]) {
console.log(relation + " is needed")
if (reduced.hasOwnProperty(relation)) {
console.log( relation + " is needed, follow path")
followPath(relation);
}
}
}
啊,迟到了。再次; - )但至少它比我想到的更简单(并且更快,如果我正确计算循环)。
答案 1 :(得分:1)
您作为示例给出的关系类型可以表示为带有标记边的有向图:
在上图中,边缘的颜色代表边缘的标签或标签:
绿色:需要
红色:禁止
边缘上的点代表关系的方向。
现在您知道可以使用图论算法来遍历/搜索数据了。见:
Depth-first search
Breadth-first search
在javascript中我会存储这样的数据:
_taskFactory
由您决定顶点或节点的外观。它可以是一个字符串,或者在你的情况下,它可以是一个html复选框元素,甚至是一个包含两者的对象:
var graph = { v: [], e: [] } // v: vertices or nodes, e: edges
有向图中的边缘如下所示:
var vertex = {
name: "A",
el : someElement
};
JS小提琴:SO33673055 我还不确定这是你需要的。从用户更改的复选框开始,所有规则仅检查一次。
答案 2 :(得分:1)
也许这有帮助。如果使用表单下面的提示。
编辑:现在简单检查循环引用。首先取消选择相互影响的选定项目。
编辑2:现在禁用/启用复选框。
var relation = [
{ name: 'A', needed: ['B', 'C'], prohibited: ['D'] },
{ name: 'B', needed: [], prohibited: ['E'] },
{ name: 'C', needed: ['F'], prohibited: [] },
{ name: 'D', needed: ['E'], prohibited: [] },
{ name: 'E', needed: [], prohibited: [] },
{ name: 'F', needed: [], prohibited: ['G'] },
{ name: 'G', needed: [], prohibited: [] },
{ name: 'S', needed: [], prohibited: ['B'] },
{ name: 'T', needed: [], prohibited: ['D'] },
{ name: 'U', needed: ['S'], prohibited: [] }
];
void function () {
var div = document.createElement('div'),
form = document.createElement('form'),
loop;
div.id = 'out';
form.name = 'boxes';
relation.forEach(function (a) {
var br = document.createElement('br'),
input = document.createElement('input'),
label = document.createElement('label');
input.type = 'checkbox';
input.name = a.name;
input.addEventListener('change', check);
label.textContent = a.name;
label.for = a.name;
label.appendChild(input);
label.appendChild(document.createTextNode((a.needed.length ? ' needed: ' + a.needed.join(', ') : '') + (a.prohibited.length ? ' prohibited: ' + a.prohibited.join(', ') : '')));
form.appendChild(label);
form.appendChild(br);
});
form.appendChild(div);
document.body.appendChild(form);
do {
loop = false;
relation.forEach(function (a) {
a.needed.forEach(function (aa) {
relation.forEach(function (b) {
b.prohibited.forEach(function (bb) {
if (aa === bb) {
if (!~a.prohibited.indexOf(b.name)) {
a.prohibited.push(b.name);
loop = true;
}
if (!~b.prohibited.indexOf(a.name)) {
b.prohibited.push(a.name);
loop = true;
}
}
});
});
});
});
} while (loop);
}();
function check() {
function getBox(l) { return document.boxes[l].checked; }
function setBox(l, v) { return document.boxes[l].checked = v; }
function setBoxDisabled(l, v) { return document.boxes[l].disabled = v; }
var disabled, msg, loop;
do {
disabled = [];
msg = [];
loop = false;
relation.forEach(function (a) {
if (getBox(a.name)) {
a.needed.forEach(function (b) {
if (!getBox(b)) {
msg.push('With ' + a.name + ', ' + b + ' is required');
setBox(b, true);
loop = true;
}
});
a.prohibited.forEach(function (b) {
if (getBox(b)) {
msg.push('With ' + a.name + ', ' + b + ' is prohibited');
setBox(b, false);
loop = true;
}
setBoxDisabled(b, true);
!~disabled.indexOf(b) && disabled.push(b);
});
}
});
relation.forEach(function (a) {
if (!getBox(a.name)) {
a.prohibited.forEach(function (b) {
!~disabled.indexOf(b) && setBoxDisabled(b, false);
});
}
});
msg.length && out(msg.join('<br>') + '<hr>');
} while (loop);
}
function out(s) {
var node = document.createElement('div');
node.innerHTML = s + '<br>';
document.getElementById('out').appendChild(node);
}
奖励:略有不同的方法,具有递归风格和适当的更改消息。
var relation = [
{ name: 'A', needed: ['B', 'C'], prohibited: ['D'] },
{ name: 'B', needed: [], prohibited: ['E'] },
{ name: 'C', needed: ['F'], prohibited: [] },
{ name: 'D', needed: ['E'], prohibited: [] },
{ name: 'E', needed: [], prohibited: [] },
{ name: 'F', needed: [], prohibited: ['G'] },
{ name: 'G', needed: [], prohibited: [] },
{ name: 'S', needed: [], prohibited: ['B'] },
{ name: 'T', needed: [], prohibited: ['D'] },
{ name: 'U', needed: ['S'], prohibited: [] }
], object = {};
void function () {
var div = document.createElement('div'),
form = document.createElement('form');
div.id = 'out';
form.name = 'boxes';
relation.forEach(function (a) {
var br = document.createElement('br'),
input = document.createElement('input'),
label = document.createElement('label');
input.type = 'checkbox';
input.name = a.name;
input.addEventListener('change', function (l) { return function () { checkBox(l); } }(a.name));
//input.addEventListener('change', function () { checkBox(a.name); });
label.textContent = a.name;
label.for = a.name;
label.appendChild(input);
label.appendChild(document.createTextNode((a.needed.length ? ' needed: ' + a.needed.join(', ') : '') + (a.prohibited.length ? ' prohibited: ' + a.prohibited.join(', ') : '')));
form.appendChild(label);
form.appendChild(br);
object[a.name] = a;
});
form.appendChild(div);
document.body.appendChild(form);
}();
function checkBox(l) {
function getBox(l) { return document.boxes[l].checked; }
function setBox(l, v, x) {
if (document.boxes[l].checked !== v) {
v ? out('With ' + x + ' option ' + l + ' is necessary.') : out('Without ' + x + ' option ' + l + ' is not valid.');
document.boxes[l].checked = v;
}
}
function setBoxDisabled(l, v, x) {
if (document.boxes[l].disabled !== v) {
v ? out('With ' + x + ' option ' + l + ' is not available.') : out('Without ' + x + ' option ' + l + ' is now available.');
document.boxes[l].disabled = v;
}
}
if (getBox(l)) {
object[l].prohibited.forEach(function (p) {
setBox(p, false, l);
setBoxDisabled(p, true, l);
relation.forEach(function (a) {
if (~a.needed.indexOf(p)) {
setBox(a.name, false, p);
setBoxDisabled(a.name, true, p);
checkBox(a.name);
}
});
checkBox(p);
});
object[l].needed.forEach(function (p) {
setBox(p, true, l);
checkBox(p);
});
} else {
var allProhibited = [];
relation.forEach(function (a) {
if (getBox(a.name)) {
a.prohibited.forEach(function (b) {
!~allProhibited.indexOf(b) && allProhibited.push(b);
});
}
});
object[l].prohibited.forEach(function (p) {
if (!~allProhibited.indexOf(p)) {
setBox(p, false, l);
setBoxDisabled(p, false, l);
}
relation.forEach(function (a) {
if (~a.needed.indexOf(p)) {
setBox(a.name, false, p);
setBoxDisabled(a.name, false, p);
checkBox(a.name);
}
});
checkBox(p);
});
relation.forEach(function (a) {
if (~a.needed.indexOf(l)) {
setBox(a.name, false, l);
checkBox(a.name);
}
});
}
}
function out(s) {
var node = document.createElement('div');
node.innerHTML = s + '<br>';
document.getElementById('out').appendChild(node);
}
答案 3 :(得分:0)