繁体   English   中英

如何在具有相同属性值的对象旁边的数组中插入对象

[英]How to insert objects in array next to objects with the same property value

我有一系列对象

const allRecords = [
  {
    type: 'fruit',
    name: 'apple'
  },
  {
    type: 'vegetable',
    name: 'celery'
  },
  {
    type: 'meat',
    name: 'chicken'
  }
]

我想从另一个数组插入对象,以便将元素放置在相同类型的元素旁边。

const  newRecords = [
  {
    type: 'fruit',
    name: 'pear'
  },
  {
    type: 'vegetable',
    name: 'spinach'
  },
  {
    type: 'meat',
    name: 'pork'
  }
]

这样的调用是这样的:

allRecords.sortAndInsert(newRecords)

返回如下内容:

[
  {
    type: 'fruit',
    name: 'apple'
  },
  {
    type: 'fruit',
    name: 'pear'
  },
  {
    type: 'vegetable',
    name: 'celery'
  },
  {
    type: 'vegetable',
    name: 'spinach'
  },
  {
    type: 'meat',
    name: 'chicken'
  },
  {
    type: 'meat',
    name: 'pork'
  },

在我的情况下,我无法比较“类型”来确定应按字母顺序或按长度排列的位置((蔬菜在肉类之前,但在水果之后)。此外,没有ID属性可以对事物进行数字定位。我只想按相同的类型对事物进行分组。

我发现我可以通过使用数组的长度获取索引来插入正确的索引:

// This gives the amount of records for each group. 
//In our example, this would be 2 for 'apple' and 'pear', etc
const multiplier = (allRecords.length + newRecords.length) / 
   (newRecords.length);
for (let i = 0; i < newRecords.length; i++){
    // Insert the record at 1 + i + multiplier. 'pear' will go to 1 + 0 * 2 = 1
    allRecords.splice(1 + i * multiplier, 0, newRecords[i]);
  }
return allRecords;

但是,该函数在做什么不是很容易理解。 此外,它假定新记录具有每种类型之一。

我想要一个函数,而不是查看属性并将它们组合在一起。 理想情况下,它还应该能够按某种顺序对组进行排序(例如,指定“水果”组排在最前面,“蔬菜”组排在其后,然后是“肉类”组)。

我会完全使用地图。 一个例子如下。

 let myMap = new Map(); myMap.set('fruit', [{ name: 'apple', type: 'fruit' }]); myMap.set('vegetable', [{ name: 'celery', type: 'vegetable' }]); myMap.set('meat', [{ name: 'chicken', type: 'meat' }]); const newRecords = [{ type: 'fruit', name: 'pear' }, { type: 'vegetable', name: 'spinach' }, { type: 'meat', name: 'pork' }] newRecords.forEach(function(el) { let arr = myMap.get(el.type); arr.push(el); myMap.set(el.type, arr); }); for (let [k, v] of myMap) { console.log(k); console.log(v); } 

分组

这里有很多要讨论的内容,所以我要快一点。 如果您有任何卡住的地方,请发表评论,我会尽力扩大任何有问题的领域。

首先,不能保证allRecordsnewRecords在合并之前会被排序。 使用Map可以轻松有效地对类似项目进行分组。 但是,当我们要按所需顺序打印项目时,将需要对地图的值进行排序。 我们将在此答案的第二部分中进行处理。 我们allRecords type属性将allRecords分组-

const allRecords =
  [ { type: 'fruit', name: 'apple' }
  , { type: 'vegetable', name: 'spinach' }
  , { type: 'meat', name: 'chicken' }
  , { type: 'fruit', name: 'raspberry' } // added this item
  ]

const m1 =
  groupBy(x => x.type, allRecords)

console.log(m1)

// Map
//   { 'fruit' =>
//       [ { type: 'fruit', name: 'apple' }
//       , { type: 'fruit', name: 'raspberry' }
//       ]
//   , 'vegetable' =>
//       [ { type: 'vegetable', name: 'spinach' }
//       ]
//   , 'meat' =>
//       [ { type: 'meat', name: 'chicken' }
//       ]
//   }

接下来,我们以相同的方式对newRecords进行分组-

const newRecords =
  [ { type: 'meat', name: 'pork' }
  , { type: 'fruit', name: 'pear' }
  , { type: 'vegetable', name: 'celery' }
  , { type: 'dairy', name: 'milk' } // added this item
  ]

const m2 =
  groupBy(x => x.type, newRecords)

console.log(m2)

// Map
//   { 'meat' =>
//       [ { type: 'meat', name: 'pork' }
//       ]
//   , 'fruit' =>
//       [ { type: 'fruit', name: 'pear' } 
//       ]
//   , 'vegetable' =>
//       [ { type: 'vegetable', name: 'celery' }
//       ]
//   , 'dairy' =>
//       [ { type: 'dairy', name: 'milk' }
//       ]
//   }

在继续之前,让我们定义通用函数groupBy

const groupBy = (f, a = []) =>
  a.reduce
    ( (map, v) => upsert(map, [ f (v), v ])
    , new Map
    )

// helper
const upsert = (map, [ k, v ]) =>
  map.has(k)
    ? map.set(k, map.get(k).concat(v))
    : map.set(k, [].concat(v))

接下来,我们需要一种方法来组合两个映射m1m2

const m3 =
  mergeMap(m1, m2)

console.log(m3)
// Map
//   { 'fruit' =>
//       [ { type: 'fruit', name: 'apple' }
//       , { type: 'fruit', name: 'raspberry' }
//       , { type: 'fruit', name: 'pear' } 
//       ]
//   , 'vegetable' =>
//       [ { type: 'vegetable', name: 'spinach' }
//       , { type: 'vegetable', name: 'celery' }
//       ]
//   , 'meat' =>
//       [ { type: 'meat', name: 'chicken' }
//       , { type: 'meat', name: 'pork' }
//       ]
//   , 'dairy' =>
//       [ { type: 'dairy', name: 'milk' }
//       ]
//   }

我们可以轻松定义mergeMap来支持将任意数量的地图合并在一起-

const mergeMap = (...maps) =>
  maps.reduce(mergeMap1, new Map)

// helper
const mergeMap1 = (m1, m2) =>
  Array.from(m2.entries()).reduce(upsert, m1)

我们可以看到,地图很好地将项目分组在一起。 让我们现在收集所有值-

const unsorted =
  [].concat(...m3.values())

console.log(unsorted)

// [ { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'vegetable', name: 'spinach' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'dairy', name: 'milk' }
// ]

排序

答案的这一部分不适合胆小的人,但我强烈建议您坚持使用。 我们采用一种功能性方法来编写比较函数,但是在使用的技术上需要权衡取舍。 在这里,我们使用了许多易于编写,测试和维护的简单功能。 因此,这些功能更加灵活,可以在程序的其他区域中重复使用。 有关此方法背后的更多原因以及使用这些技术时会发生什么的详细信息,请参阅主题的最新解答

好的,所以我们看到列表当前是按水果蔬菜乳制品排序的。 这是由于它们在原始地图中的分组顺序。 如果您希望他们以其他方式订购怎么办?

unsorted.sort(orderByTypes("vegetable", "meat", "fruit"))

// [ { type: 'vegetable', name: 'spinach' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'dairy', name: 'milk' } 
// ]

好的,如果我们希望它们按name排序怎么办?

unsorted.sort(orderByName)

// [ { type: 'fruit', name: 'apple' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'meat', name: 'chicken' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'vegetable', name: 'spinach' }
// ]

是否可以先orderByTypes然后使用orderByName进行二级排序?

unsorted.sort
  ( mergeComparator
      ( orderByTypes("meat", "fruit", "dairy") // primary sort
      , orderByName                            // secondary sort (tie breaker)
      )
  )

// [ { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'vegetable', name: 'spinach' }
// ]

我们看到结果是按类型, 肉类水果乳制品优先的第一顺序。 我们还看到按name二级排序。 鸡肉猪肉的含量升高, 苹果覆盆子的含量也较高 请注意,即使orderByTypes中未使用"vegetables" ,次级排序仍然适用,因此芹菜菠菜是有序的。

如您所见,我们可以定义灵活的比较器函数,例如orderByTypesorderByName ,并使用mergeComparator对其进行mergeComparator以实现更为复杂和复杂的行为。 我们将从两者中的简单者开始,即orderByName

const orderByName =
  contramap
    ( ascending     // transform base comparator
    , x => x.name   // by first getting object's name property
    )

// base comparator
const ascending = (a, b) =>
  a > b
    ? 1
    : a < b
      ? -1
      : 0

// functional utility
const contramap = (f, g) =>
  (a, b) =>
    f(g(a), g(b))

orderByTypes比较器的作用更多一些-

const orderByTypes = (...types) =>
  contramap
    ( ascending                         // transform base comparator
    , pipe                              // using a function sequence
        ( x => x.type                   // first get the item's type property
        , x => matchIndex(types, x)     // then get the index of the matched type
        , x => x === -1 ? Infinity : x  // then if it doesn't match, put it at the end
        )
    )

// helper
const matchIndex = (values = [], query) =>
  values.findIndex(v => v === query)

// functional utility
const identity = x =>
  x

// functional utility
const pipe = (f = identity, ...more) =>
  more.reduce(pipe1, f)

// pipe helper
const pipe1 = (f, g) =>
  x => g(f(x))

我们定义了两(2)个独立的比较器orderByNameorderByTypes ,我们要做的最后一件事是确定如何组合它们-

const mergeComparator = (c = ascending, ...more) =>
  more.reduce(mergeComparator1, c)

// helper 1
const mergeComparator1 = (c1, c2) =>
  (a, b) =>
    mergeComparator2(c1(a, b), c2(a, b))

// helper 2
const mergeComparator2 = (a, b) =>
  a === 0 ? b : a

放在一起

好吧,让我们看看是否可以鞠躬-

const allRecords =
  [ { type: 'fruit', name: 'apple' }
  , { type: 'vegetable', name: 'spinach' }
  , { type: 'meat', name: 'chicken' }
  , { type: 'fruit', name: 'raspberry' }
  ]

const newRecords =
  [ { type: 'meat', name: 'pork' }
  , { type: 'fruit', name: 'pear' }
  , { type: 'vegetable', name: 'celery' }
  , { type: 'dairy', name: 'milk' }
  ]

// efficient grouping, can support any number of maps
const grouped = 
  mergeMap
    ( groupBy(x => x.type, allRecords)
    , groupBy(x => x.type, newRecords)
    )

const unsorted =
  [].concat(...grouped.values())

// efficient sorting; can support any number of comparators
const sorted =
  unsorted.sort
    ( mergeComparator
        ( orderByTypes("meat", "fruit", "dairy")
        , orderByName
        )
    )

输出量

console.log(sorted)

// [ { type: 'meat', name: 'chicken' }
// , { type: 'meat', name: 'pork' }
// , { type: 'fruit', name: 'apple' }
// , { type: 'fruit', name: 'pear' }
// , { type: 'fruit', name: 'raspberry' }
// , { type: 'dairy', name: 'milk' }
// , { type: 'vegetable', name: 'celery' }
// , { type: 'vegetable', name: 'spinach' }
// ]

展开下面的代码段,以在您自己的浏览器中验证结果-

 // --------------------------------------------------- // STEP 1 const upsert = (map, [ k, v ]) => map.has(k) ? map.set(k, map.get(k).concat(v)) : map.set(k, [].concat(v)) const groupBy = (f, a = []) => a.reduce ( (map, v) => upsert(map, [ f (v), v ]) , new Map ) const allRecords = [ { type: 'fruit', name: 'apple' } , { type: 'vegetable', name: 'spinach' } , { type: 'meat', name: 'chicken' } , { type: 'fruit', name: 'raspberry' } ] const newRecords = [ { type: 'meat', name: 'pork' } , { type: 'fruit', name: 'pear' } , { type: 'vegetable', name: 'celery' } , { type: 'dairy', name: 'milk' } ] const m1 = groupBy(x => x.type, allRecords) console.log("first grouping\\n", m1) // Map // { 'fruit' => // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'spinach' } // ] // , 'meat' => // [ { type: 'meat', name: 'chicken' } // ] // } const m2 = groupBy(x => x.type, newRecords) console.log("second grouping\\n", m2) // Map // { 'meat' => // [ { type: 'meat', name: 'pork' } // ] // , 'fruit' => // [ { type: 'fruit', name: 'pear' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'celery' } // ] // , 'dairy' => // [ { type: 'dairy', name: 'milk' } // ] // } // --------------------------------------------------- // STEP 2 const mergeMap1 = (m1, m2) => Array.from(m2.entries()).reduce(upsert, m1) const mergeMap = (...maps) => maps.reduce(mergeMap1, new Map) const m3 = mergeMap(m1, m2) console.log("merged grouping\\n", m3) // Map // { 'fruit' => // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // ] // , 'vegetable' => // [ { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // ] // , 'meat' => // [ { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // ] // , 'dairy' => // [ { type: 'dairy', name: 'milk' } // ] // } const unsorted = [].concat(...m3.values()) console.log("unsorted\\n", unsorted) // [ { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // , { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'dairy', name: 'milk' } // ] // --------------------------------------------------- // STEP 3 const ascending = (a, b) => a > b ? 1 : a < b ? -1 : 0 const contramap = (f, g) => (a, b) => f(g(a), g(b)) const orderByName = contramap(ascending, x => x.name) const sorted1 = unsorted.sort(orderByName) console.log("sorted by name only\\n", sorted1) // [ { type: 'fruit', name: 'apple' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'dairy', name: 'milk' } // , { type: 'fruit', name: 'pear' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'vegetable', name: 'spinach' } // ] // --------------------------------------------------- // STEP 4 const identity = x => x const pipe1 = (f, g) => x => g(f(x)) const pipe = (f = identity, ...more) => more.reduce(pipe1, f) const matchIndex = (values = [], query) => values.findIndex(v => v === query) const orderByTypes = (...types) => contramap ( ascending , pipe ( x => x.type , x => matchIndex(types, x) , x => x === -1 ? Infinity : x ) ) const sorted2 = unsorted.sort(orderByTypes("vegetable", "meat", "fruit")) console.log("sorted by types\\n", sorted2) // [ { type: 'vegetable', name: 'spinach' } // , { type: 'vegetable', name: 'celery' } // , { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'fruit', name: 'pear' } // , { type: 'dairy', name: 'milk' } // ] // --------------------------------------------------- // STEP 5 const mergeComparator = (c = ascending, ...more) => more.reduce(mergeComparator1, c) const mergeComparator1 = (c1, c2) => (a, b) => mergeComparator2(c1(a, b), c2(a, b)) const mergeComparator2 = (a, b) => a === 0 ? b : a const sorted3 = unsorted.sort ( mergeComparator ( orderByTypes("meat", "fruit", "dairy") , orderByName ) ) console.log("sorted by types, then name\\n", sorted3) // [ { type: 'meat', name: 'chicken' } // , { type: 'meat', name: 'pork' } // , { type: 'fruit', name: 'apple' } // , { type: 'fruit', name: 'pear' } // , { type: 'fruit', name: 'raspberry' } // , { type: 'dairy', name: 'milk' } // , { type: 'vegetable', name: 'celery' } // , { type: 'vegetable', name: 'spinach' } // ] 

请注意,如果您想查看地图内容,则需要打开浏览器的开发者控制台。

假设allRecords已经按type排序,那么具有任何特定type的值都位于数组的一个连续段中(或者该type在数组中根本不存在),那么以下内容将与Object.assign()非常相似Object.assign()

function spliceBy<T, K extends keyof T> (key: K, target: T[], ...sources: Iterable<T>[]) {
  const groups: Map<T[K], T[]> = new Map()

  for (const source of sources) {
    for (const entry of source) {
      const value = entry[key]
      const oldEntries = groups.get(value)
      const entries = oldEntries || []

      if (!oldEntries) groups.set(value, entries)

      entries.push(entry)
    }
  }

  for (const [value, entries] of groups) {
    // find the end of a group of entries
    let found = false
    const index = target.findIndex(
      entry => entry[key] === value ? (found = true, false) : found
    )

    if (found) target.splice(index, 0, ...entries)
    else target.push(...entries)
  }

  return target
}

const allRecords = [{type:'fruit',name:'apple'},{type:'vegetable',name:'celery'},{type:'meat',name:'chicken'}]
const newRecords = [{type:'fruit',name:'pear'},{type:'vegetable',name:'spinach'},{type:'meat',name:'pork'}]

console.log(spliceBy('type', allRecords, newRecords))

在线尝试!

如果您不想修改allRecords ,则可以这样调用它:

console.log(spliceBy('type', [], allRecords, newRecords))

这应该做的工作:

interface Record {
   type: string;
   name: string;
}

interface TypedRecords {
   [type: string]: records[];
}

private _recordsByType: TypedRecords = {};

sortAndInsert(allRecords: Record[], newRecords: Record[]): Record[] {
   const records: Record[] = [];
   this.insert(allRecords);
   this.insert(newRecords);
   Object.keys(this._recordsByType).forEach(type => {
      this._recordsByType[type].forEach(name => {
         records.push({type, name});
      });
   });

   return records;
}

private insert(records: Record[]) {
   records.forEach(record => {
      if (!this._recordsByType[record.type]) {
         this._recordsByType[record.type] = [];
      }
      this._recordsByType[record.type].push(record.value);
   });
}

不知道这是否是最佳解决方案性能,但是这里是:

const allRecords = [
  {
    type: 'fruit',
    name: 'apple'
  },
  {
    type: 'vegetable',
    name: 'celery'
  },
  {
    type: 'meat',
    name: 'chicken'
  }
]

const  newRecords = [
  {
    type: 'fruit',
    name: 'pear'
  },
  {
    type: 'vegetable',
    name: 'spinach'
  },
  {
    type: 'meat',
    name: 'pork'
  }
]

function sortAndInsert(...records){
    let totalRecords = [];
    for(let record of records){
        totalRecords = totalRecords.concat(record);
    }
    totalRecords.sort((rec1, rec2)=>{
        if(rec1.type == rec2.type)
            return 0;
        else if(rec1.type > rec2.type)
            return 1;
        else
            return -1;
    })
    return totalRecords;
}

let completeRecords = sortAndInsert(newRecords, allRecords);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM