创建一个生成器来迭代对象 属性 排列

Create a generator to iterate object property permutations

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

// 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}

我已经尝试了很多事情,但我得到的最深入的是将第一次迭代正确地 return,但没有别的。您如何概括 getUnits() 函数以及我应该注意哪些陷阱?

您可以对 属性 个名称的列表使用递归:

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);
            }
        }
    }
}

如果您正在寻找性能更高的解决方案,您可以动态生成 "hardcoded" 版本的源代码并使用 Function - see this answer 进行编译作为示例。

您如何实施 Range class?我尝试了这项工作:

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 class 实施上。

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

// 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 指出 yielded 对象实例在所有情况下都是相同的,理想情况下应该在每个分支上克隆它(因此它们都是不同的实例)。

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

// 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...