使用 .reduce() 在对象列表上

4

我有这个样本数据集:

let list = [
  {'first': 'morgan', 'id': 1},
  {'first': 'eric', 'id': 1},
  {'first': 'brian', 'id': 2 },
  {'first': 'derek', 'id' : 2},
  {'first': 'courtney', 'id': 3},
  {'first': 'eric', 'id': 4},
  {'first': 'jon', 'id':4},
]

我正在尝试得到这个结果:

[[1, [morgan, eric]], [2, [brian, derek]], [3, [courtney]], [4, [eric, jon]]

我正在使用.reduce()函数来映射列表。然而,我有些卡住了。

我已经做到了这一点:

let b = list.reduce((final_list, new_item) => {
  x = final_list.concat([[new_item.id, [new_item.first]]])
  return x
}, [])

然而,这样会将列表扁平化为列表的列表,但不会合并共享相似id的名称。

我尝试使用.map() 下面的代码不起作用

我尝试对final_list(这是一个[id,[names]]列表)进行映射,查看new_item的id是否存在于smaller_list中,然后将new_item.first添加到smaller_list [1](应该是名称列表)。

这是正确的方法吗?

let b = list.reduce((final_list, new_item) => {
  final_list.map((smaller_list) => {
    if (smaller_list.indexOf((new_item.id)) >=0) {
      smaller_list[1].concat(new_item.first)
      // not sure what to do here...
    } else {
        // not sure what to do here either...
    }

  })
  x = final_list.concat([[item.id, [item.first]]])
  return x
}, [])
7个回答

6

在我的方法中,我使用reduce来创建一个稀疏数组,将索引作为列表id。然后我使用Object.values来压缩它。

let list = [
  {'first': 'morgan', 'id': 1},
  {'first': 'eric', 'id': 1},
  {'first': 'brian', 'id': 2 },
  {'first': 'derek', 'id' : 2},
  {'first': 'courtney', 'id': 3},
  {'first': 'eric', 'id': 4},
  {'first': 'jon', 'id':4}
];

let result = list.reduce( (acc, item, index) => {
  if(acc[item.id]){
   acc[item.id][1].push(item.first);
  } else {
   acc[item.id] = [item.id, [item.first]];
  }
  return acc;
}, []);

console.log(Object.values(result));


3
我的解决方案是首先映射您的列表并组合具有相似ID的项,然后循环遍历该结果以更改为所请求的格式。

let list = [
  {'first': 'morgan', 'id': 1},
  {'first': 'eric', 'id': 1},
  {'first': 'brian', 'id': 2 },
  {'first': 'derek', 'id' : 2},
  {'first': 'courtney', 'id': 3},
  {'first': 'eric', 'id': 4},
  {'first': 'jon', 'id':4},
]

let temp = [], result = [];

list.map(entry => {
    if(!temp[entry.id]) temp[entry.id] = [entry.first]
    else temp[entry.id].push(entry.first)
})
temp.forEach((names, id) => {
    result.push([id, names])
})

console.log(JSON.stringify(result))

编辑:我使用一个数组作为temp,因为ID是一个整数,并且为了方便使用forEach循环。如果键是其他内容,则应使用哈希表。


你确定 temp 应该是一个数组吗? - Phil
@Phil 我认为是这样的,因为数组比字典更容易和更快地迭代,而且由于id是整数,所以很容易将其用作数组键。 - FrenchMajesty
啊,我明白了是什么困扰着我。temp 在位置 0 会有一个 undefined 的条目。看起来 forEach 不会包括它或者任何其他索引中的间隙。今天我学到了这个。 - Phil

3

首先创建一个ID到名称的映射表,然后将它映射到你最终的数组中,例如:

let list = [
  {'first': 'morgan', 'id': 1},
  {'first': 'eric', 'id': 1},
  {'first': 'brian', 'id': 2 },
  {'first': 'derek', 'id' : 2},
  {'first': 'courtney', 'id': 3},
  {'first': 'eric', 'id': 4},
  {'first': 'jon', 'id':4},
]

const idMap = list.reduce((map, item) => {
  if (Array.isArray(map[item.id])) {
    map[item.id].push(item.first)
  } else {
    map[item.id] = [item.first]
  }
  return map
}, {})

const b = Object.keys(idMap).map(id => [parseInt(id), idMap[id]])
console.info(JSON.stringify(b))


我不知道我们有console.info,很酷。算法也很好 :-) - Fred Gandt

1
你想使用reduce来完成它,使用单个reduce会更好。以下是3种方法。
冗长的、循环更多的方式
const tuples1 = list.reduce( (tuples, obj) => {
  if( tuples.some( ( [id, _] ) => id === obj.id ) ){
    tuples.find( ( [id, _] ) => id === obj.id )[1].push(obj.first)
  }else{
    tuples.push([obj.id, [obj.first]])
  }
  return tuples
}, [])

