创建生成器以迭代对象属性排列

时间:2016-04-06 04:28:34

标签: javascript node.js ecmascript-6 permutation

我创建了一个Range类,它创建了一个生成器函数,用于迭代一系列整数。我的下一步是创建一个生成器函数,该函数迭代每个属性的所有可能的值排列。以下是硬编码示例的简化代码:

// Create the parameter definitions (works perfectly).
const paramDef = {
  propA: new Range(1, 2, 3), // [1,2,3] as iterator
  propB: new Range(2, 4, 6)  // [2,4,6] as iterator
};

// Hardcoded implementation (the goal is to make this generic/re-usable)
function* getUnits(def){
  // Foreach value of propA...
  for(let valPropA of def.propA.getValues()){
    // and foreach value of propB...
    for(let valPropB of def.propB.getValues()){
      // Yield an instance with the current values...
      yield {
        propA: valPropA,
        propB: valPropB
      };
    }
  }
}

// Iterate one-by-one, creating a permutation of object properties.
for(let unit of getUnits(paramDef)){
    console.log(unit);
}

// Outputs:
// {"propA":1,"propB":2}
// {"propA":1,"propB":4}
// {"propA":1,"propB":6}
// {"propA":2,"propB":2}
// {"propA":2,"propB":4}
// {"propA":2,"propB":6}
// {"propA":3,"propB":2}
// {"propA":3,"propB":4}
// {"propA":3,"propB":6}

我已经尝试了很多东西,但我得到的最远的是让第一次迭代正确返回,但没有别的。你如何推广getUnits()函数以及我应该注意哪些陷阱?

3 个答案:

答案 0 :(得分:1)

您可以在属性名称列表中使用递归:

function getObjectsOf(def) {
    var keys = Object.keys(def),
        o = {};
    return rec(keys.length);

    function* rec(i) {
        if (i <= 0) {
            let clone = {}; // I assume you want to yield different objects
            for (let k of keys) // or: k in o
                clone[k] = o[k];
            yield clone;
        } else {
            let key = keys[i];
            for (let value of def[key]) {
                o[key] = value;
                yield* rec(i-1);
            }
        }
    }
}

如果您正在寻找更高效的解决方案,您可以动态生成&#34;硬编码&#34;版本并使用Function进行编译 - 请参阅this answer以获取示例。

答案 1 :(得分:0)

如何实施Range课程?我尝试过这项工作:

class Range{
    constructor(){
        var args = Array.prototype.slice.call(arguments);
        this.getValues = function*(){
            for(const num of args){
                yield num;
            }
        };
    }
}

使用数组也有效:

'use strict';

const paramDef = {
  propA: [1, 2, 3], // [1,2,3] as iterator
  propB: [2, 4, 6]  // [2,4,6] as iterator
};

// Hardcoded implementation (the goal is to make this generic/re-usable)
function* getUnits(def){
  // Foreach value of propA...
  for(let valPropA of def.propA){
    // and foreach value of propB...
    for(let valPropB of def.propB){
      // Yield an instance with the current values...
      yield {
        propA: valPropA,
        propB: valPropB
      };
    }
  }
}                                                                                                                         

// Iterate one-by-one, creating a permutation of object properties.
for(let unit of getUnits(paramDef)){
    console.log(unit);
}

所以,我认为问题是你的Range类实现。

答案 2 :(得分:0)

这是主要功能(下面的完整代码):

// Generic implementation 
function* getUnits(def, props, obj){
    props = props || [];

  // If there are no remaining properties...
  if(props.length === 0){
    // Then we might be starting out...
    if(typeof obj === 'undefined'){
        // Grab the property names from the definition.
        props = Object.keys(def);
      // And create an empty object 
      obj = {};
    } else {
        yield obj;
      return;
    }
  }

  // Grab the first prop and a copy of the remaining props.
  let currentProp = props[0];
  let remainingProps = props.slice(1);
  // Foreach value of the currentProp...
  for(let val of def[currentProp].getValues()){

    // Assign the value to a new instance
    let currentObj = Object.assign({}, obj, {
        [currentProp]: val
    });
    // Pass the definition, remainingProps, and the new instance to the next level down (smaller subset of properties)
    yield* getUnits(def, remainingProps, currentObj);
  }
}

我找到的最佳资源是MDN's Example with yield*演示。如果你想更好地理解ES6发生器,那么绝对值得阅读整篇文章。

@Bergi指示yield'ed对象实例在所有情况下都相同,理想情况下应该在每个分支处克隆(因此它们是不同的实例)。

整个示例包含在此代码段中(运行它以查看结果)。

// Helper class, provides an iterator from a set of args.
class Range {
  constructor() {
    this.values = Array.prototype.slice.call(arguments);
  }

  * getValues() {
    for (let i = 0; i < this.values.length; i++) {
      yield this.values[i];
    }
  }
}

// Create the parameter definitions (works perfectly).
const paramDef = {
  a: new Range(1, 2, 3),
  b: new Range(0, 1),
  c: new Range(1, 1, 2, 3, 5)
};

// Generic implementation 
function* getUnits(def, props, obj){
	props = props || [];
  
  // If there are no remaining properties...
  if(props.length === 0){
  	// Then we might be starting out...
  	if(typeof obj === 'undefined'){
    	// Grab the property names from the definition.
    	props = Object.keys(def);
      // And create an empty object 
      obj = {};
    } else {
    	yield obj;
      return;
    }
  }
  
  // Grab the first prop and a copy of the remaining props.
  let currentProp = props[0];
  let remainingProps = props.slice(1);
  // Foreach value of the currentProp...
  for(let val of def[currentProp].getValues()){
  	
    // Assign the value to a new instance
    let currentObj = Object.assign({}, obj, {
    	[currentProp]: val
    });
    // Pass the definition, remainingProps, and the new instance to the next level down (smaller subset of properties)
    yield* getUnits(def, remainingProps, currentObj);
  }
}

let outputStr = '';

// Iterate one-by-one, creating a permutation of object properties.
for (let unit of getUnits(paramDef)) {
  outputStr += JSON.stringify(unit) + '\n';
}
alert(outputStr);

// Outputs:
// See console for the result...