簡體   English   中英

如何按多個字段對對象數組進行排序?

[英]How to sort an array of objects by multiple fields?

從這個原始問題,我將如何對多個字段應用排序?

使用這種稍微調整過的結構,我將如何對城市(升序)和價格(降序)進行排序?

var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

我喜歡這個事實,而不是給出了提供一般方法的答案 在我計划使用此代碼的地方,我將不得不對日期和其他內容進行排序。 “啟動”object 的能力似乎很方便,即使不是有點麻煩。

我試圖將這個答案構建成一個很好的通用示例,但我運氣不佳。

您可以使用鏈式排序方法,獲取值的增量,直到它達到不等於零的值。

 var data = [{ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500" }, { h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250" }, { h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699" }, { h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500" }]; data.sort(function (a, b) { return a.city.localeCompare(b.city) || b.price - a.price; }); console.log(data);
 .as-console-wrapper { max-height: 100% !important; top: 0; }

或者,使用 es6,簡單地:

data.sort((a, b) => a.city.localeCompare(b.city) || b.price - a.price);

對於您的確切問題的非通用,簡單的解決方案:

homes.sort(
   function(a, b) {          
      if (a.city === b.city) {
         // Price is only important when cities are the same
         return b.price - a.price;
      }
      return a.city > b.city ? 1 : -1;
   });

基於此答案的多維排序方法:

