How to prevent automatic sort of Object numeric property?
Asked Answered
P

13

61

Why I met this problem: I tried to solve an algorithm problem and I need to return the number which appeared most of the times in an array. Like [5,4,3,2,1,1] should return 1. And also when two number appear same time as the maximum appearance return the one came first. Like [5,5,2,2,1] return 5 because 5 appear first. I use an object to store the appearance of each number. The key is the number itself.

So When the input is [5,5,2,2,1] my object should be Object {5: 2, 2: 2, 1: 1} but actually I got Object {1: 1, 2: 2, 5: 2} So When I use for..in to iterate the object I got 2 returned instead of 5 . So that's why I asked this question.

This problem occurs in Chrome console and I'm not sure if this is a common issue: When I run the following code

var a = {};
a[0]=1;
a[1]=2;
a[2]=3;

a is: Object {0: 1, 1: 2, 2: 3}

But when I reverse the order of assignment like:

 var a = {};
 a[2]=3;
 a[1]=2;
 a[0]=1;

a is also:Object {0: 1, 1: 2, 2: 3} The numeric property automatic sorted in ascending order. I tried prefix or postfix the numeric property like

var a = {};
a['p'+0]=1;
a['p'+1]=2;
a['p'+2]=3;
console.log(a);//Object {p0: 1, p1: 2, p2: 3}

And this keep the property order. Is this the best way to solve the problem? And is there anyway to prevent this auto sort behavior? Is this only happen in Chrome V8 JavaScript engine? Thank you in advance!

Priestcraft answered 26/10, 2015 at 17:22 Comment(5)
Why you're using an object at all?Slaphappy
Objects are intrinsically unordered key-value pairs. They don't have an order, so don't expect them to keep one. What is your actual problem?Aloise
See edited guys. Sorry for confusion.Priestcraft
@TinyGiant Thanks for your code. But what if I want to find the mode which appear first? In your code, if the input is [5,5,4,3,2,1,1], how can I get 5 return instead of 1?Priestcraft
in your answer you told that using string in the key keeps the order as it it but in your example you showed only ordered assignment. Did you try unordered assignment with string as well?Tocci
K
12

You are using a JS object, that by definition does not keep order. Think of it as a key => value map.

You should be using an array, that will keep whatever you insert on the index you inserted it into. Think of it as a list.

Also notice that you did not in fact "reverse the order of the assignment", because you inserted elements on the same index every time.

Komarek answered 26/10, 2015 at 17:31 Comment(4)
Yeah I know this. Seems like I didn't explain my problem clearly...I've edit my question.Priestcraft
@Priestcraft your problem remains the same: you are trying to keep order in an object that simply does not keep order. You probably need a better data structure to solve the other problem you have, using arrays for the ordering and maybe an object for the count.Komarek
Ok I'll try another way. Thank you!Priestcraft
The answer is outdated. JS objects have ordered keys since ES2015 (yes, by spec). It's just that numeric keys behave differently.Cowhide
N
17
target = {}
target[' ' + key] = value // numeric key

This can prevent automatic sort of Object numeric property.

Negatron answered 19/1, 2018 at 2:38 Comment(1)
This is the same as @Priestcraft has suggested to use a prefix. The difference is that in your case it is a prefix with a space instead a letter.Clonus
W
15

You really can't rely on order of an object fields in JavaScript, but I can suggest to use Map (ES6/ES2015 standard) if you need to preserve order of your key, value pair object. See the snippet below:

let myObject = new Map();
myObject.set('z', 33);
myObject.set('1', 100);
myObject.set('b', 3);

for (let [key, value] of myObject) {
  console.log(key, value);
}
// z 33
// 1 100
// b 3
Wiser answered 24/7, 2018 at 11:50 Comment(0)
K
12

You are using a JS object, that by definition does not keep order. Think of it as a key => value map.

You should be using an array, that will keep whatever you insert on the index you inserted it into. Think of it as a list.

Also notice that you did not in fact "reverse the order of the assignment", because you inserted elements on the same index every time.

