Как реализовать карту или отсортированный набор в javascript
Javascript имеет массивы, которые используют числовые индексы ["john", "Bob", "Joe"]
и объекты, которые могут использоваться как ассоциативные массивы или "карты", которые позволяют использовать строковые ключи для значений объекта {"john" : 28, "bob": 34, "joe" : 4}
.
В PHP легко и A) сортировать по значениям (при сохранении ключа) и B) тестировать существование значения в ассоциативном массиве.
$array = ["john" => 28, "bob" => 34, "joe" => 4];
asort($array); // ["joe" => 4, "john" => 28, "bob" => 34];
if(isset($array["will"])) { }
Как вы могли бы воспользоваться этой функциональностью в Javascript?
Это обычная потребность в таких вещах, как взвешенные списки или отсортированные наборы, где вам нужно сохранить одну копию значения в структуре данных (например, имя тега), а также сохранить взвешенное значение.
Это лучшее, что я придумал до сих пор:
function getSortedKeys(obj) {
var keys = Object.keys(obj);
keys = keys.sort(function(a,b){return obj[a]-obj[b]});
var map = {};
for (var i = keys.length - 1; i >= 0; i--) {
map[keys[i]] = obj[keys[i]];
};
return map;
}
var list = {"john" : 28, "bob": 34, "joe" : 4};
list = getSortedKeys(list);
if(list["will"]) { }
Ответы
Ответ 1
Глядя на этот ответ от Люка Шафера Я думаю, что я мог бы найти лучший способ справиться с этим, расширив Object.prototype:
// Sort by value while keeping index
Object.prototype.iterateSorted = function(worker, limit)
{
var keys = Object.keys(this), self = this;
keys.sort(function(a,b){return self[b] - self[a]});
if(limit) {
limit = Math.min(keys.length, limit);
}
limit = limit || keys.length;
for (var i = 0; i < limit; i++) {
worker(keys[i], this[keys[i]]);
}
};
var myObj = { e:5, c:3, a:1, b:2, d:4, z:1};
myObj.iterateSorted(function(key, value) {
console.log("key", key, "value", value)
}, 3);
http://jsfiddle.net/Xeoncross/kq3gbwgh/
Ответ 2
В ES6 вы можете расширить конструктор/класс Map
с помощью метода sort
который принимает необязательную функцию сравнения (как в случае с массивами). Этот метод sort
будет принимать два аргумента, каждый из которых является парой ключ/значение, так что сортировка может происходить либо по ключам, либо по значениям (или обоим).
Метод sort
будет опираться на документированное поведение Карт, когда записи повторяются в порядке вставки. Таким образом, этот новый метод будет посещать записи в соответствии с отсортированным порядком, а затем удалять и немедленно вставлять их заново.
Вот как это может выглядеть:
class SortableMap extends Map {
sort(cmp = (a, b) => a[0].localeCompare(b[0])) {
for (const [key, value] of [...this.entries()].sort(cmp)) {
this.delete(key);
this.set(key, value); // New keys are added at the end of the order
}
}
}
// Demo
const mp = new SortableMap([[3, "three"],[1, "one"],[2, "two"]]);
console.log("Before: ", JSON.stringify([...mp])); // Before
mp.sort( (a, b) => a[0] - b[0] ); // Custom compare function: sort numerical keys
console.log(" After: ", JSON.stringify([...mp])); // After
Ответ 3
Обычно вы не сортируете объект. Но если вы выполните: Сортировка объекта JavaScript по значению свойства
Если вы хотите отсортировать массив, скажем, следующее
var arraylist = [{"john" : 28},{ "bob": 34},{ "joe" : 4}];
Вы всегда можете использовать функцию Array.prototype.sort
.
Источник: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Ответ 4
Возможно, этот код выглядит так, как вы хотите:
Object.prototype.asort = function(){
var retVal = {};
var self = this;
var keys = Object.keys(this);
keys = keys.sort(function(a,b){return self[a] - self[b]});
for (var i = 0; i < keys.length; i++) {
retVal[keys[i]] = this[keys[i]];
}
return retVal;
}
var map = {"john" : 28, "bob": 34, "joe" : 4}
var sortedMap = map.asort();//sortedMap["will"]: undefined
Ответ 5
Если вы используете проект с открытым исходным кодом jinqJs его легко.
Смотрите Fiddler
var result = jinqJs()
.from([{"john" : 28},{ "bob": 34},{ "joe" : 4}])
.orderBy([{field: 0}])
.select();
Ответ 6
Я не уверен, почему ни один из этих ответов не упоминает о существовании встроенного класса JS, Set. Кажется, это дополнение к ES6, возможно, поэтому.
В идеале переопределяйте либо add
либо keys
ниже... NB. Для переопределения keys
даже не требуется доступ к прототипу объекта Set
. Конечно, вы можете переопределить эти методы для всего класса Set
. Или сделать подкласс, SortedSet
.
const mySet = new Set();
const mySetProto = Object.getPrototypeOf( mySet );
const addOverride = function( newObj ){
const arr = Array.from( this );
arr.add( newObj );
arr.sort(); // or arr.sort( function( a, b )... )
this.clear();
for( let item of arr ){
mySetProto.add.call( this, item );
}
}
mySet.add = addOverride;
const keysOverride = function(){
const arr = Array.from( this );
arr.sort(); // or arr.sort( function( a, b )... )
return arr[Symbol.iterator]();
}
mySet.keys = keysOverride;
Использование:
mySet.add( 3 ); mySet.add( 2 ); mySet.add( 1 ); mySet.add( 2 );
for( let item of mySet.keys() ){ console.log( item ) };
Распечатывает:
1... 2... 3
Примечание. Set.keys()
возвращает не элементы в Set
, а итератор. Вместо этого вы можете вернуть отсортированный массив, но вы, очевидно, нарушаете класс "контракт".
Какой из них переопределить? Зависит от вашего использования и размера вашего Set
. Если вы переопределите оба, вы будете дублировать действие сортировки, но в большинстве случаев это, вероятно, не будет иметь значения.
NB. Функция add
я предлагаю, является, конечно, наивным, "первым черновиком": перестроение всего набора каждый раз, когда вы add
может быть довольно дорогостоящим. Очевидно, есть гораздо более умные способы сделать это, основываясь на изучении существующих элементов в Set
и использовании функции сравнения, двоичной древовидной структуры * или какого-либо другого метода, чтобы определить, куда в нем добавить кандидата для добавления (я говорю "кандидата"). "потому что он будет отклонен, если" идентичный "элемент, а именно сам, уже найден присутствующим).
Вопрос также задает вопрос о похожих расположениях для отсортированной карты... фактически выясняется, что у ES6 есть новый класс Map, который поддается аналогичной обработке... а также, что Set
- это просто специализированная Map
, как и следовало ожидать.
* например, https://github.com/Crizstian/data-structure-and-algorithms-with-ES6/tree/master/10-chapter-Binary-Tree
Ответ 7
Здесь реализация OrderedMap. Используйте функции get()
и set()
чтобы извлечь или OrderedMap
пары значений ключа в OrderedMap
. Он внутренне использует массив для поддержания порядка.
class OrderedMap {
constructor() {
this.arr = [];
return this;
}
get(key) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
return this.arr[i].value;
}
}
return undefined;
}
set(key, value) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
this.arr[i].value = value;
return;
}
}
this.arr.push({key, value})
}
values() {
return this.arr;
}
}
let m = new OrderedMap();
m.set('b', 60)
m.set('a', 10)
m.set('c', 20)
m.set('d', 89)
console.log(m.get('a'));
console.log(m.values());