更新:這是一個“優化”版本。 它做了更多的預處理,並預先為每個排序選項創建了一個比較函數。 它可能需要更多的內存(因為它為每個排序選項存儲一個函數,但它應該執行得更好一些,因為它不必在比較期間確定正確的設置。雖然我沒有做任何分析。

var sort_by;

(function() {
    // utility functions
    var default_cmp = function(a, b) {
            if (a == b) return 0;
            return a < b ? -1 : 1;
        },
        getCmpFunc = function(primer, reverse) {
            var dfc = default_cmp, // closer in scope
                cmp = default_cmp;
            if (primer) {
                cmp = function(a, b) {
                    return dfc(primer(a), primer(b));
                };
            }
            if (reverse) {
                return function(a, b) {
                    return -1 * cmp(a, b);
                };
            }
            return cmp;
        };

    // actual implementation
    sort_by = function() {
        var fields = [],
            n_fields = arguments.length,
            field, name, reverse, cmp;

        // preprocess sorting options
        for (var i = 0; i < n_fields; i++) {
            field = arguments[i];
            if (typeof field === 'string') {
                name = field;
                cmp = default_cmp;
            }
            else {
                name = field.name;
                cmp = getCmpFunc(field.primer, field.reverse);
            }
            fields.push({
                name: name,
                cmp: cmp
            });
        }

        // final comparison function
        return function(A, B) {
            var a, b, name, result;
            for (var i = 0; i < n_fields; i++) {
                result = 0;
                field = fields[i];
                name = field.name;

                result = field.cmp(A[name], B[name]);
                if (result !== 0) break;
            }
            return result;
        }
    }
}());

示例用法:

homes.sort(sort_by('city', {name:'price', primer: parseInt, reverse: true}));

演示


原始功能:

var sort_by = function() {
   var fields = [].slice.call(arguments),
       n_fields = fields.length;

   return function(A,B) {
       var a, b, field, key, primer, reverse, result, i;

       for(i = 0; i < n_fields; i++) {
           result = 0;
           field = fields[i];

           key = typeof field === 'string' ? field : field.name;

           a = A[key];
           b = B[key];

           if (typeof field.primer  !== 'undefined'){
               a = field.primer(a);
               b = field.primer(b);
           }

           reverse = (field.reverse) ? -1 : 1;

           if (a<b) result = reverse * -1;
           if (a>b) result = reverse * 1;
           if(result !== 0) break;
       }
       return result;
   }
};

演示

這是一個簡單的功能通用方法。 使用數組指定排序順序。 在前面加上減號以指定降序。

var homes = [
    {"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":"162500"},
    {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"},
    {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"},
    {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"}
    ];

homes.sort(fieldSorter(['city', '-price']));
// homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative

function fieldSorter(fields) {
    return function (a, b) {
        return fields
            .map(function (o) {
                var dir = 1;
                if (o[0] === '-') {
                   dir = -1;
                   o=o.substring(1);
                }
                if (a[o] > b[o]) return dir;
                if (a[o] < b[o]) return -(dir);
                return 0;
            })
            .reduce(function firstNonZeroValue (p,n) {
                return p ? p : n;
            }, 0);
    };
}

編輯:在 ES6 中它甚至更短!

 "use strict"; const fieldSorter = (fields) => (a, b) => fields.map(o => { let dir = 1; if (o[0] === '-') { dir = -1; o=o.substring(1); } return a[o] > b[o] ? dir : a[o] < b[o] ? -(dir) : 0; }).reduce((p, n) => p ? p : n, 0); const homes = [{"h_id":"3", "city":"Dallas", "state":"TX","zip":"75201","price":162500}, {"h_id":"4","city":"Bevery Hills", "state":"CA", "zip":"90210", "price":319250},{"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":556699},{"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":962500}]; const sortedHomes = homes.sort(fieldSorter(['state', '-price'])); document.write('<pre>' + JSON.stringify(sortedHomes, null, '\t') + '</pre>')

我今天做了一個非常通用的多特征分類器。 您可以在這里查看 thenBy.js: https ://github.com/Teun/thenBy.js

它允許您使用標准的 Array.sort,但使用 firstBy().thenBy().thenBy() 樣式。 與上面發布的解決方案相比,它的代碼和復雜性要少得多。

這是一種按多個字段排序的可擴展方式。

homes.sort(function(left, right) {
    var city_order = left.city.localeCompare(right.city);
    var price_order = parseInt(left.price) - parseInt(right.price);
    return city_order || -price_order;
});

筆記

  • 傳遞給數組排序的函數應返回負數、零、正數以表示小於、等於、大於。
  • a.localeCompare(b)普遍支持字符串,如果a<b , a==b , a>b則返回 -1,0,1 。
  • 減法適用於數字字段,因為如果a<ba==ba>ba - b給出 -,0,+ 。
  • || 在最后一行中, city優先於price
  • 否定以反轉任何字段中的順序,如-price_order
  • 向 or 鏈添加新字段: return city_order || -price_order || date_order; return city_order || -price_order || date_order;
  • 日期與減法比較,因為自 1970 年以來日期數學轉換為毫秒。
    var date_order = new Date(left.date) - new Date(right.date);
  • 布爾值與減法比較, 保證將真假轉為 1 和 0(因此減法產生 -1 或 0 或 1)。
    var goodness_order = Boolean(left.is_good) - Boolean(right.is_good)
    這很不尋常,我建議使用布爾構造函數引起注意,即使它們已經是布爾值。

這是一個完整的作弊,但我認為它為這個問題增加了價值,因為它基本上是一個罐頭庫函數,您可以開箱即用地使用。

如果您的代碼可以訪問lodashunderscore之類的 lodash 兼容庫,那么您可以使用_.sortBy方法。 下面的代碼片段直接從lodash 文檔中復制而來。

示例中的注釋結果看起來像是返回數組數組,但這只是顯示順序,而不是對象數組的實際結果。

var users = [
  { 'user': 'fred',   'age': 48 },
  { 'user': 'barney', 'age': 36 },
  { 'user': 'fred',   'age': 40 },
  { 'user': 'barney', 'age': 34 }
];

_.sortBy(users, [function(o) { return o.user; }]);
 // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]

_.sortBy(users, ['user', 'age']);
// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]

以下函數將允許您對一個或多個屬性的對象數組進行排序,對每個屬性升序(默認)或降序,並允許您選擇是否執行區分大小寫的比較。 默認情況下,此函數執行不區分大小寫的排序。

第一個參數必須是包含對象的數組。 隨后的參數必須是一個逗號分隔的字符串列表,這些字符串引用不同的對象屬性進行排序。 最后一個參數(可選)是一個布爾值,用於選擇是否執行區分大小寫的排序 - 對區分大小寫的排序使用true

默認情況下,該函數將按升序對每個屬性/鍵進行排序。 如果您希望特定鍵按降序排序,則改為以以下格式傳入數組: ['property_name', true]

以下是該函數的一些示例用法,然后是說明(其中, homes是包含對象的數組):

objSort(homes, 'city') --> 按城市排序(升序,不區分大小寫)

objSort(homes, ['city', true]) --> 按城市排序(降序,不區分大小寫)

objSort(homes, 'city', true) --> 按城市排序然后價格(升序,區分大小寫

objSort(homes, 'city', 'price') --> 按城市然后價格排序(均升序,不區分大小寫)

objSort(homes, 'city', ['price', true]) --> 按城市(升序)然后價格(降序)排序,不區分大小寫)

事不宜遲,功能如下:

function objSort() {
    var args = arguments,
        array = args[0],
        case_sensitive, keys_length, key, desc, a, b, i;

    if (typeof arguments[arguments.length - 1] === 'boolean') {
        case_sensitive = arguments[arguments.length - 1];
        keys_length = arguments.length - 1;
    } else {
        case_sensitive = false;
        keys_length = arguments.length;
    }

    return array.sort(function (obj1, obj2) {
        for (i = 1; i < keys_length; i++) {
            key = args[i];
            if (typeof key !== 'string') {
                desc = key[1];
                key = key[0];
                a = obj1[args[i][0]];
                b = obj2[args[i][0]];
            } else {
                desc = false;
                a = obj1[args[i]];
                b = obj2[args[i]];
            }

            if (case_sensitive === false && typeof a === 'string') {
                a = a.toLowerCase();
                b = b.toLowerCase();
            }

            if (! desc) {
                if (a < b) return -1;
                if (a > b) return 1;
            } else {
                if (a > b) return -1;
                if (a < b) return 1;
            }
        }
        return 0;
    });
} //end of objSort() function

這是一些示例數據:

var homes = [{
    "h_id": "3",
    "city": "Dallas",
    "state": "TX",
    "zip": "75201",
    "price": 162500
}, {
    "h_id": "4",
    "city": "Bevery Hills",
    "state": "CA",
    "zip": "90210",
    "price": 1000000
}, {
    "h_id": "5",
    "city": "new york",
    "state": "NY",
    "zip": "00010",
    "price": 1000000
}, {
    "h_id": "6",
    "city": "Dallas",
    "state": "TX",
    "zip": "85000",
    "price": 300000
}, {
    "h_id": "7",
    "city": "New York",
    "state": "NY",
    "zip": "00020",
    "price": 345000
}];

使用 MULTIPLE 鍵的動態方法:

  • 從排序的每個列/鍵中過濾唯一值
  • 整理或顛倒它
  • 根據 indexOf(value) 鍵值為每個對象添加權重寬度 zeropad
  • 使用計算權重排序

在此處輸入圖像描述

Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) { 
    sorts.map(sort => {            
        sort.uniques = Array.from(
            new Set(this.map(obj => obj[sort.key]))
        );
        
        sort.uniques = sort.uniques.sort((a, b) => {
            if (typeof a == 'string') {
                return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
            }
            else if (typeof a == 'number') {
                return sort.inverse ? b - a : a - b;
            }
            else if (typeof a == 'boolean') {
                let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
                return x;
            }
            return 0;
        });
    });

    const weightOfObject = (obj) => {
        let weight = "";
        sorts.map(sort => {
            let zeropad = `${sort.uniques.length}`.length;
            weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
        });
        //obj.weight = weight; // if you need to see weights
        return weight;
    }

    this.sort((a, b) => {
        return weightOfObject(a).localeCompare( weightOfObject(b) );
    });
    
    return this;
}
});

利用:

// works with string, number and boolean
let sortered = your_array.orderBy([
    {key: "type", inverse: false}, 
    {key: "title", inverse: false},
    {key: "spot", inverse: false},
    {key: "internal", inverse: true}
]);

在此處輸入圖像描述

這是一個通用的多維排序,允許在每個級別上進行反轉和/或映射。

用打字稿寫的。 對於 Javascript,請查看此JSFiddle

編碼

type itemMap = (n: any) => any;

interface SortConfig<T> {
  key: keyof T;
  reverse?: boolean;
  map?: itemMap;
}

export function byObjectValues<T extends object>(keys: ((keyof T) | SortConfig<T>)[]): (a: T, b: T) => 0 | 1 | -1 {
  return function(a: T, b: T) {
    const firstKey: keyof T | SortConfig<T> = keys[0];
    const isSimple = typeof firstKey === 'string';
    const key: keyof T = isSimple ? (firstKey as keyof T) : (firstKey as SortConfig<T>).key;
    const reverse: boolean = isSimple ? false : !!(firstKey as SortConfig<T>).reverse;
    const map: itemMap | null = isSimple ? null : (firstKey as SortConfig<T>).map || null;

    const valA = map ? map(a[key]) : a[key];
    const valB = map ? map(b[key]) : b[key];
    if (valA === valB) {
      if (keys.length === 1) {
        return 0;
      }
      return byObjectValues<T>(keys.slice(1))(a, b);
    }
    if (reverse) {
      return valA > valB ? -1 : 1;
    }
    return valA > valB ? 1 : -1;
  };
}

使用示例

按姓氏排序人員數組,然后是名字:

interface Person {
  firstName: string;
  lastName: string;
}

people.sort(byObjectValues<Person>(['lastName','firstName']));

名稱排序語言代碼,而不是語言代碼(參見map ),然后按版本降序(參見reverse )。

interface Language {
  code: string;
  version: number;
}

// languageCodeToName(code) is defined elsewhere in code

languageCodes.sort(byObjectValues<Language>([
  {
    key: 'code',
    map(code:string) => languageCodeToName(code),
  },
  {
    key: 'version',
    reverse: true,
  }
]));

這是另一個可能更接近您對語法的想法

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {}; // primers are optional

    properties = properties.map(function(prop) {
        if( !(prop instanceof Array) ) {
            prop = [prop, 'asc']
        }
        if( prop[1].toLowerCase() == 'desc' ) {
            prop[1] = -1;
        } else {
            prop[1] = 1;
        }
        return prop;
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

// just for fun use this to reverse the city name when sorting
function demoPrimer(str) {
    return str.split('').reverse().join('');
}

// Example
sortObjects(homes, ['city', ['price', 'desc']], {city: demoPrimer});

演示:http: //jsfiddle.net/Nq4dk/2/


編輯:只是為了好玩,這里有一個變體,它只需要一個類似 sql 的字符串,所以你可以做sortObjects(homes, "city, price desc")

function sortObjects(objArray, properties /*, primers*/) {
    var primers = arguments[2] || {};

    properties = properties.split(/\s*,\s*/).map(function(prop) {
        prop = prop.match(/^([^\s]+)(\s*desc)?/i);
        if( prop[2] && prop[2].toLowerCase() === 'desc' ) {
            return [prop[1] , -1];
        } else {
            return [prop[1] , 1];
        }
    });

    function valueCmp(x, y) {
        return x > y ? 1 : x < y ? -1 : 0; 
    }

    function arrayCmp(a, b) {
        var arr1 = [], arr2 = [];
        properties.forEach(function(prop) {
            var aValue = a[prop[0]],
                bValue = b[prop[0]];
            if( typeof primers[prop[0]] != 'undefined' ) {
                aValue = primers[prop[0]](aValue);
                bValue = primers[prop[0]](bValue);
            }
            arr1.push( prop[1] * valueCmp(aValue, bValue) );
            arr2.push( prop[1] * valueCmp(bValue, aValue) );
        });
        return arr1 < arr2 ? -1 : 1;
    }

    objArray.sort(function(a, b) {
        return arrayCmp(a, b);
    });
}

為什么復雜? 只需排序兩次! 這完美地工作:(只需確保將重要性順序從最低到最高顛倒):

jj.sort( (a, b) => (a.id >= b.id) ? 1 : -1 );
jj.sort( (a, b) => (a.status >= b.status) ? 1 : -1 );

更簡單的一個:

var someArray = [...];

function generateSortFn(props) {
    return function (a, b) {
        for (var i = 0; i < props.length; i++) {
            var prop = props[i];
            var name = prop.name;
            var reverse = prop.reverse;
            if (a[name] < b[name])
                return reverse ? 1 : -1;
            if (a[name] > b[name])
                return reverse ? -1 : 1;
        }
        return 0;
    };
};

someArray.sort(generateSortFn([{name: 'prop1', reverse: true}, {name: 'prop2'}]));

這是我基於Schwartzian transform idiom的解決方案,希望對您有用。

function sortByAttribute(array, ...attrs) {
  // generate an array of predicate-objects contains
  // property getter, and descending indicator
  let predicates = attrs.map(pred => {
    let descending = pred.charAt(0) === '-' ? -1 : 1;
    pred = pred.replace(/^-/, '');
    return {
      getter: o => o[pred],
      descend: descending
    };
  });
  // schwartzian transform idiom implementation. aka: "decorate-sort-undecorate"
  return array.map(item => {
    return {
      src: item,
      compareValues: predicates.map(predicate => predicate.getter(item))
    };
  })
  .sort((o1, o2) => {
    let i = -1, result = 0;
    while (++i < predicates.length) {
      if (o1.compareValues[i] < o2.compareValues[i]) result = -1;
      if (o1.compareValues[i] > o2.compareValues[i]) result = 1;
      if (result *= predicates[i].descend) break;
    }
    return result;
  })
  .map(item => item.src);
}

這是一個如何使用它的示例:

let games = [
  { name: 'Pako',              rating: 4.21 },
  { name: 'Hill Climb Racing', rating: 3.88 },
  { name: 'Angry Birds Space', rating: 3.88 },
  { name: 'Badland',           rating: 4.33 }
];

// sort by one attribute
console.log(sortByAttribute(games, 'name'));
// sort by mupltiple attributes
console.log(sortByAttribute(games, '-rating', 'name'));

我喜歡 SnowBurnt 的方法,但它需要調整來測試城市的等效性,而不是差異。

homes.sort(
   function(a,b){
      if (a.city==b.city){
         return (b.price-a.price);
      } else {
         return (a.city-b.city);
      }
   });

另一種方式

 var homes = [ {"h_id":"3", "city":"Dallas", "state":"TX", "zip":"75201", "price":"162500"}, {"h_id":"4", "city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"}, {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"}, {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"} ]; function sortBy(ar) { return ar.sort((a, b) => a.city === b.city ? b.price.toString().localeCompare(a.price) : a.city.toString().localeCompare(b.city)); } console.log(sortBy(homes));

只是另一種選擇。 考慮使用以下效用函數:

/** Performs comparing of two items by specified properties
 * @param  {Array} props for sorting ['name'], ['value', 'city'], ['-date']
 * to set descending order on object property just add '-' at the begining of property
 */
export const compareBy = (...props) => (a, b) => {
  for (let i = 0; i < props.length; i++) {
    const ascValue = props[i].startsWith('-') ? -1 : 1;
    const prop = props[i].startsWith('-') ? props[i].substr(1) : props[i];
    if (a[prop] !== b[prop]) {
      return a[prop] > b[prop] ? ascValue : -ascValue;
    }
  }
  return 0;
};

用法示例(在您的情況下):

homes.sort(compareBy('city', '-price'));

應該注意的是,這個函數可以更加通用化,以便能夠使用嵌套屬性,如 'address.city' 或 'style.size.width' 等。

為簡單起見,請使用這些輔助函數。

您可以根據需要按任意多個字段進行排序。 對於每個排序字段,指定屬性名稱,然后(可選)指定-1作為排序方向以降序而不是升序。

 const data = [ {"h_id":"3","city":"Dallas","state":"TX","zip":"75201","price":"162500"}, {"h_id":"4","city":"Bevery Hills","state":"CA","zip":"90210","price":"319250"}, {"h_id":"6","city":"Dallas","state":"TX","zip":"75000","price":"556699"}, {"h_id":"5","city":"New York","state":"NY","zip":"00010","price":"962500"}, {"h_id":"7","city":"New York","state":"NY","zip":"00010","price":"800500"} ] const sortLexically = (p,d=1)=>(a,b)=>d * a[p].localeCompare(b[p]) const sortNumerically = (p,d=1)=>(a,b)=>d * (a[p]-b[p]) const sortBy = sorts=>(a,b)=>sorts.reduce((r,s)=>r||s(a,b),0) // sort first by city, then by price descending data.sort(sortBy([sortLexically('city'), sortNumerically('price', -1)])) console.log(data)

這是@Snowburnt 解決方案的通用版本:

var sortarray = [{field:'city', direction:'asc'}, {field:'price', direction:'desc'}];
array.sort(function(a,b){
    for(var i=0; i<sortarray.length; i++){
        retval = a[sortarray[i].field] < b[sortarray[i].field] ? -1 : a[sortarray[i].field] > b[sortarray[i].field] ? 1 : 0;
        if (sortarray[i].direction == "desc") {
            retval = retval * -1;
        }
        if (retval !== 0) {
            return retval;
        }
    }
}


})

這是基於我正在使用的排序例程。 我沒有測試這個特定的代碼,所以它可能有錯誤,但你明白了。 這個想法是根據指示差異的第一個字段進行排序,然后停止並轉到下一條記錄。 因此,如果您按三個字段排序,並且比較中的第一個字段足以確定要排序的兩條記錄的排序順序,則返回該排序結果並轉到下一條記錄。

我在 5000 條記錄上測試了它(實際上使用了更復雜的排序邏輯),它在眨眼間就完成了。 如果您實際上向客戶端加載了超過 1000 條記錄,您可能應該使用服務器端排序和過濾。

此代碼不區分大小寫,但我將其留給讀者來處理這個微不足道的修改。

function sort(data, orderBy) {
        orderBy = Array.isArray(orderBy) ? orderBy : [orderBy];
        return data.sort((a, b) => {
            for (let i = 0, size = orderBy.length; i < size; i++) {
                const key = Object.keys(orderBy[i])[0],
                    o = orderBy[i][key],
                    valueA = a[key],
                    valueB = b[key];
                if (!(valueA || valueB)) {
                    console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!");
                    return [];
                }
                if (+valueA === +valueA) {
                    return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB;
                } else {
                    if (valueA.localeCompare(valueB) > 0) {
                        return o.toLowerCase() === 'desc' ? -1 : 1;
                    } else if (valueA.localeCompare(valueB) < 0) {
                        return o.toLowerCase() === 'desc' ? 1 : -1;
                    }
                }
            }
        });
    }

使用 :

sort(homes, [{city : 'asc'}, {price: 'desc'}])

 var homes = [ {"h_id":"3", "city":"Dallas", "state":"TX", "zip":"75201", "price":"162500"}, {"h_id":"4", "city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"}, {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"}, {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"} ]; function sort(data, orderBy) { orderBy = Array.isArray(orderBy) ? orderBy : [orderBy]; return data.sort((a, b) => { for (let i = 0, size = orderBy.length; i < size; i++) { const key = Object.keys(orderBy[i])[0], o = orderBy[i][key], valueA = a[key], valueB = b[key]; if (!(valueA || valueB)) { console.error("the objects from the data passed does not have the key '" + key + "' passed on sort!"); return []; } if (+valueA === +valueA) { return o.toLowerCase() === 'desc' ? valueB - valueA : valueA - valueB; } else { if (valueA.localeCompare(valueB) > 0) { return o.toLowerCase() === 'desc' ? -1 : 1; } else if (valueA.localeCompare(valueB) < 0) { return o.toLowerCase() === 'desc' ? 1 : -1; } } } }); } console.log(sort(homes, [{city : 'asc'}, {price: 'desc'}]));

// custom sorting by city
const sortArray = ['Dallas', 'New York', 'Beverly Hills'];

const sortData = (sortBy) =>
  data
    .sort((a, b) => {
      const aIndex = sortBy.indexOf(a.city);
      const bIndex = sortBy.indexOf(b.city);

      if (aIndex < bIndex) {
        return -1;
      }

      if (aIndex === bIndex) {
        // price descending
        return b.price- a.price;
      }

      return 1;
    });

sortData(sortArray);

只需按照您的排序標准列表

即使您有 36 個排序標准要封裝,此代碼也將始終保持可讀性和可理解性

Nina 在這里提出的解決方案當然非常優雅,但它意味着知道值為零對應於布爾邏輯中的 false 值,並且布爾測試可以在 JavaScript 中返回除 true/false 以外的值(這里是數字值)這對於初學者來說總是令人困惑。

還要考慮誰需要維護您的代碼。 也許會是你:想象你自己花了幾天的時間來搜索另一個人的代碼並且遇到了一個有害的錯誤......並且你已經厭倦了閱讀這數千行充滿提示的行

 const homes = [ { h_id: '3', city: 'Dallas', state: 'TX', zip: '75201', price: '162500' } , { h_id: '4', city: 'Bevery Hills', state: 'CA', zip: '90210', price: '319250' } , { h_id: '6', city: 'Dallas', state: 'TX', zip: '75000', price: '556699' } , { h_id: '5', city: 'New York', state: 'NY', zip: '00010', price: '962500' } ] const fSort = (a,b) => { let Dx = a.city.localeCompare(b.city) // 1st criteria if (Dx===0) Dx = Number(b.price) - Number(a.price) // 2nd // if (Dx===0) Dx = ... // 3rd // if (Dx===0) Dx = ... // 4th.... return Dx } console.log( homes.sort(fSort))

添加幾個輔助函數可以讓您通用且簡單地解決此類問題。 sortByKey接受一個數組和一個函數,該函數應該返回一個項目列表,用於比較每個數組條目。

這利用了 javascript 對簡單值數組進行智能比較的事實,即[0] < [0, 0] < [0, 1] < [1, 0]


// Two helpers:
function cmp(a, b) {
    if (a > b) {
        return 1
    } else if (a < b) {
        return -1
    } else {
        return 0
    }
}

function sortByKey(arr, key) {
    arr.sort((a, b) => cmp(key(a), key(b)))
}

// A demonstration:
let arr = [{a:1, b:2}, {b:3, a:0}, {a:1, b:1}, {a:2, b:2}, {a:2, b:1}]
sortByKey(arr, item => [item.a, item.b])

console.log(JSON.stringify(arr))
// '[{"b":3,"a":0},{"a":1,"b":1},{"a":1,"b":2},{"a":2,"b":1},{"a":2,"b":2}]'

sortByKey(arr, item => [item.b, item.a])
console.log(JSON.stringify(arr))
//'[{"a":1,"b":1},{"a":2,"b":1},{"a":1,"b":2},{"a":2,"b":2},{"b":3,"a":0}]'

我從 Python 的list.sort函數中巧妙地竊取了這個想法。

function sortMultiFields(prop){
    return function(a,b){
        for(i=0;i<prop.length;i++)
        {
            var reg = /^\d+$/;
            var x=1;
            var field1=prop[i];
            if(prop[i].indexOf("-")==0)
            {
                field1=prop[i].substr(1,prop[i].length);
                x=-x;
            }

            if(reg.test(a[field1]))
            {
                a[field1]=parseFloat(a[field1]);
                b[field1]=parseFloat(b[field1]);
            }
            if( a[field1] > b[field1])
                return x;
            else if(a[field1] < b[field1])
                return -x;
        }
    }
}

如何使用(如果要按降序對特定字段進行排序,請在字段前放置 -(減號)符號)

homes.sort(sortMultiFields(["city","-price"]));

使用上述函數,您可以對具有多個字段的任何 json 數組進行排序。 完全不需要改變函數體

改編@chriskelly 的答案。


大多數答案忽略了如果價值在萬以下或超過一百萬,價格將無法正確排序。 JS 的原因是按字母順序排序。 在這里回答得很好,為什么 JavaScript 不能對 "5, 10, 1"進行排序,在這里如何正確地對整數數組進行排序

如果我們排序的字段或節點是一個數字,最終我們必須做一些評估。 我並不是說在這種情況下使用parseInt()是正確的答案,排序結果更重要。

 var homes = [{ "h_id": "2", "city": "Dallas", "state": "TX", "zip": "75201", "price": "62500" }, { "h_id": "1", "city": "Dallas", "state": "TX", "zip": "75201", "price": "62510" }, { "h_id": "3", "city": "Dallas", "state": "TX", "zip": "75201", "price": "162500" }, { "h_id": "4", "city": "Bevery Hills", "state": "CA", "zip": "90210", "price": "319250" }, { "h_id": "6", "city": "Dallas", "state": "TX", "zip": "75000", "price": "556699" }, { "h_id": "5", "city": "New York", "state": "NY", "zip": "00010", "price": "962500" }]; homes.sort(fieldSorter(['price'])); // homes.sort(fieldSorter(['zip', '-state', 'price'])); // alternative function fieldSorter(fields) { return function(a, b) { return fields .map(function(o) { var dir = 1; if (o[0] === '-') { dir = -1; o = o.substring(1); } if (!parseInt(a[o]) && !parseInt(b[o])) { if (a[o] > b[o]) return dir; if (a[o] < b[o]) return -(dir); return 0; } else { return dir > 0 ? a[o] - b[o] : b[o] - a[o]; } }) .reduce(function firstNonZeroValue(p, n) { return p ? p : n; }, 0); }; } document.getElementById("output").innerHTML = '<pre>' + JSON.stringify(homes, null, '\t') + '</pre>';
 <div id="output"> </div>


一個要測試的小提琴

哇,這里有一些復雜的解決方案。 如此復雜,我決定想出一些更簡單但也很強大的東西。 這里是;

function sortByPriority(data, priorities) {
  if (priorities.length == 0) {
    return data;
  }

  const nextPriority = priorities[0];
  const remainingPriorities = priorities.slice(1);

  const matched = data.filter(item => item.hasOwnProperty(nextPriority));
  const remainingData = data.filter(item => !item.hasOwnProperty(nextPriority));

  return sortByPriority(matched, remainingPriorities)
    .sort((a, b) => (a[nextPriority] > b[nextPriority]) ? 1 : -1)
    .concat(sortByPriority(remainingData, remainingPriorities));
}

這是一個如何使用它的示例。

const data = [
  { id: 1,                         mediumPriority: 'bbb', lowestPriority: 'ggg' },
  { id: 2, highestPriority: 'bbb', mediumPriority: 'ccc', lowestPriority: 'ggg' },
  { id: 3,                         mediumPriority: 'aaa', lowestPriority: 'ggg' },
];

const priorities = [
  'highestPriority',
  'mediumPriority',
  'lowestPriority'
];


const sorted = sortByPriority(data, priorities);

這將首先按屬性的優先級排序,然后按屬性的值。

我認為這可能是最簡單的方法。

https://coderwall.com/p/ebqhca/javascript-sort-by-two-fields

這真的很簡單,我嘗試了 3 個不同的鍵值對,效果很好。

這是一個簡單的例子,查看鏈接了解更多詳情

testSort(data) {
    return data.sort(
        a['nameOne'] > b['nameOne'] ? 1
        : b['nameOne'] > a['nameOne'] ? -1 : 0 ||
        a['date'] > b['date'] ||
        a['number'] - b['number']
    );
}

這是我的供您參考,例如:

function msort(arr, ...compFns) {
  let fn = compFns[0];
  arr = [].concat(arr);
  let arr1 = [];
  while (arr.length > 0) {
    let arr2 = arr.splice(0, 1);
    for (let i = arr.length; i > 0;) {
      if (fn(arr2[0], arr[--i]) === 0) {
        arr2 = arr2.concat(arr.splice(i, 1));
      }
    }
    arr1.push(arr2);
  }

  arr1.sort(function (a, b) {
    return fn(a[0], b[0]);
  });

  compFns = compFns.slice(1);
  let res = [];
  arr1.map(a1 => {
    if (compFns.length > 0) a1 = msort(a1, ...compFns);
    a1.map(a2 => res.push(a2));
  });
  return res;
}

let tstArr = [{ id: 1, sex: 'o' }, { id: 2, sex: 'm' }, { id: 3, sex: 'm' }, { id: 4, sex: 'f' }, { id: 5, sex: 'm' }, { id: 6, sex: 'o' }, { id: 7, sex: 'f' }];

function tstFn1(a, b) {
  if (a.sex > b.sex) return 1;
  else if (a.sex < b.sex) return -1;
  return 0;
}

function tstFn2(a, b) {
  if (a.id > b.id) return -1;
  else if (a.id < b.id) return 1;
  return 0;
}

console.log(JSON.stringify(msort(tstArr, tstFn1, tstFn2)));
//output:
//[{"id":7,"sex":"f"},{"id":4,"sex":"f"},{"id":5,"sex":"m"},{"id":3,"sex":"m"},{"id":2,"sex":"m"},{"id":6,"sex":"o"},{"id":1,"sex":"o"}]

我一直在尋找類似的東西並最終得到了這個:

首先,我們有一個或多個排序函數,總是返回 0、1 或 -1:

const sortByTitle = (a, b): number => 
  a.title === b.title ? 0 : a.title > b.title ? 1 : -1;

您可以為要排序的每個其他屬性創建更多函數。

然后我有一個函數將這些排序函數合二為一:

const createSorter = (...sorters) => (a, b) =>
  sorters.reduce(
    (d, fn) => (d === 0 ? fn(a, b) : d),
    0
  );

這可以用來以一種可讀的方式組合上述排序函數:

const sorter = createSorter(sortByTitle, sortByYear)

items.sort(sorter)

當排序函數返回 0 時,將調用下一個排序函數進行進一步排序。

這是一種遞歸算法,用於按多個字段排序,同時有機會在比較之前格式化值。

var data = [
{
    "id": 1,
    "ship": null,
    "product": "Orange",
    "quantity": 7,
    "price": 92.08,
    "discount": 0
},
{
    "id": 2,
    "ship": "2017-06-14T23:00:00.000Z".toDate(),
    "product": "Apple",
    "quantity": 22,
    "price": 184.16,
    "discount": 0
},
...
]
var sorts = ["product", "quantity", "ship"]

// comp_val formats values and protects against comparing nulls/undefines
// type() just returns the variable constructor
// String.lower just converts the string to lowercase.
// String.toDate custom fn to convert strings to Date
function comp_val(value){
    if (value==null || value==undefined) return null
    var cls = type(value)
    switch (cls){
        case String:
            return value.lower()
    }
    return value
}

function compare(a, b, i){
    i = i || 0
    var prop = sorts[i]
    var va = comp_val(a[prop])
    var vb = comp_val(b[prop])

    // handle what to do when both or any values are null
    if (va == null || vb == null) return true

    if ((i < sorts.length-1) && (va == vb)) {
        return compare(a, b, i+1)
    } 
    return va > vb
}

var d = data.sort(compare);
console.log(d);

如果 a 和 b 相等,它將只嘗試下一個字段,直到沒有可用的字段。

您可以使用 lodash orderBy 函數lodash

它需要兩個參數字段數組和方向數組('asc','desc')

  var homes = [
    {"h_id":"3",
     "city":"Dallas",
     "state":"TX",
     "zip":"75201",
     "price":"162500"},
    {"h_id":"4",
     "city":"Bevery Hills",
     "state":"CA",
     "zip":"90210",
     "price":"319250"},
    {"h_id":"6",
     "city":"Dallas",
     "state":"TX",
     "zip":"75000",
     "price":"556699"},
    {"h_id":"5",
     "city":"New York",
     "state":"NY",
     "zip":"00010",
     "price":"962500"}
    ];

var sorted =. data._.orderBy(data, ['city', 'price'], ['asc','desc'])

通過添加 3 個相對簡單的助手可以制作一個非常直觀的功能解決方案。 在我們深入研究之前,讓我們從用法開始:

 function usage(homes, { asc, desc, fallback }) { homes.sort(fallback( asc(home => home.city), desc(home => parseInt(home.price, 10)), )); console.log(homes); } var homes = [{ h_id: "3", city: "Dallas", state: "TX", zip: "75201", price: "162500", }, { h_id: "4", city: "Bevery Hills", state: "CA", zip: "90210", price: "319250", }, { h_id: "6", city: "Dallas", state: "TX", zip: "75000", price: "556699", }, { h_id: "5", city: "New York", state: "NY", zip: "00010", price: "962500", }]; const SortHelpers = (function () { const asc = (fn) => (a, b) => (a = fn(a), b = fn(b), -(a < b) || +(a > b)); const desc = (fn) => (a, b) => asc(fn)(b, a); const fallback = (...fns) => (a, b) => fns.reduce((diff, fn) => diff || fn(a, b), 0); return { asc, desc, fallback }; })(); usage(homes, SortHelpers);

如果您向下滾動代碼段,您可能已經看到了幫助程序:

const asc  = (fn) => (a, b) => (a = fn(a), b = fn(b), -(a < b) || +(a > b));
const desc = (fn) => (a, b) => asc(fn)(b, a);
const fallback = (...fns) => (a, b) => fns.reduce((diff, fn) => diff || fn(a, b), 0);

讓我快速解釋一下每個函數的作用。

  • asc創建一個比較器函數。 使用比較器參數ab調用提供的函數fn 然后比較兩個函數調用的結果。 如果resultA < resultB則返回-1 ,如果resultA > resultB則返回1 ,如果兩者都為 false 則返回0 這些返回值對應於升序方向。

    也可以這樣寫:

     function asc(fn) { return function (a, b) { // apply `fn` to both `a` and `b` a = fn(a); b = fn(b); if (a < b) return -1; if (a > b) return 1; return 0; // or `return -(a < b) || +(a > b)` for short }; }
  • desc非常簡單,因為它只是調用asc但交換了ab參數,從而導致降序而不是升序。

  • fallback (可能有一個更好的名字)允許我們使用多個比較器函數和一個sort

    ascdesc都可以傳遞給自己sort

     homes.sort(asc(home => home.city))

    但是,如果您想組合多個比較器功能,則會出現問題。 sort只接受一個比較器函數。 fallback將多個比較器功能組合到一個比較器中。

    第一個比較器使用參數ab調用,如果比較器返回值0 (意味着值相等),那么我們回退到下一個比較器。 這一直持續到找到非0值,或者直到調用所有比較器,在這種情況下,返回值為0

您提供了超級簡單的自定義比較器功能。 假設您想使用localeCompare()而不是將字符串與<>進行比較。 在這種情況下,您可以簡單地將asc(home => home.city)替換為(a, b) => a.city.localeCompare(b.city)

homes.sort(fallback(
  (a, b) => a.city.localeCompare(b.city),
  desc(home => parseInt(home.price, 10)),
));

需要注意的一件事是,與<>進行比較時,可以undefined的值將始終返回false 因此,如果一個值可能丟失,您可能需要先按其存在進行排序。

homes.sort(fallback(
  // homes with optionalProperty first, true (1) > false (0) so we use desc
  desc(home => home.optionalProperty != null), // checks for both null and undefined
  asc(home => home.optionalProperty),
  // ...
))

自 2019 年以來,所有主要的 Javascript 引擎都實現了穩定的 Array.sort 這本質上意味着每次我們按屬性a排序時,具有相同屬性b的項目將按相同的順序排列。 多虧了這一點,我們可以做到以下幾點:

 const homes = [{ "h_id": "3", "city": "Dallas", "state": "TX", "zip": "75201", "price": "162500" }, { "h_id": "4", "city": "Bevery Hills", "state": "CA", "zip": "90210", "price": "319250" }, { "h_id": "6", "city": "Dallas", "state": "TX", "zip": "75000", "price": "556699" }, { "h_id": "5", "city": "New York", "state": "NY", "zip": "00010", "price": "962500" } ]; const byCityAscending = (a, b) => a.city.localeCompare(b.city); const byPriceDescending = (a, b) => parseFloat(b.price) - parseFloat(a.price); const result = homes.sort(byPriceDescending).sort(byCityAscending) console.log(result)

⚠️ 請注意,我們以相反的順序應用排序功能。 如果您考慮一下,這是有道理的——鏈接仍然從左到右發生,但我們從內部“展開”排序。 首先我們穩定最里面的物品順序。 然后,由於穩定​​的排序,當我們在外部排序期間獲得相同的值時,該順序得以保留。

在這里,您可以嘗試按多個字段排序的更小更方便的方式!

 var homes = [ {"h_id":"3", "city":"Dallas", "state":"TX", "zip":"75201", "price":"162500"}, {"h_id":"4", "city":"Bevery Hills", "state":"CA", "zip":"90210", "price":"319250"}, {"h_id":"6", "city":"Dallas", "state":"TX", "zip":"75000", "price":"556699"}, {"h_id":"5", "city":"New York", "state":"NY", "zip":"00010", "price":"962500"} ]; homes.sort((a, b)=> { if (a.city === b.city){ return a.price < b.price? -1: 1 } else { return a.city < b.city? -1: 1 } }) console.log(homes);

這里 'AffiliateDueDate' 和 'Title' 是列,都按升序排序。

array.sort(function(a, b) {

               if (a.AffiliateDueDate > b.AffiliateDueDate ) return 1;
               else if (a.AffiliateDueDate < b.AffiliateDueDate ) return -1;
               else if (a.Title > b.Title ) return 1;
               else if (a.Title < b.Title ) return -1;
               else return 0;
             })

對兩個日期字段和一個數字字段示例進行排序:

var generic_date =  new Date(2070, 1, 1);
checkDate = function(date) {
  return Date.parse(date) ? new Date(date): generic_date;
}

function sortData() {  
  data.sort(function(a,b){
    var deltaEnd = checkDate(b.end) - checkDate(a.end);
    if(deltaEnd) return deltaEnd;

    var deltaRank = a.rank - b.rank;
    if (deltaRank) return deltaRank;

    var deltaStart = checkDate(b.start) - checkDate(a.start);
    if(deltaStart) return deltaStart;

    return 0;
  });
}

http://jsfiddle.net/hcWgf/57/

這個簡單的解決方案怎么樣:

const sortCompareByCityPrice = (a, b) => {
    let comparison = 0
    // sort by first criteria
    if (a.city > b.city) {
        comparison = 1
    }
    else if (a.city < b.city) {
        comparison = -1
    }
    // If still 0 then sort by second criteria descending
    if (comparison === 0) {
        if (parseInt(a.price) > parseInt(b.price)) {
            comparison = -1
        }
        else if (parseInt(a.price) < parseInt(b.price)) {
            comparison = 1
        }
    }
    return comparison 
}

基於this question javascript sort array by multiple (number) fields

homes.sort(function(a,b) { return a.city - b.city } );
homes.sort(function(a,b){
    if (a.city==b.city){
        return parseFloat(b.price) - parseFloat(a.price);
    } else {
        return 0;
    }
});

按多個字段對對象數組進行排序的最簡單方法:

 let homes = [ {"h_id":"3",
   "city":"Dallas",
   "state":"TX",
   "zip":"75201",
   "price":"162500"},
  {"h_id":"4",
   "city":"Bevery Hills",
   "state":"CA",
   "zip":"90210",
   "price":"319250"},
  {"h_id":"6",
   "city":"Dallas",
   "state":"TX",
   "zip":"75000",
   "price":"556699"},
  {"h_id":"5",
   "city":"New York",
   "state":"NY",
   "zip":"00010",
   "price":"962500"}
  ];

homes.sort((a, b) => (a.city > b.city) ? 1 : -1);

輸出:“Bevery Hills”“Dallas”“Dallas”“Dallas”“New York”

暫無
暫無

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

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