簡體   English   中英

有什么辦法可以“加入”兩個 8822996504788 arrays 的內容,就像我加入 SQL 一樣

[英]Is there some way I can "join" the contents of two javascript arrays much like I would do a join in SQL

我有兩個 arrays:Question 和 UserProfile

  • userProfiles : [] 數組包含{ id, name }對象
  • questions :[] array contains { id, text, createdBy } objects

問題中的createdBy integer 始終是userProfiles中的 id 值之一。

有沒有一種方法可以“加入”arrays,就像我使用數據庫時加入兩個 SQL 表一樣。

作為最終結果,我需要的是一個包含

{ id, text, name }

對應的 SQL 為:

SELECT u.id, q.text, u.name 
FROM userProfiles u 
JOIN questions q ON q.createdBy=u.id

我認為你想要的是一個內部連接,它很簡單,可以在 JavaScript 中實現:

const innerJoin = (xs, ys, sel) =>
    xs.reduce((zs, x) =>
    ys.reduce((zs, y) =>        // cartesian product - all combinations
    zs.concat(sel(x, y) || []), // filter out the rows and columns you want
    zs), []);

出於演示目的,我們將使用以下數據集(謝謝@AshokDamani):

const userProfiles = [
    {id: 1, name: "Ashok"},
    {id: 2, name: "Amit"},
    {id: 3, name: "Rajeev"},
];

const questions = [
    {id: 1, text: "text1", createdBy: 2},
    {id: 2, text: "text2", createdBy: 2},
    {id: 3, text: "text3", createdBy: 1},
    {id: 4, text: "text4", createdBy: 2},
    {id: 5, text: "text5", createdBy: 3},
    {id: 6, text: "text6", createdBy: 3},
];

這是你將如何使用它:

const result = innerJoin(userProfiles, questions,
    ({id: uid, name}, {id, text, createdBy}) =>
        createdBy === uid && {id, text, name});

在 SQL 術語中,這類似於:

SELECT questions.id, questions.text, userProfiles.name
FROM userProfiles INNER JOIN questions
ON questions.createdBy = userProfiles.id;

