جاوااسکریپت - ادغام دو آرایه از اشیاء بر اساس یک کلید


من دو آرایه دارم:

آرایه ۱:

[
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
]

و آرایه ۲:

[
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
]

من نیاز دارم این دو آرایه را بر اساس id ادغام کنم و این را دریافت کنم:

[
  { id: "abdc4051", date: "2017-01-24", name: "ab" },
  { id: "abdc4052", date: "2017-01-22", name: "abc" }
]

چطور میتوانم این کار را بدون تکرار از طریق Object.keys انجام دهم؟

پاسخ شماره ۱

میتوانی اینطور انجامش بدهی -

let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

let arr3 = arr1.map((item, i) => Object.assign({}, item, arr2[i]));

console.log(arr3);

اگر arr1 و arr2 به ترتیب متفاوت هستند، از کد زیر استفاده کنید:

let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...(arr2.find((itmInner) => itmInner.id === arr1[i].id))}
  );
}

console.log(merged);

از این استفاده کنید اگر arr1 و arr2 به یک ترتیب هستند

let arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
  { id: "abdc4051", name: "ab" },
  { id: "abdc4052", name: "abc" }
];

let merged = [];

for(let i=0; i<arr1.length; i++) {
  merged.push({
   ...arr1[i], 
   ...arr2[i]
  });
}

console.log(merged);

پاسخ شماره ۲

این راهحل حتی زمانی که آرایههای ادغامشده اندازههای متفاوتی دارند نیز قابل استفاده است. همچنین، حتی اگر کلیدهای مطابق نامهای متفاوتی داشته باشند.

دو آرایه را با استفاده از یک Map به صورت زیر ترکیب کنید:

const arr1 = [
  { id: "abdc4051", date: "2017-01-24" }, 
  { id: "abdc4052", date: "2017-01-22" },
  { id: "abdc4053", date: "2017-01-22" }
];
const arr2 = [
  { nameId: "abdc4051", name: "ab" },
  { nameId: "abdc4052", name: "abc" }
];

const map = new Map();
arr1.forEach(item => map.set(item.id, item));
arr2.forEach(item => map.set(item.nameId, {...map.get(item.nameId), ...item}));
const mergedArr = Array.from(map.values());

console.log(JSON.stringify(mergedArr));
.as-console-wrapper { max-height: 100% !important; top: 0; }

قطعه کد پشته را اجرا کنید تا نتیجه را ببینید:

[
  {
    "id": "abdc4051",
    "date": "2017-01-24",
    "nameId": "abdc4051",
    "name": "ab"
  },
  {
    "id": "abdc4052",
    "date": "2017-01-22",
    "nameId": "abdc4052",
    "name": "abc"
  },
  {
    "id": "abdc4053",
    "date": "2017-01-22"
  }
]

پاسخ شماره ۳

شما میتوانید این را در یک خط انجام دهید

let arr1 = [
    { id: "abdc4051", date: "2017-01-24" },
    { id: "abdc4052", date: "2017-01-22" }
];

let arr2 = [
    { id: "abdc4051", name: "ab" },
    { id: "abdc4052", name: "abc" }
];

const mergeById = (a1, a2) =>
    a1.map(itm => ({
        ...a2.find((item) => (item.id === itm.id) && item),
        ...itm
    }));

console.log(mergeById(arr1, arr2));
  1. اعمال تابع map بر روی آرایه۱
  2. جستجو در array2 برای array1.id
  3. اگر آن را پیدا کردی ...نتیجهی array2 را در array1 پخش کن

آرایه نهایی تنها شامل شناسههایی خواهد بود که در هر دو آرایه مطابقت دارند

پاسخ شماره ۴

در اینجا یک راهحل O(n) با استفاده از reduce و Object.assign آورده شده است

const joinById = ( ...lists ) =>
    Object.values(
        lists.reduce(
            ( idx, list ) => {
                list.forEach( ( record ) => {
                    if( idx[ record.id ] )
                        idx[ record.id ] = Object.assign( idx[ record.id ], record)
                    else
                        idx[ record.id ] = record
                } )
                return idx
            },
            {}
        )
    )

