根据另一个数组中的数据对数组进行排序

时间:2013-10-09 01:37:10

标签: javascript arrays sorting

我有两个对象数组,如下所示:

items = [{"id":"5","tobuy":"1","name":"pop"},
         {"id":"6","tobuy":"1","name":"fish"},
         {"id":"7","tobuy":"0","name":"soda"}]
pkgs =  [{"item_id":"5","store":"Market","aisle":"3"},
         {"item_id":"6","store":"Market","aisle":"2"},
         {"item_id":"6","store":"Dept","aisle":"8"},
         {"item_id":"7","store":"Market","aisle":"4"}]

我正在尝试对items数组进行排序,但我想利用pkgs数组中的数据 pkgs数组中的“item_id”字段对应于items数组中的“id”字段 例如,我想排序:

  
      
  • 首先按降序排列“tobuy”
  •   
  • 然后通过“store”
  •   
  • 然后是“aisle”
  •   
  • 然后通过“name”
  •   

虽然item_id和id在两个数组之间对应,但是没有1比1的关系。可能有0个或更多pkgs对应于任何给定项目。

(如果我有一个数据库,我只会加入表,但在JavaScript中我只有两个相关的数组。)

我不确定如何构建比较器函数并传入第二个数组。

感谢您的帮助。

3 个答案:

答案 0 :(得分:4)

也许是这样的?

items = items.map(function (item, index) {
    return {
        item: item,
        pkg: pkgs[index] //I assumed associated pkgs were at the same index
    };
}).sort(function (a, b) {
   var pkgA = a.pkg, pkgB = b.pkg, r;

   r = +b.item.tobuy - +a.item.tobuy;

   if (r !== 0) return r;

   r = pkgA.store < pkgB.store? -1 : (pkgA.store === pkgB.store? 0 : 1);

   if (r !== 0) return r;

   r = +pkgA.aisle - +pkgB.aisle;

   if (r !== 0) return r;

   return pkgA.name < pkgB.name? -1 : (pkgA.name === pkgB.name? 0 : 1);

}).map(function (item) {
    return item.item;
});

您还可以创建一个查找映射,以便直接从sort函数快速检索关联的包,而不是合并数据。

E.g。

var pkgsMap = pkgs.reduce(function (res, pkg) {
    res[pkg.item_id] = pkg;
    return res;
}, {});

然后在sort函数中你可以这样做:

var pkgA = pkgsMap[a.id], pkgB = pkgsMap[b.id];

修改

  

pkgs数组中实际上有另一个名为“ppu”的字段   是每单位的价格。最低的ppu是将要使用的ppu。

您可以使用以下内容构建包映射,然后使用sort函数中的映射来检索上述相关包,并实现排序算法。

var pkgsMap = pkgs.sort(function (a, b) {
    //not sure what ppu is so I sort it as a string
    return a.ppu < b.ppu? -1 : Number(a.ppu > b.ppu);
}).reduce(function (res, pkg) {
    if (!(pkg.item_id in res)) res[pkg.item_id] = pkg;
    return res;
}, {});

答案 1 :(得分:1)

创建一个生成比较器的函数,这看起来很笨重,但意味着您可以生成任何所需的排序顺序

function generateComparator(dict, index, order) {
    return function (a, b) {
        var i, key, direction,
            ai = a[index], av,
            bi = b[index], bv;
        for (i = 0; i < order.length; ++i) {
            key = order[i].key;
            direction = +!!order[i].reverse || -1;
            if (dict[ai].hasOwnProperty(key)) // if in dict, lookup
                av = dict[ai][key];
            else                              // else look up in item
                av = a[key];
            if (dict[bi].hasOwnProperty(key))
                bv = dict[ai][key];
            else
                bv = b[key];
            // console.log(i, key, av, bv, direction); // debug
            if (av === bv)
                continue;
            if (av < bv)
                return direction;
            return -direction;
        }
        return 0;
    };
}

Arrays 转换为字典

var dict = (function (arr, index) {
    var o = {}, i;
    for (i = 0; i < arr.length; ++i) {
        o[arr[i][index]] = arr[i];
    }
    return o;
}(pkgs, 'item_id'));

定义您的排序选择