Komarek answered 26/10, 2015 at 17:31 Comment(4)
Yeah I know this. Seems like I didn't explain my problem clearly...I've edit my question.Priestcraft
@Priestcraft your problem remains the same: you are trying to keep order in an object that simply does not keep order. You probably need a better data structure to solve the other problem you have, using arrays for the ordering and maybe an object for the count.Komarek
Ok I'll try another way. Thank you!Priestcraft
The answer is outdated. JS objects have ordered keys since ES2015 (yes, by spec). It's just that numeric keys behave differently.Cowhide
G
6

This is an old topic but it is still worth mentioning as it is hard to find a straight explanation in one-minute googling.

I recently had a coding exercise that finding the first occurrence of the least/most frequent integer in an array, it is pretty much the same as your case.

I encountered the same problem as you, having the numeric keys sorted by ASC in JavaScript object, which is not preserving the original order of elements, which is the default behavior in js.

A better way to solve this in ES6 is to use a new data type called: Map

Map can preserve the original order of elements(pairs), and also have the unique key benefit from object.

let map = new Map()
map.set(4, "first") // Map(1) {4 => "first"}
map.set(1, "second") // Map(2) {4 => "first", 1 => "second"}
map.set(2, "third") // Map(3) {4 => "first", 1 => "second", 2 => "third"}
for(let [key, value] of map) {
  console.log(key, value)
}
// 4 "first"
// 1 "second"
// 2 "third"

However, using the object data type can also solve the problem, but we need the help of the input array to get back the original order of elements:

function findMostAndLeast(arr) {
  let countsMap = {};
  let mostFreq = 0;
  let leastFreq = arr.length;
  let mostFreqEl, leastFreqEl;

  for (let i = 0; i < arr.length; i++) {
    let el = arr[i];
    // Count each occurrence
    if (countsMap[el] === undefined) {
      countsMap[el] = 1;
    } else {
      countsMap[el] += 1;
    }
  }

  // Since the object is sorted by keys by default in JS, have to loop again the original array
  for (let i = 0; i < arr.length; i++) {
    const el = arr[i];

    // find the least frequent 
    if (leastFreq > countsMap[el]) {
      leastFreqEl = Number(el);
      leastFreq = countsMap[el];
    }

    // find the most frequent 
    if (countsMap[el] > mostFreq) {
      mostFreqEl = Number(el);
      mostFreq = countsMap[el];
    }
  }

  return {
    most_frequent: mostFreqEl,
    least_frequent: leastFreqEl
  }
}
const testData = [6, 1, 3, 2, 4, 7, 8, 9, 10, 4, 4, 4, 10, 1, 1, 1, 1, 6, 6, 6, 6];    
console.log(findMostAndLeast(testData)); // { most_frequent: 6, least_frequent: 3 }, it gets 6, 3 instead of 1, 2 
Guan answered 26/4, 2020 at 21:13 Comment(0)
F
3

To prevent the automatic sort of numeric keys of Object in Javascript, the best way is to tweak the Object keys a little bit.

We can insert an "e" in front of every key name to avoid lexicographical sorting of keys and to get the proper output slice the "e", by using the following code;

object_1 = {
      "3": 11,
      "2": 12,
      "1": 13
      }

let automaticSortedKeys = Object.keys(object_1);
console.log(automaticSortedKeys)     //["1", "2", "3"]

object_2 = {
      "e3": 11,
      "e2": 12,
      "e1": 13
      }
    
let rawObjectKeys = Object.keys(object_2); 
console.log(rawObjectKeys)   //["e3", "e2", "e1"]
    
let properKeys = rawObjectKeys.map(function(element){
      return element.slice(1)
      }); 
console.log(properKeys)     //["3", "2", "1"]
Frontward answered 27/6, 2021 at 11:17 Comment(0)
M
2

instead of generating an object like {5: 2, 2: 2, 1: 1}

generate an array to the effect of

[
   {key: 5, val: 2},
   {key: 2, val: 2},
   {key: 1, val: 1}
]

or... keep track of the sort order in a separate value or key

Malayopolynesian answered 28/9, 2017 at 22:5 Comment(0)
B
2

The simplest and the best way to preserve the order of the keys in the array obtained by Object.keys() is to manipulate the Object keys a little bit.

