将 Javascript 数组缩减为 Map,从内部值创建键

Reducing Javascript array into a Map, creating keys out of an inner value

这是进一步 的尝试。我觉得这完全不同,足以保证另一个主题,希望能帮助任何试图解决同样问题的人。

如果我有一个 key-value 对的数据集,假设我想完成 3 件事:

  1. 查找内部 key-value 对值的第一次出现。
  2. 将该值复制到地图中
  3. 利用另一个 key-value 对的值作为映射的键。

因此,例如,假设我有以下数据集:

[
  {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},
  {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},
  {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},
  {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},
  {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},
  {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},
  {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}
]

我想以下面的地图结束 object:

[
  "2019-01-01" => "snow",
  "2019-02-01" => "none",
  "2019-03-01" => "rain",
  "2019-06-01" => "hail"
]

作为最后一个挑战,我如何在我的结果可以是动态的函数中执行此操作?所以在上面的例子中,我在最终的Map中选择了'precip'作为想要的值。但是如果我想要 'season' 怎么办?有没有办法动态地做到这一点,我可以将 'key' 名称作为函数的参数传递?

另外,这个操作有名字吗?我很难想出一个标题。如果有人有更好的主意,我很乐意重命名它。我觉得这是一个优雅的问题,许多人可能会 运行 陷入其中。

您可以使用

  1. Array#filter 删除任何最终会产生重复值的条目
  2. Array#map 处理您的数据以生成键值对
  3. collect into a Map via the constructor

要获得这种动态,您只需提供用于键和值的属性名称:

const data = [
  {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},
  {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},
  {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},
  {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},
  {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},
  {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},
  {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}
];

function transform(keyProp, valueProp, arr) {
  const keyValuePairs = arr
    .filter(function(obj) {
      const value = obj[valueProp];
      //only keep the value if it hasn't been encountered before
      const keep = !this.has(value);

      //add the value, so future repeats are removed
      this.add(value)

      return keep;
    }, new Set()) // <-- pass a Set to use as `this` in the callback
    .map(obj => [obj[keyProp], obj[valueProp]]);
  
  return new Map(keyValuePairs);
}

const map = transform("date", "precip", data);

//Stack Snippets don't print the Map content
//via console.log(map), so doing it manually
for (let [key, value] of map) {
  console.log(`${key} -> ${value}`);
}

请注意,这使用了 .filter 的第二个参数 - 它设置了回调的 this 上下文。通过将其设置为 Set,它确保仅用于 .filter 操作 - 您不需要在整个函数的范围内保留额外的变量。此外,由于它设置了 this 上下文,因此您需要一个普通的 function 而不是箭头函数,因为后者的 this 值无法更改。

这对 Array.prototype.reduce

来说是个好场景

const invertMap = map => {
  const entires = Array.from(map.entries());
  const reversedKeyValues = entires.map(([key, value]) => [value, key]);
  return new Map(reversedKeyValues);
};

const weatherLogs = [
      {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},
      {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},
      {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},
      {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},
      {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},
      {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},
      {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}
    ];

const firstPrecipToDate = weatherLogs.reduce((acc, log) => {
  const { date, precip } = log;
  
  if (!acc.has(precip)) {
    // only add precip to the `precip->date` map if we don't have it yet
    acc.set(precip, date);
  }

  return acc;
}, new Map());

// Now invert map to be date -> precip
const dateToPrecip = invertMap(firstPrecipToDate);

console.log(dateToPrecip);
console.log('-- It shows as empty object in Whosebug, view browser console');


说明

weatherLogs.reduce 接受 2 个参数。第一个参数是回调,第二个参数是(累加器的)初始值。

回调按顺序对数组中的每个元素执行,可以接受以下参数。

  • 累加器(示例中的acc
    • 这是之前从最后一项返回的值
  • currentValue(示例中的log
    • 这是数组中正在处理的当前元素
  • index(示例中未使用)
    • 这是当前元素的索引
  • 数组(示例中未使用)
    • 调用reduce的原始数组

在此处阅读有关 Array.prototype.reduce 的更多信息 - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

使用 reduceRightmapsort

const arr = [{"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},{"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},{"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},{"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},{"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},{"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},{"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}];

const key = "precip";

const res = new Map(Object.entries(arr.reduceRight((a, { date, [key]: c }) => (a[c] = date, a), {})).map(e => e.reverse()).sort(([a], [b]) => new Date(a) - new Date(b)));

console.log(res);

您实际上有几个问题。我会一一解答。

问题 1:使其动态化

您需要做的就是接受更多参数,如下所示。

这样,您不仅可以使结果的 value 动态化,还可以使结果的 key 动态化。

由于动态特性,大多数时候您需要更详细的检查。所以你会在下面看到很多条件抛出。觉得合适就拿出来吧。

const data = [
  {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},
  {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},
  {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},
  {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},
  {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},
  {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},
  {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}
];


let result;

result = firstOccurance(data, 'date', 'precip');
console.log('date => precip');
log(result);

result = firstOccurance(data, 'date', 'season');
console.log('date => season');
log(result);

result = firstOccurance(data, 'temp', 'precip');
console.log('temp => precip');
log(result);


/**
 * All you need to do is accept a few more arguments:
 *   1. keyPropName
 *   2. valuePropName
 *
 * This not only make value dynamic, the key can also be dynamic.
 */
function firstOccurance(data, keyPropName, valuePropName){
  if (keyPropName === valuePropName)
    throw new TypeError('`keyPropName` and `valuePropName` cannot be the same.');

  const ret = new Map();

  for (const obj of data){
    if (!hasOwnProperty(obj, keyPropName) || !hasOwnProperty(obj, valuePropName))
      throw new ReferenceError(`Property ${keyPropName} is not found in the dataset.`);

    const key = obj[keyPropName]
    const value = obj[valuePropName];

    // Check if `value` already exist in Map.
    if ([...ret.values()].includes(value))
      continue;

    ret.set(key, value);
  }

  return ret;
}

function hasOwnProperty(obj, name){
  return Object.prototype.hasOwnProperty.call(obj, name);
}

function log(iterable){
  for (const [key, val] of iterable){
    console.log(key, '=>', val);
  }
  console.log('\n');
}

您可以继续将 keyPropNamevaluePropName 更改为其他内容,它会按预期正常工作。

问题二:这个操作有名称吗?

没有

试试这个方法:

const data = [
  {"date":"2019-01-01", "temp":"cold", "season":"winter", "precip":"snow"},
  {"date":"2019-02-01", "temp":"cold", "season":"winter", "precip":"none"},
  {"date":"2019-03-01", "temp":"mild", "season":"spring", "precip":"rain"},
  {"date":"2019-04-01", "temp":"mild", "season":"spring", "precip":"none"},
  {"date":"2019-05-01", "temp":"warm", "season":"spring", "precip":"rain"},
  {"date":"2019-06-01", "temp":"warm", "season":"summer", "precip":"hail"},
  {"date":"2019-07-01", "temp":"hot", "season":"summer", "precip":"none"}
]

function getFirstOccurenceMap(key, value) {
    const myMap = new Map();
    for (let i=0; i<data.length; i++) {
        if (data[i][value] && !myMap.has(data[i][value])) {
            myMap.set(data[i][value], data[i][key]);
        }
    }
    result = new Map();
    if (myMap) {
        for (var [key, value] of myMap) {
            if (!result.has(value)) {
                result.set(value, key);
            }
        }
    }
    return result;
}

console.log(getFirstOccurenceMap("date", "temp"));