把它們放在一起:

 const innerJoin = (xs, ys, sel) => xs.reduce((zs, x) => ys.reduce((zs, y) => // cartesian product - all combinations zs.concat(sel(x, y) || []), // filter out the rows and columns you want zs), []); const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; const result = innerJoin(userProfiles, questions, ({id: uid, name}, {id, text, createdBy}) => createdBy === uid && {id, text, name}); console.log("Open your browser console to see the output."); console.table(result);


編輯:但這不是最好的解決方案。 由於上述解決方案循環通過笛卡爾積,因此運行需要O(m × n)時間。 通過一些修改,我們可以讓它在O(m + n)時間內運行 - @pebbl 首先找到它

const equijoin = (xs, ys, primary, foreign, sel) => {
    const ix = xs.reduce((ix, row) => // loop through m items
        ix.set(row[primary], row),    // populate index for primary table
    new Map);                         // create an index for primary table

    return ys.map(row =>              // loop through n items
        sel(ix.get(row[foreign]),     // get corresponding row from primary
        row));                        // select only the columns you need
};

現在您可以按如下方式使用它:

const result = equijoin(userProfiles, questions, "id", "createdBy",
    ({name}, {id, text}) => ({id, text, name}));

把它們放在一起:

 const equijoin = (xs, ys, primary, foreign, sel) => { const ix = xs.reduce((ix, row) => ix.set(row[primary], row), new Map); return ys.map(row => sel(ix.get(row[foreign]), row)); }; const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; const result = equijoin(userProfiles, questions, "id", "createdBy", ({name}, {id, text}) => ({id, text, name})); console.log("Open your browser console to see the output."); console.table(result);

這似乎是一個重要的通用問題,雖然有很多答案,但有些具有邊緣行為,例如修改現有數據,解決與手頭問題完全不同的問題,使用高達 93,057 字節的 JavaScript(更不用說產生錯誤的結果),產生過於復雜的額外數據結構嵌套,每次調用都需要大量代碼,最嚴重的是,它不是解決這個問題核心的更重要的通用問題的獨立解決方案。

因此,無論好壞,我編寫了一個 shim,它使用一個方法.joinWith擴展 JavaScript Array對象,該方法旨在by公共索引字段將this對象數組與that對象數組連接起來。 可以select輸出中所需的字段列表(當只需要幾個字段時,適用於合並具有多個字段的對象數組)或omit輸出中的字段列表(適用於需要大多數字段時合並對象數組但有一些不是)。

填充代碼看起來並不漂亮,所以它會在最后,首先是如何將它用於 OP 的特定類型的數據的示例:

/* this line will produce the array of objects as desired by the OP */
joined_objects_array = userProfiles.joinWith(questions, 'id', ['createdBy'], 'omit');

/* edit: I just want to make 100% sure that this solution works for you, i.e.,
 *       does exactly what you need. I haven't seen your actual data, so it's
 *       possible that your IDs are are not in common, (i.e., your createdBy
 *       is in common like you said, but not the IDs, and if so you could
 *       morph your data first like this:
 * questions.map(function(x) { x.id = x.createdBy; });
 *       before joining the arrays of objects together.
 *
 */

以下代碼用於演示:

var array1 = [{ id: 3124, name: 'Mr. Smith' },
              { id: 710, name: 'Mrs. Jones' }];
var array2 = [{ id: 3124, text: 'wow', createdBy: 'Mr. Jones' },
              { id: 710, text: 'amazing' }];

var results_all = array1.joinWith(array2, 'id');

// [{id:3124, name:"Mr. Smith", text:"wow", createdBy:"Mr. Jones"},
// {id:710, name:"Mrs. Jones", text:"amazing"}]*

var results_selected = array1.joinWith(array2, 'id', ['id', 'text', 'name']);

// [{id:3124, name:"Mr. Smith", text:"wow"},
// {id:710, name:"Mrs. Jones", text:"amazing"}]*

/* or equivalently, */
var results_omitted = array1.joinWith(array2, 'id', ['createdBy'], 1);

// [{id:3124, name:"Mr. Smith", text:"wow"},
// {id:710, name:"Mrs. Jones", text:"amazing"}]*

這個解決方案還有一些其他的好處(其中之一是保留通過索引鍵訪問結果數據的能力,盡管返回一個數組)。

享受!

/* Array.joinWith - shim by Joseph Myers 7/6/2013 */


if (!Array.prototype.joinWith) {
    +function () {
        Array.prototype.joinWith = function(that, by, select, omit) {
            var together = [], length = 0;
            if (select) select.map(function(x){select[x] = 1;});
            function fields(it) {
                var f = {}, k;
                for (k in it) {
                    if (!select) { f[k] = 1; continue; }
                    if (omit ? !select[k] : select[k]) f[k] = 1;
                }
                return f;
            }
            function add(it) {
                var pkey = '.'+it[by], pobj = {};
                if (!together[pkey]) together[pkey] = pobj,
                    together[length++] = pobj;
                pobj = together[pkey];
                for (var k in fields(it))
                    pobj[k] = it[k];
            }
            this.map(add);
            that.map(add);
            return together;
        }
    }();
}

文檔:

        /* this and that both refer to an array of objects, each containing
           object[by] as one of their fields */
        /*
         N.B. It is the responsibility of the user of this method
         to ensure that the contents of the [by] fields are
         consistent with each other between the two arrays!
        */
        /* select is an array of field names to be included in the resulting
           objects--all other fields will be excluded, or, if the Boolean value
           of omit evaluates to true, then select is an array of field names to
           be excluded from the resulting objects--all others will be included.
        */

我幾乎總是使用underscore.js,因為它對數組和“map reduce”有很好的支持,可以解決這個問題。

這是針對您的問題的解決方案(假設每個用戶只有一個問題,正如您的原始帖子所建議的那樣)

http://jsfiddle.net/x5Z7f/

(打開瀏覽器控制台查看輸出)

    var userProfiles = [{ id:'1', name:'john' }, { id:'2', name:'mary' }];

var questions =[ { id:'1', text:'question john', createdBy:'1' }, { id:'2', text:'question mary', createdBy:'2' }];

var rows = _.map(userProfiles, function(user){ 
    var question = _.find(questions, function(q){ return q.createdBy == user.id });
    user.text = question? question.text:'';
    return user; 
})

_.each(rows, function(row){ console.log(row) });

上面的答案假設您使用 id == createdBy 作為連接列。

如果是我,我會通過以下方式解決這個問題:

設置:

var userProfiles = [], questions = [];

userProfiles.push( {id:1, name:'test'} );
userProfiles.push( {id:2, name:'abc'} );
userProfiles.push( {id:3, name:'def'} );
userProfiles.push( {id:4, name:'ghi'} );

questions.push( {id:1, text:'monkey', createdBy:1} );
questions.push( {id:2, text:'Monkey', createdBy:1} );
questions.push( {id:3, text:'big',    createdBy:2} );
questions.push( {id:4, text:'string', createdBy:2} );
questions.push( {id:5, text:'monKey', createdBy:3} );

首先,將創建一個查找對象,其中鏈接 ID 用作鍵

var createObjectLookup = function( arr, key ){
  var i, l, obj, ret = {};
  for ( i=0, l=arr.length; i<l; i++ ) {
    obj = arr[i];
    ret[obj[key]] = obj;
  }
  return ret;
};

var up = createObjectLookup(userProfiles, 'id');

現在你有了這個,應該很容易逐步完成問題,並找到要合並的用戶對象:

var i, l, question, user, result = [];
for ( i=0, l=questions.length; i<l; i++ ) {
  if ( (question = questions[i]) && (user = up[question.createdBy]) ) {
    result.push({
      id: question.id,
      text: question.text,
      name: user.name
    });
  }
}

您現在應該擁有所需的一切result

console.log(result);

這是我嘗試以某種方式制定通用解決方案。 我在這里使用Array.mapArray.index方法:

var arr1 = [
    {id: 1, text:"hello", oid:2},
    {id: 2, text:"juhu", oid:3},
    {id: 3, text:"wohoo", oid:4},
    {id: 4, text:"yeehaw", oid:1}
];
var arr2 = [
    {id: 1, name:"yoda"},
    {id: 2, name:"herbert"},
    {id: 3, name:"john"},
    {id: 4, name:"walter"},
    {id: 5, name:"clint"}
];

function merge(arr1, arr2, prop1, prop2) {
    return arr1.map(function(item){
        var p = item[prop1];
        el = arr2.filter(function(item) {
            return item[prop2] === p;
        });
        if (el.length === 0) {
            return null;
        }
        var res = {};
        for (var i in item) {
            if (i !== prop1) {
                res[i] = item[i];
            }
        }
        for (var i in el[0]) {
            if (i !== prop2) {
                res[i] = el[0][i];
            }
        }
        return res;
    }).filter(function(el){
        return el !== null;
    });
}

var res = merge(arr1, arr2, "oid", "id");
console.log(res);

所以基本上你可以為每個數組定義兩個數組和一個屬性,這樣 prop1 將被替換為 array2 中一個項目的所有屬性,其 prop2 等於 prop1。

在這種情況下,結果將是:

var res = [
    {id: 1, text:"hello", name:"herbert"},
    {id: 2, text:"juhu", name:"john"},
    {id: 3, text:"wohoo", name:"walter"},
    {id: 4, text:"yeehaw", name:"yoda"}
];

請注意,如果有多個匹配項,將使用第一個項目,如果沒有匹配項,則該對象將從結果數組中刪除。

小提琴

你想要的ResultArray下面計算的ResultArray

    var userProfiles1= new Array(1, "ashok");
    var userProfiles2= new Array(2, "amit");
    var userProfiles3= new Array(3, "rajeev");

    var UArray = new Array(userProfiles1, userProfiles2, userProfiles3);

    var questions1= new Array(1, "text1", 2);
    var questions2= new Array(2, "text2", 2);
    var questions3= new Array(3, "text3", 1);
    var questions4= new Array(4, "text4", 2);
    var questions5= new Array(5, "text5", 3);
    var questions6= new Array(6, "text6", 3);

    var QArray = new Array(questions1, questions2, questions3, questions4, questions5, questions6);

    var ResultArray = new Array();

    for (var i=0; i<UArray.length; i++)
    {
        var uid = UArray[i][0];
        var name = UArray[i][1];

        for(var j=0; j<QArray.length; j++)
        {
            if(uid == QArray[j][2])
            {
                 var qid = QArray[j][0]
                 var text = QArray[j][1];

                 ResultArray.push(qid +"," + text +","+ name)
            }
        }    
    }

for(var i=0; i<ResultArray.length; i++)
    {
        document.write(ResultArray[i] + "<br>")
    }

演示: http : //jsfiddle.net/VqmVv/

只是想分享一些通用代碼:

// Create a cartesian product of the arguments.
// product([1,2],['a','b'],['X']) => [[1,"a","X"],[1,"b","X"],[2,"a","X"],[2,"b","X"]]
// Accepts any number of arguments.
product = function() {
    if(!arguments.length)
        return [[]];
    var p = product.apply(null, [].slice.call(arguments, 1));
    return arguments[0].reduce(function(r, x) {
        return p.reduce(function(r, y) {
            return r.concat([[x].concat(y)]);
        }, r);
    }, []);
}

你的問題:

result = product(userProfiles, questions).filter(function(row) {
    return row[0].id == row[1].createdBy;
}).map(function(row) {
    return {
        userName: row[0].name,
        question: row[1].text
    }
})

您可以使用reducemap來做到這一點。

首先,創建從 ID 到名稱的映射:

var id2name = userProfiles.reduce(function(id2name, profile){
    id2name[profile.id] = profile.name;
    return id2name;
}, {});

其次,創建一個新的問題數組,但使用創建問題的用戶名代替他們的 ID:

var qs = questions.map(function(q){
    q.createdByName = id2name[q.createdBy];
    delete q.createdBy;
    return q;
});

在 JavaScript 中執行SQL 連接的一種簡單方法:

 let userProfiles = [ { id: 3, name: "Paquito"}, { id: 2, name: "Jaime" } ]; let questions = [ { id: 22, text: "My question", createdBy: 3 }, { id: 44, text: "Other question", createdBy: 5 } ]; let left_join = questions .map ( q => ({ ...userProfiles.find( u => q.createdBy === u.id ), ...q }) ); document.write("<p>Left join: <br>", JSON.stringify(left_join)); let right_join = userProfiles .map ( u => ({ ...questions.find( q => q.createdBy === u.id ), ...u }) );; document.write("</p><p>Right join: <br>", JSON.stringify(right_join)); let inner_join = questions .filter( q => userProfiles.find( u => q.createdBy === u.id ) ) .map ( q => ({ ...userProfiles.find( u => q.createdBy === u.id ), ...q }) ); document.write("</p><p>Inner join: <br>", JSON.stringify(inner_join));

我不知道任何允許這樣做的內置函數。

你可以編寫自己的函數,類似於這個 jsFiddle

var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}];
    var questions = [
        {id:1, text:'text1', createdBy:'foo'},
        {id:1, text:'text2', createdBy:'bar'},
        {id:2, text:'text3', createdBy:'foo'}];

    merged = mergeMyArrays(userProfiles,questions);

    console.log(merged);
    /**
     * This will give you an array like this:
     * [{id:1, name:name1, text:text1}, {...]
     * params : 2 arrays to merge by id
     */
    function mergeMyArrays(u,q){
        var ret = [];
        for(var i = 0, l = u.length; i < l; i++){
            var curU = u[i];
            for(var j = 0, m = q.length; j<m; j++){
                if(q[j].id == curU.id){
                    ret.push({
                        id: curU.id,
                        name: curU.name,
                        text: q[j].text
                    });
                }
            }
        }
        return ret;
    }