同元组1,简洁
const tuples2 = list.reduce( (tuples, obj) =>
  ( tuples.some( ( [id, _] ) => id === obj.id ) ) ?
    ( tuples.find( ( [id, _] ) => id === obj.id )[1].push(obj.first), tuples ) :
    ( tuples.push([obj.id, [obj.first]]), tuples ), [])

更冗长,循环更少
const tuples3 = list.reduce( (tuples, obj) => {
  const existingId = tuples.find( ( [id, _] ) => id === obj.id )
  if( existingId ){
    existingId[1].push(obj.first)
  }else{
    tuples.push([obj.id, [obj.first]])
  }
  return tuples
}, [])

每个都产生相同的结果。
[ [ 1, [ 'morgan', 'eric' ] ],
  [ 2, [ 'brian', 'derek' ] ],
  [ 3, [ 'courtney' ] ],
  [ 4, [ 'eric', 'jon' ] ] ]

0

如果您也在寻找一种非 reduce 解决方案,那么请看这里。虽然有点啰嗦,但我试图将问题分解成几个部分。

  1. 首先,我遍历 list 并将元素按其各自的 Id 排序。

  2. 然后,对于排序后的对象中的每个 id,我再次通过首先实例化一个外部元素来循环它。

  3. id 键推入该元素中。
  4. 循环所有相关元素并提取它们的 first 属性并将其添加到嵌套元素中。
  5. 构建完嵌套元素后,我只需将它们合并到父元素中并将其附加到 result 数组中。

希望这不会对您太复杂,但我怀疑通过 IDE 查看代码将更容易理解。

var list = [
    {'first': 'morgan', 'id': 1},
    {'first': 'eric', 'id': 1},
    {'first': 'brian', 'id': 2 },
    {'first': 'derek', 'id' : 2},
    {'first': 'courtney', 'id': 3},
    {'first': 'eric', 'id': 4},
    {'first': 'jon', 'id':4},
]

var result = [];
var keyById = {};

// Sort the list of items to be keyed by: Id => [ item, item ]
list.forEach(function(item) {
    if (!keyById.hasOwnProperty(item.id)) {
        keyById[item.id] = [];
    }
    keyById[item.id].push(item);
});

// Build the data structure
for (var id in keyById) {
    var item = []; // The outer element
    var nestedItem = []; // The nested element belonging to the outer
    item.push(id);
    keyById[id].forEach(function(element) {
        nestedItem.push(element.first);
    });
    item.push(nestedItem);
    result.push(item); 
}

console.log(JSON.stringify(result, '', 2));


0

不太性感,但它有效

  • 遍历list并使用id作为key,使用first名字的array作为value构建对象。
  • 清空list(如果您计划覆盖它)。
  • 遍历结果对象,为每个id创建一个array,其下一个兄弟元素是与该id相关联的value中的名称的array,并将它们pushlist(或新的array,如果要保留原始list)中。

var list = [
      {'first': 'morgan', 'id': 1},
      {'first': 'eric', 'id': 1},
      {'first': 'brian', 'id': 2 },
      {'first': 'derek', 'id' : 2},
      {'first': 'courtney', 'id': 3},
      {'first': 'eric', 'id': 4},
      {'first': 'jon', 'id':4},
    ],
    o = {}, p;

list.forEach( ( v ) => {
    if ( !o[ v.id ] ) {
        o[ v.id ] = [];
    }
    o[ v.id ].push( v.first );
} );

list = []; // if overwriting

for ( p in o ) {
    if ( o.hasOwnProperty( p ) ) {
        list.push( [ parseInt( p ), o[ p ] ] );
    }
}

console.log( JSON.stringify( list ).replace( /,/g, ", " ) );

编辑:添加parseInt()(在另一个答案中注意到),因为问题的示例输出确实将数字显示为整数,而不是字符串。
并将演示输出更改为stringifyJSON(也在其他答案中注意到(更易读))。


-2

对于这个问题,最简单的方法可能是编写一个普通的循环:

let final_list = []
for (k = 0; k < list.length; k += 2) {
    let item1 = list[k]
    let item2 = list[k + 1]
    final_list.push([item1.id, [item1.first, item2.first]])
}

这会导致出现“未捕获的类型错误:无法读取未定义属性'first'”。 - Phil
好的,那就写正确一些,但是这种方法是有效的。 - Alex
不,这并不是这样的。您假设a)当前索引后始终存在一个项目(在最后一次迭代中并非如此),b) k + 1项始终具有与k项相同的ID。 - Phil
啊,好的。那么问题就是遍历列表并将名称与ID制表,就像@FrenchMajesty所做的那样。 - Alex

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接