Как реализовать карту или отсортированный набор в 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());