或者,如果您想要更好的“加入”(SQL-y):

var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}];
var questions = [
    {id:1, text:'text1', createdBy:'foo'},
    {id:1, text:'text2', createdBy:'bar'},
    {id:2, text:'text3', createdBy:'foo'}];

merged = mergeMyArrays(userProfiles,questions);

console.log(merged);
/**
 * This will give you an array like this:
 * [{id:1, name:name1, questions:[{...}]]
 * params : 2 arrays to merge by id
 */
function mergeMyArrays(u,q){
    var ret = [];
    for(var i = 0, l = u.length; i < l; i++){
        var curU = u[i],
            curId = curU.id,
            tmpObj = {id:curId, name:curU.name, questions:[]};
        for(var j = 0, m = q.length; j<m; j++){
            if(q[j].id == curId){
                tmpObj.questions.push({
                    text: q[j].text,
                    createdBy: q[j].createdBy
                });
            }
        }
        ret.push(tmpObj);
    }
    return ret;
}

就像在這個 jsFiddle

這很容易用StrelkiJS完成

var userProfiles = new StrelkiJS.IndexedArray();
userProfiles.loadArray([
    {id: 1, name: "Ashok"},
    {id: 2, name: "Amit"},
    {id: 3, name: "Rajeev"}
]);

var questions = new StrelkiJS.IndexedArray();
questions.loadArray([
    {id: 1, text: "text1", createdBy: 2},
    {id: 2, text: "text2", createdBy: 2},
    {id: 3, text: "text3", createdBy: 1},
    {id: 4, text: "text4", createdBy: 2},
    {id: 5, text: "text5", createdBy: 3},
    {id: 6, text: "text6", createdBy: 3}
]);