insert a "_" in front of every key name. then run the following code!

myObject = {
  _a: 1,
  _1: 2,
  _2: 3
}

const myObjectRawKeysArray = Object.keys(myObject); 
console.log(myObjectRawKeysArray)
//["_a", "_1", "_2"]

const myDesiredKeysArray = myObjectRawKeysArray.map(rawKey => {return rawKey.slice(1)}); 
console.log(myDesiredKeysArray)
//["a", "1", "2"]

You get the desired order in the array with just a few lines of code. hApPy CoDiNg :)

Buryat answered 17/8, 2020 at 18:28 Comment(0)
E
1

I came across this same problem, and after search a lot about that, i found out that the solution to prevent this behavior is make key as string.

Like that:

{"a": 2, "b": 2}
Electrosurgery answered 9/8, 2018 at 16:54 Comment(1)
Yes, the key should be string except for the string value of a number. If you do something like let obj = {}, obj["5"] = 2; obj["2"] = 2, the key order will still be 2,5 which is not you want. Have to use some prefix.Priestcraft
U
1

I've stumbled with this issue with our normalised array which keyed with Ids> After did my research, I found out there's no way to fix using the object keys because by default the Javascript is sorting any object key with number when you iterate it.

The solution I've done and it worked for me is to put a 'sortIndex' field and used that to sort the list.

Unvalued answered 30/9, 2019 at 1:0 Comment(0)
H
0
  1. you can use Map() in javascript ES6 which will keep the order of the keys insertion.

  2. just trying to solve your problem in an alternative solution, recently like to practise leetcode-like question

function solution(arr) {
  const obj = {};
  const record = {
    value: null,
    count: 0
  };

  for (let i = 0; i < arr.length; i++) {
    let current = arr[i];
    if (!obj[current]) {
      obj[current] = 0;
    }

    obj[current]++;

    if (obj[current] > record.count) {
      record.value = current;
      record.count = obj[current];
    }
  }

  console.log("mode number: ", record.value);
  console.log("mode number count: ", record.count);
}
Hyperthermia answered 20/6, 2020 at 13:13 Comment(0)
F
0

You can add an extra space to the object keys. It's hacky fix but it works.

const obj = {
  '0': 0,
  'date': 0,
  '2': 2,
  '1': 1
}

const objectWithSpaceInKey = {
  '0 ': 0,
  'date': 0,
  '2 ': 2,
  '1 ': 1
}

console.log(obj)
/* Output: {
  "0": 0,
  "1": 1,
  "2": 2,
  "date": 0
} */

console.log(objectWithSpaceInKey)
/* Output: {
  "0 ": 0,
  "date": 0,
  "2 ": 2,
  "1 ": 1
} */
Fare answered 1/3 at 4:18 Comment(0)
K
0

The easiest way I have found is this:

  const romanNumbers = [
    { value: 1000, symbol: 'M' },
    { value: 900, symbol: 'CM' },
    { value: 500, symbol: 'D' },
    { value: 400, symbol: 'CD' },
    { value: 100, symbol: 'C' },
    { value: 90, symbol: 'XC' },
    { value: 50, symbol: 'L' },
    { value: 40, symbol: 'XL' },
    { value: 10, symbol: 'X' },
    { value: 9, symbol: 'IX' },
    { value: 5, symbol: 'V' },
    { value: 4, symbol: 'IV' },
    { value: 1, symbol: 'I' },
  ];

and to iterate through:

  let romanNumber = '';
  //iterate through the array
  for (let i = 0; i < romanNumbers.length; i++) {
    //for current entry compare the value
    while (decimalNumber >= romanNumbers[i].value) {
      //for current entry value get the pair symbol
      romanNumber += romanNumbers[i].symbol;
      //reduce decimal number by the current arry entry value
      decimalNumber -= romanNumbers[i].value;
    }
  }
  return romanNumber;
Karlin answered 19/3 at 9:45 Comment(0)
E
-1

simply do that while you're working with a numeric array index

data      = {}
data[key] = value
Eatable answered 16/7, 2018 at 14:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.