برای استفاده از این تابع در مورد OP، آرایههایی را که میخواهید به هم بپیوندند به joinById بدهید (توجه داشته باشید که lists یک پارامتر باقیمانده است).

let joined = joinById(list1, list2)

هر فهرست به یک شیء واحد کاهش مییابد که در آن کلیدها شناسهها و مقادیر همان اشیاء هستند. اگر در کلید دادهشده مقداری وجود داشته باشد، روی آن و رکورد فعلی تابع object.assign فراخوانی میشود.

در اینجا راهحل عمومی O(n*m) آمده است، که در آن n تعداد رکوردها و m تعداد کلیدها است. این تنها برای کلیدهای شیء معتبر کار خواهد کرد. اگر نیاز دارید، میتوانید هر مقدار را به base64 تبدیل کرده و از آن استفاده کنید.

const join = ( keys, ...lists ) =>
    lists.reduce(
        ( res, list ) => {
            list.forEach( ( record ) => {
                let hasNode = keys.reduce(
                    ( idx, key ) => idx && idx[ record[ key ] ],
                    res[ 0 ].tree
                )
                if( hasNode ) {
                    const i = hasNode.i
                    Object.assign( res[ i ].value, record )
                    res[ i ].found++
                } else {
                    let node = keys.reduce( ( idx, key ) => {
                        if( idx[ record[ key ] ] )
                            return idx[ record[ key ] ]
                        else
                            idx[ record[ key ] ] = {}
                        return idx[ record[ key ] ]
                    }, res[ 0 ].tree )
                    node.i = res[ 0 ].i++
                    res[ node.i ] = {
                        found: 1,
                        value: record
                    }
                }
            } )
            return res
        },
        [ { i: 1, tree: {} } ]
         )
         .slice( 1 )
         .filter( node => node.found === lists.length )
         .map( n => n.value )

این اساساً همانند روش joinById است، با این تفاوت که یک شیء شاخص برای شناسایی رکوردهایی که باید متصل شوند نگه میدارد. رکوردها در یک آرایه ذخیره میشوند و شاخص موقعیت رکورد برای مجموعه کلید داده شده و تعداد لیستهایی که در آنها پیدا شده است را ذخیره میکند.

هر بار که همان مجموعه کلید برخورد میشود، گره را در درخت پیدا میکند، عنصر موجود در ایندکس آن را بهروزرسانی میکند، و تعداد دفعاتی که پیدا شده افزایش مییابد.

پس از الحاق، شیء idx با استفاده از slice از آرایه حذف میشود و هر عنصری که در هر مجموعه یافت نشده باشد، حذف میشود. این باعث میشود که یک اتصال داخلی باشد، میتوانید این فیلتر را حذف کنید و یک اتصال خارجی کامل داشته باشید.

در نهایت هر عنصر به مقدار خود نگاشته میشود و شما آرایههای متصل شده را خواهید داشت.

پاسخ شماره ۵

شما میتوانید از تعداد دلخواهی از آرایهها استفاده کنید و روی همان ایندکس اشیاء جدید را نگاشته کنید.

var array1 = [{ id: "abdc4051", date: "2017-01-24" }, { id: "abdc4052", date: "2017-01-22" }],
    array2 = [{ id: "abdc4051", name: "ab" }, { id: "abdc4052", name: "abc" }],
    result = [array1, array2].reduce((a, b) => a.map((c, i) => Object.assign({}, c, b[i])));
    
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

پاسخ شماره ۶

اگر دو آرایه دارید که باید بر اساس مقادیر با هم ادغام شوند حتی اگر ترتیب آنها متفاوت باشد

let arr1 = [
    { id:"1", value:"this", other: "that" },
    { id:"2", value:"this", other: "that" }
];

let arr2 = [
    { id:"2", key:"val2"},
    { id:"1", key:"val1"}
];

تو میتوانی اینطور انجام بدهی

const result = arr1.map(item => {
    const obj = arr2.find(o => o.id === item.id);
    return { ...item, ...obj };
  });

console.log(result);

پاسخ شماره ۷

برای ادغام دو آرایه بر اساس id، با فرض اینکه طول آرایهها برابر است:

arr1.map(item => ({
    ...item,
    ...arr2.find(({ id }) => id === item.id),
}));