将 Javascript 数组缩减为 Map,从内部值创建键
Reducing Javascript array into a Map, creating keys out of an inner value
这是进一步 的尝试。我觉得这完全不同,足以保证另一个主题,希望能帮助任何试图解决同样问题的人。
如果我有一个 key-value 对的数据集,假设我想完成 3 件事:
- 查找内部 key-value 对值的第一次出现。
- 将该值复制到地图中
- 利用另一个 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' 名称作为函数的参数传递?
另外,这个操作有名字吗?我很难想出一个标题。如果有人有更好的主意,我很乐意重命名它。我觉得这是一个优雅的问题,许多人可能会 运行 陷入其中。
您可以使用
Array#filter
删除任何最终会产生重复值的条目
Array#map
处理您的数据以生成键值对
- 就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
使用 reduceRight
、map
和 sort
。
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');
}
您可以继续将 keyPropName
和 valuePropName
更改为其他内容,它会按预期正常工作。
问题二:这个操作有名称吗?
没有
试试这个方法:
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"));
这是进一步
如果我有一个 key-value 对的数据集,假设我想完成 3 件事:
- 查找内部 key-value 对值的第一次出现。
- 将该值复制到地图中
- 利用另一个 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' 名称作为函数的参数传递?
另外,这个操作有名字吗?我很难想出一个标题。如果有人有更好的主意,我很乐意重命名它。我觉得这是一个优雅的问题,许多人可能会 运行 陷入其中。
您可以使用
Array#filter
删除任何最终会产生重复值的条目Array#map
处理您的数据以生成键值对- 就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
使用 reduceRight
、map
和 sort
。
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');
}
您可以继续将 keyPropName
和 valuePropName
更改为其他内容,它会按预期正常工作。
问题二:这个操作有名称吗?
没有
试试这个方法:
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"));