遍历javascript

时间:2015-11-12 12:51:00

标签: javascript recursion matrix checkbox

有许多元素(表示为复选框)具有一些不同的关系。例如:

  • A需要B
  • A需要C
  • A不能与D
  • 组合使用
  • B不能与E
  • 结合使用
  • D需要E
  • C需要F
  • F不能与G
  • 组合使用
  • S不能与B
  • 结合使用
  • T不能与D
  • 组合使用
  • 你需要S

编辑:答案中出现了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必然。

现在的重大问题是:

  1. 如何在javascript中理想地表达关系
  2. 如果有人激活,如何检查与javascript的所有关系 一个复选框。
  3. 问题是递归。每一个行动都会导致更多的行为导致(可能)更多的行动。

    以下逻辑应适用于该示例,“A”已激活:

    1. B将被激活
    2. C将被激活
    3. D将被禁用
    4. E将被禁用
    5. S将被禁用
    6. T将被禁用
    7. U将被禁用
    8. 关系的当前定义(可以更改):

      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,则需要再次检查所有关系 - 对于所有仍处于活动状态的复选框。

4 个答案:

答案 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)

您作为示例给出的关系类型可以表示为带有标记边的有向图:

Graph

在上图中,边缘的颜色代表边缘的标签或标签:
绿色:需要
红色:禁止

边缘上的点代表关系的方向。

现在您知道可以使用图论算法来遍历/搜索数据了。见:
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)

您可以尝试基于规则的引擎,例如nools

https://github.com/C2FO/nools

对于这种特殊情况来说可能有点过分,但如果它变得更复杂,它可以保证解决这个问题的可靠方法