var order = [
    {key: 'tobuy', reverse: 1},
    {key: 'store'},
    {key: 'aisle'},
    {key: 'name'}
];

使用字典生成比较器

var comparator = generateComparator(dict, 'id', order);

然后对您的第一个数组

进行排序
items.sort(comparator);
/* [
    {"id": "6", "tobuy": "1", "name": "fish"},
    {"id": "5", "tobuy": "1", "name": "pop"},
    {"id": "7", "tobuy": "0", "name": "soda"}
] */

答案 2 :(得分:1)

让我们考虑一下如何在SQL中执行此操作:

SELECT * FROM items INNER JOIN pkgs ON items.id = pkgs.item_id
ORDER BY tobuy DESC, store, aisle, name

following answer演示了如何在JavaScript中实现内连接和等连接:

function equijoin(primary, foreign, primaryKey, foreignKey, select) {
    var m = primary.length, n = foreign.length, index = [], c = [];

    for (var i = 0; i < m; i++) {     // loop through m items
        var row = primary[i];
        index[row[primaryKey]] = row; // create an index for primary table
    }

    for (var j = 0; j < n; j++) {     // loop through n items
        var y = foreign[j];
        var x = index[y[foreignKey]]; // get corresponding row from primary
        c.push(select(x, y));         // select only the columns you need
    }

    return c;
}

现在您可以使用equijoin加入itemspkgs,如下所示:

equijoin(items, pkgs, "id", "item_id", function (item, pkg) {
    return {
        id: +item.id,
        tobuy: +item.tobuy,
        store: pkg.store,
        aisle: +pkg.aisle,
        name: item.name
    };
});

请注意,我通过将一元item.id运算符应用于数字来强制item.tobuypkg.aisle+

现在我们加入了两个表,我们需要对它们进行排序。要对表进行排序,我们使用内置数组sort方法:

.sort(function (a, b) {
    // ORDER BY tobuy DESC

    var aTobuy = a.tobuy, bTobuy = b.tobuy;

    if (aTobuy < bTobuy) return 1;
    if (aTobuy > bTobuy) return -1;

    // ORDER BY store

    var aStore = a.store, bStore = b.store;

    if (aStore < bStore) return -1;
    if (aStore > bStore) return 1;

    // ORDER BY aisle

    var aAisle = a.aisle, bAisle = b.aisle;

    if (aAisle < bAisle) return -1;
    if (aAisle > bAisle) return 1;

    // ORDER BY name

    var aName = a.name, bName = b.name;

    if (aName < bName) return -1;
    if (aName > bName) return 1;

    // keep them unchanged

    return a.id - b.id;
});

sort方法是unstable(即它可能不会保留输入列表中具有相等排序值的项目的排序)。因此,要解决此限制,我们将a.id - b.id作为最后一个语句返回。

另请注意,我们正在使用<>比较所有值(字符串或数字)。字符串按字典顺序进行比较,而数字则按数字进行比较。

汇总代码如下:

var table = equijoin(items, pkgs, "id", "item_id", function (item, pkg) {
    return {
        id: +item.id,
        tobuy: +item.tobuy,
        store: pkg.store,
        aisle: +pkg.aisle,
        name: item.name
    };
}).sort(function (a, b) {
    var aTobuy = a.tobuy, bTobuy = b.tobuy;

    if (aTobuy < bTobuy) return 1;
    if (aTobuy > bTobuy) return -1;

    var aStore = a.store, bStore = b.store;

    if (aStore < bStore) return -1;
    if (aStore > bStore) return 1;

    var aAisle = a.aisle, bAisle = b.aisle;

    if (aAisle < bAisle) return -1;
    if (aAisle > bAisle) return 1;

    var aName = a.name, bName = b.name;

    if (aName < bName) return -1;
    if (aName > bName) return 1;

    return a.id - b.id;
});

不像SQL那么简洁吗?无论如何,请亲自查看演示:http://jsfiddle.net/7ZG96/


修改:如果您只想要idtobuyname列,则可以使用{{1}从排序表中提取它如下:

map

这对应于以下SQL查询:

table.map(function (item) {
    return {
        id: item.id,
        tobuy: item.tobuy,
        name: item.name
    };
});

请参阅更新后的演示:http://jsfiddle.net/7ZG96/1/