var res=questions.query([{
    from_col:  "createdBy", 
    to_table:  userProfiles, 
    to_col:    "id", 
    type:      "outer"
}]);

結果將是:

[
 [
  {"id":1,"text":"text1","createdBy":2},
  {"id":2,"name":"Amit"}
 ],
 [
  {"id":2,"text":"text2","createdBy":2},
  {"id":2,"name":"Amit"}
 ],
 [
  {"id":3,"text":"text3","createdBy":1},
  {"id":1,"name":"Ashok"}
 ],
 [
  {"id":4,"text":"text4","createdBy":2},
  {"id":2,"name":"Amit"}
 ],
 [
  {"id":5,"text":"text5","createdBy":3},
  {"id":3,"name":"Rajeev"}
 ],
 [
  {"id":6,"text":"text6","createdBy":3},
  {"id":3,"name":"Rajeev"}
 ]
]

Aadit 的第二個 O(m+n) 解決方案在我看來簡潔、實用且高效。 但是,當我的用例需要多個鍵匹配時,它沒有實現記錄的重復,所以我寫了這個輕微的變化:

function equijoin(arrL,arrR,keyL,keyR=keyL){
        const idx = arrL.reduce(
                (idx, objL) => 
                        objL[keyL] === undefined
                        ?idx
                        :idx.set(objL[keyL], [...(idx.get(objL[keyL])||[]), ...[objL]]) 
                ,new Map
            )
        const matches = 
                arrR
                .map(objR =>
                        objR[keyR] === undefined
                        ? []
                        : idx.get(objR[keyR])
                                .map(objL => ({l:objL, r:objR}))
                                .reduce((a,b)=>a.concat(b),[])
                    )
                .reduce((a,b)=>a.concat(b),[])
        return matches
}

