转换键值对数组:值的键和唯一键的值

Convert array of keys-value pairs: keys for values, and values for unique keys

我想转换用作键值存储的数组数组。

每个子数组采用以下形式:['tom',['hello','world']],其中 [0] 索引 ('tom') 是 'key',[1] 索引(数组)是 'value'.

我希望我的数组中的所有 'values' 都是新数组的唯一键,并且我数组中的键应该构造新的子数组,其中包含保存相应值的所有先前键.

例如:

var myArray = [
    ['tom',['hello','world']],
    ['bob',['world','foo']],
    ['jim',['foo','bar']]
];

以上输入应该得到如下输出:

var newArray = [
    ['hello',['tom']],
    ['world',['tom','bob']],
    ['foo',['bob','jim']],
    ['bar',['jim']],
];

我怎样才能做到这一点?

在开始 'solution' 之前,我想说明这样一个事实,即这种存储和描述数据的方式非常糟糕。如果您正在寻找类似于 PHP 中的关联数组的内容,您应该学习如何 work with objects.

JS 中的对象只是唯一键(属性)-> 值对的集合。

作为对象,您的数据集将如下所示:

var before = {
  tom: ['hello','world'],
  dick: ['world','foo'],
  harry: ['foo','bar']
};

var after = {
  bar: ["harry"],
  foo: ["dick", "harry"],
  hello: ["tom"],
  world: ["tom", "dick"]
};

这是一个使用对象的实现。也很幼稚,但是简单多了。

DEMO

var before = {
  tom: ['hello','world'],
  dick: ['world','foo'],
  harry: ['foo','bar']
};

var after = {
  bar: ["harry"],
  foo: ["dick", "harry"],
  hello: ["tom"],
  world: ["tom", "dick"]
};


function resObj(obj) {
  var o = {};
  
  for (var k in obj) {
    for (var i = 0; i < obj[k].length; i++) {
      o[obj[k][i]] = o[obj[k][i]] || [];
      o[obj[k][i]].push(k);
    }
  }
  
  return o;
}

console.log('Expected:', after);
console.log('Actual:', resObj(before));


下面是一个示例,说明如何使用数组执行您想要的操作。它幼稚、缓慢,我相信它可以改进,但这与我的回答无关。

粗略的演示。注意,我们用一个对象作为交换,使得它和上面几乎一样。

DEMO

var inp = [
    ['tom',['hello','world']],
    ['dick',['world','foo']],
    ['harry',['foo','bar']]
];

var out = [
    ['hello',['tom']],
    ['world',['tom','dick']],
    ['foo',['dick','harry']],
    ['bar',['harry']]
];

function resArray(arr) {
  var q = {},
      o = [];
  
  for(var i = 0; i < arr.length; i++) {
    for (var j = 0; j < arr[i][1].length; j++) {
      q[arr[i][1][j]] = q[arr[i][1][j]] || [];
      q[arr[i][1][j]].push(arr[i][0]);
    }
  }
  
  for (var m in q) {
    o.push([m, q[m]]);
  }
  
  return o;
}

console.log('Expected:', out);
console.log('Actual:', resArray(inp));

就像我在评论中所说的那样,您实际上是在要求计算多对一关系的倒数。您可以将关系视为一对对象的映射。从逻辑上讲,您正在将 "tom" 映射到 "hello" 和 "world"。反向关系将 "hello" 和 "world" 映射到 "tom".

当您想到"relations"时,您应该想到关联容器而不是数组。使用数组会使你的算法效率低得多,除非你的键是密集的整数。

这会产生正确的输出:

var myRelation = [
  ['tom', ['hello', 'world']],
  ['dick', ['world', 'foo']],
  ['harry', ['foo', 'bar']]
];

function inverse(relation) {
  // This first half does the hard work of computing the inverse.
  var intermediate = {};
  relation.forEach(function(outerEntry) {
    outerEntry[1].forEach(function(innerEntry) {
      if (!intermediate[innerEntry]) {
        intermediate[innerEntry] = {};
      }
      intermediate[innerEntry][outerEntry[0]] = true;
    });
  });
  // This second half turns the intermediate assocative container
  // back into an array of nested arrays.
  var output = [];
  Object.keys(intermediate).forEach(function(outerEntry) {
    output.push([outerEntry, []]);
    Object.keys(intermediate[outerEntry]).forEach(function(innerEntry) {
      output[output.length - 1][1].push(innerEntry);
    });
  });
  return output;
}

console.log(inverse(myRelation));

如果您不再需要在数组中重现原始输出格式,问题就会变得简单一些。

这里是您转换函数的一个干净的实现。

/**
 * Converts from a one-to-many to many-to-one relationship.
 * @param  {[array]} pairs [array representing a one-to-many relationship.]
 * @return {[array]}       [array representing the same data, in a many-to-one relationship]
 */
var oneToManyFlip = function(pairs){
  //Recall that current[0] is our 'key'.
  //Also recall that 'prev' is the empty object we passed in.
  var result = pairs.reduce(function(storage, current, index, array){ 
    current[1].forEach(function(element, index){
      if(storage[element] === undefined){ storage[element] = [];}
      storage[element].push(current[0]);
    });
    return storage;
  }, {});
  return Object.keys(result).map(function(element){
      return [element, result[element]];
    });
}

我希望它更简洁、更易于推理。 Map & Reduce 一开始可能有点棘手。

您可以了解有关函数式 JS 的更多信息here。它对可迭代对象的这类计算很有用。