(人為的)示例:

var players =[
    {id:"Alice", team:"Red"},
    {id:"Bob", team:"Red"},
    {id:"Clair", team:"Blue"},
    {id:"Dave"},
    {id:"Elliot"}
];

equijoin(players,players,"team")

[
{l:{id:"Alice",team:"Red"},r:{id:"Alice",team:"Red"}},
{l:{id:"Bob",team:"Red"},r:{id:"Alice",team:"Red"}},
{l:{id:"Alice",team:"Red"},r:{id:"Bob",team:"Red"}},
{l:{id:"Bob",team:"Red"},r:{id:"Bob",team:"Red"}},
{l:{id:"Clair",team:"Blue"},r:{id:"Clair",team:"Blue"}}
]

添加到 Aadit M Shah 的 equijoin 中,有人要求它類似於左連接。 但是由於該方法不是連接,而是直接的 1 到 1 等於,所以該方法不完全是左連接,更像是一個等值連接,如果不匹配則使用默認值。 然而,為了實現這一點並且閱讀它是有意義的,我顛倒了這個方法,因為它實際上是反向加入的。

const equijoinWithDefault = (xs, ys, primary, foreign, sel, def) => {
  const iy = ys.reduce((iy, row) => iy.set(row[foreign], row), new Map);
  return xs.map(row => typeof iy.get(row[primary]) !== 'undefined' ? sel(row, iy.get(row[primary])): sel(row, def));
};

示例調用:

const userProfiles = [
  {id: 1, name: "Ashok"},
  {id: 2, name: "Amit"},
  {id: 3, name: "Rajeev"},
];

const questions2 = [
  {id: 1, text: "text1", createdBy: 2},
  {id: 2, text: "text2", createdBy: 2},
  {id: 3, text: "text3", createdBy: 1},
  {id: 4, text: "text4", createdBy: 2},
  {id: 5, text: "text5", createdBy: 3},
  {id: 6, text: "text6", createdBy: 3},
  {id: 7, text: "text7", createdBy: 4},
];

let result2 = equijoinWithDefault(questions2, userProfiles, "createdBy", "id", ({id, text}, {name}) => ({id, text, name}), {name:null});
Thats another way

var array1 = [{ id: 1, name: 'Khikmat'},
              { id: 2, name: 'User'}];
var array2 = [{ id: 1, text: 'hi test' },
              { id: 2, text: 'amazing test' }, { id: 2, text: 'test'}];
 
 var result = array1.map((elem) => {
   elem["complaints"] = array2.filter((val) => {
       return val.id === elem.id; 
   });
   return elem;
 });

 
console.log(JSON.stringify(result))

@aadit-m-shah 的回答的 TypeScript 版本:

export const equiJoin = <T, U, K>(
    xs: T[],
    ys: U[],
    primaryPredicate: (v: T) => unknown,
    foreignPredicate: (v: U) => unknown,
    selectorFn: (value: T|undefined, row: U) => K) =>
{
    const ix = xs.reduce<Map<unknown, T>>(
        (acc, row) => acc.set(primaryPredicate(row), row),
        new Map
    );
    
    return ys.map((row) => selectorFn(ix.get(foreignPredicate(row)), row));
};

如何使用:

const userProfiles = [
    {id: 1, name: "Ashok"},
    {id: 2, name: "Amit"},
    {id: 3, name: "Rajeev"},
];

const questions = [
    {id: 1, text: "text1", createdBy: 2},
    {id: 2, text: "text2", createdBy: 2},
    {id: 3, text: "text3", createdBy: 1},
    {id: 4, text: "text4", createdBy: 2},
    {id: 5, text: "text5", createdBy: 3},
    {id: 6, text: "text6", createdBy: 3},
];

const joinedArrays = equiJoin(
    userProfiles,
    questions,
    (profile) => profile.id,
    (q) => q.createdBy,
    (p, {id, text}) => ({ id, text, name: p?.name || 'Unknown' })
);

您可以先使用 jQuery.merge() 然后使用 jQuery.unique() 來實現這一點。 merge() 將添加一個數組中的所有項目,而 unique() 將從該數組中刪除重復項。

http://api.jquery.com/jQuery.merge/

http://api.jquery.com/jQuery.unique/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM