I have an array with alot of items, and I am creating a list of them. I was thinking of paginating the list. I wonder how can I start a forEach or for loop at some index in an array, that would be in my example the number of items in the list on each page, so that I don't need to iterate over the whole array in each loop?
arr.forEach(function (item) {
someFn(item);
})
for (var i = 0, len = arr.length; i < len; i++) {
someFn(arr[i]);
}
You could use a copy of the array, by using Array#slice
The slice() method returns a shallow copy of a portion of an array into a new array object selected from begin to end (end not included). The original array will not be modified.
array.slice(10, 20).forEach(someFn); // only for functions which respects API of forEach*
* parameters for a callback
Or you can start at a given index and end at a given index.
for (var i = 10, len = Math.min(20, arr.length); i < len; i++) {
someFn(arr[i]);
}
With
Math.min(20, arr.length)
returns a value, if the array is smaller than the given value 20. For example if the array has only index 0 ... 14, you get as result 15.
Unfortunately Array#forEach iterates over every element in the given array, but you could apply a simple condition to determine to which elements (with specified index) apply the given function.
i > 3 ? someFn(item) : null;
^ if index more than 3 - call the function
var arr = [1,2,3,4,5,6,7];
function someFn(elem){
console.log(elem);
}
arr.forEach(function(item, i) {
return i > 3 ? someFn(item) : null;
})
forEach doesn't offer that feature, no. So your choices are:
A simple for loop
Ignoring the indexes you don't want to handle (as in Kind user's answer)
Using slice (as in Nina's answer)
Writing your own function
Here's #4 as an Array.prototype extension (non-enumerable, of course; adding enumerable properties to Array.prototype breaks a lot of code); after it is a standalone version for when adding to Array.prototype isn't appropriate:
// Giving ourselves the function
Object.defineProperty(Array.prototype, "myEach", {
value: function(from, to, callback, thisArg) {
if (typeof from === "function") {
thisArg = callback;
callback = to;
to = from;
from = 0;
}
if (typeof to === "function") {
thisArg = callback;
callback = to;
to = this.length;
}
for (var n = from; n < to; ++n) {
callback.call(thisArg, this[n], n, this);
}
}
});
// Using it:
var arr = ["zero", "one", "two", "three", "four", "five", "six", "seven"];
console.log("*** From 3:");
arr.myEach(3, function(e) { console.log(e); });
console.log("*** From 3 (inclusive) to 5 (exclusive):");
arr.myEach(3, 5, function(e) { console.log(e); });
console.log("*** All:");
arr.myEach(function(e) { console.log(e); });
console.log("*** Check thisArg handling on 0-2:");
var o = {answer: 42};
arr.myEach(0, 2, function(e) {
console.log(e + " (this.answer = " + this.answer + ")");
}, o);
.as-console-wrapper {
max-height: 100% !important;
}
Again note that that's a non-enumerable property, which is vital if you ever add anything to Array.prototype (otherwise, you break a lot of code).
You wouldn't do that in a library to be consumed by others, you'd just have a standalone function:
// Giving ourselves the function
function myEach(array, from, to, callback, thisArg) {
if (typeof from === "function") {
thisArg = callback;
callback = to;
to = from;
from = 0;
}
if (typeof to === "function") {
thisArg = callback;
callback = to;
to = array.length;
}
for (var n = from; n < to; ++n) {
callback.call(thisArg, array[n], n, array);
}
}
// Using it:
var arr = ["zero", "one", "two", "three", "four", "five", "six", "seven"];
console.log("*** From 3:");
myEach(arr, 3, function(e) {
console.log(e);
});
console.log("*** From 3 (inclusive) to 5 (exclusive):");
myEach(arr, 3, 5, function(e) {
console.log(e);
});
console.log("*** All:");
myEach(arr, function(e) {
console.log(e);
});
console.log("*** Check thisArg handling on 0-2:");
var o = {answer: 42};
myEach(arr, 0, 2, function(e) {
console.log(e + " (this.answer = " + this.answer + ")");
}, o);
.as-console-wrapper {
max-height: 100% !important;
}
Thinking on what #NinaScholz commented, perhaps you can use variables and any changes would be set in those instead of changing the loop.
function someFn(item, array2){
array2.push(item, array2);
}
var arrayItems1 = [1,2,3,4,5,6,7,8,9,10];
var arrayItems2 = [];
var firstIndex = 1;
var lastIndex = 5;
var i = 0;
for (i = firstIndex; i < lastIndex; i++){
someFn(arrayItems1[i], arrayItems2);
}
alert(arrayItems2.join(' '));
You could apply some kind of implementation of the iterator pattern.
var Iterator = function (list, position) {
return {
isNext: function () {
return position + 1 < list.length;
},
isDefined: function () {
return (position < list.length && position >= 0);
},
element: function () {
return list[position];
},
position: function () {
return position;
},
moveNext: function () {
if (this.isNext()) { return Iterator(list, position + 1); }
return Iterator([], 0);
}
}
Iterator.forEach = function (action, iterator, length) {
var counter = 0;
while (counter < length && iterator.isDefined()) {
counter = counter + 1;
action(iterator.element(), iterator.position());
iterator = iterator.moveNext();
}
return iterator;
}
And then have an iterator to use for going over the list and keep the state of the last iteration over a list.
var list = [1, 3, 5, 3, 6];
var iterator = Iterator(list, 0);
iterator = Iterator.forEach(function (element, index) {
console.log(element, index);
}, iterator, 3);
//1 0
//3 1
//5 2
Iterator.forEach(function (element, index) {
console.log(element, index);
}, iterator, 5);
//3 3
//6 4
array.values() to get the iterator, .next() it and use it.
let ar=[1,2,3,4]
var _;for(let a of(_=ar.values(),_.next(),_)){
console.log(a)
}
Related
I am a junior developer who has been coding for 4 weeks.
I'm working on a JavaScript method.
I'll show you the code I used first.
_.each = function (collection, iteratee) {
if(Array.isArray(collection)===true){
for(let i=0;i<collection.length;i++){
iteratee(collection[i],i,collection)
}
}else{
let objvalues= Object.values(collection)
let objkeys = Object.keys(collection)
for(let i=0;i<objvalues.length;i++){
iteratee(objvalues[i],objkeys[i],collection)
}
}
};
_.includes = function (arr, target) {
let result
_.each(arr, function(a){
if(a === target)
result = true
if (a !== target)
result = false
})
return result;
};
It's a condition.
If the _.include method matches the value found by the element in the array, the true must be returned.
If the element in the array does not match the value you are looking for, you must return false.
I made the _include method.
If the element in the array does not match the value you are looking for, the return to false is successful.ten thousand
If the element in the array matches the value you are looking for, you must return true
This is where you fail.
It seems that the ture cannot be returned and only false is returned.
How should I handle this?
The problem is here:
_.each(arr, function(a){
if(a === target)
result = true
if (a !== target)
result = false
})
You reassign result on every iteration. As a result, the only iteration that matters for the final value of result is the last iteration.
Instead, initialize result to false, and reassign to true when the target is found:
const _ = {};
_.each = function(collection, iteratee) {
if (Array.isArray(collection) === true) {
for (let i = 0; i < collection.length; i++) {
iteratee(collection[i], i, collection)
}
} else {
let objvalues = Object.values(collection)
let objkeys = Object.keys(collection)
for (let i = 0; i < objvalues.length; i++) {
iteratee(objvalues[i], objkeys[i], collection)
}
}
};
_.includes = function(arr, target) {
let result = false;
_.each(arr, function(a) {
if (a === target)
result = true
})
return result;
};
console.log(
_.includes([1, 2, 3], 2)
);
It'd be cleaner to break the loop once a match is found, but your _each isn't set up for that:
const _ = {};
_.each = function(collection, iteratee) {
if (Array.isArray(collection) === true) {
for (let i = 0; i < collection.length; i++) {
iteratee(collection[i], i, collection)
}
} else {
let objvalues = Object.values(collection)
let objkeys = Object.keys(collection)
for (let i = 0; i < objvalues.length; i++) {
iteratee(objvalues[i], objkeys[i], collection)
}
}
};
_.includes = function(arr, target) {
for (const a of arr) {
if (a === target)
return true
}
return false;
};
console.log(
_.includes([1, 2, 3], 2)
);
In addition to what CertainPerfomance already said, you could see how Array.includes was implemented to get an inspiration, here is the specifications from TC39.
[1,2,3,4,5].duplicate(); // [1,2,3,4,5,1,2,3,4,5]
Maybe something like:
var array = [1,2,3,4,5];
array.push(array);
But what's the fastest method?
You can use concat and replace the original array with the new one:
array = array.concat(array);
Or push with apply (modifies the original array):
[].push.apply(array, array);
Or push with the spread operator (modifies the original array):
array.push(...array);
The FASTEST way to do something in JS is usually using simple C-like native statements.
I think this will be the fastest one:
function duplicate(arr) {
for(var i=0,len=arr.length;i<len;i++)
arr[i+len]=arr[i];
}
A slower but more elegant one would be this:
arr=arr.concat(arr);
Or this one:
[].push.apply(arr,arr);
EcmaScript 6 also allows you to do the same using the spread operator. This operator just puts all the values of the array for you in the place you have written it, so this var arr = [0,1,2,3];console.log(...arr) turns into var arr = [0,1,2,3];console.log(0,1,2,3).
arr.push(...arr);
However, EcmaScript 6 is not yet widely supported (and this feature neither) so if I were you I wouldn't use it. Although ES 6 has just been approved & released (as #LyeFish said in the comments) most of browsers are still working on it.
EcmaScript-262 6th edition officially released 5 days ago (thanks to #LyeFish)!
http://www.ecma-international.org/news/index.html
According to my test results, the winners are concat followed by push methods. I also tested looping method and my own newly-introduced splice routine.
Here's the testing "infrastructure" code that was used:
var toArray = Function.prototype.call.bind(Array.prototype.slice);
Function.prototype.testerTimes = function(number) {
var func = this;
return function() {
var args = toArray(arguments), i = 0;
for( ; i < number; func.apply(this, args), i++);
};
};
Function.prototype.testerTime = function(units) {
var func = this;
return function() {
var start = Date.now(), diff;
func.apply(this, toArray(arguments));
diff = Date.now() - start;
return units === "s" ? diff / 1000 : units === "m" ? diff / 60000 : units === "h" ? diff / 3600000 : diff;
};
};
Function.prototype.testerToConsole = function(prefix, message) {
var func = this;
return function() {
console.log(prefix + message + func.apply(this, toArray(arguments)));
};
};
Function.prototype.makeTestReady = function(times, units, prefix, message) {
return this.testerTimes(times).testerTime(units).testerToConsole(prefix, message);
};
Here's the test code:
function genArray(num) {
for(var i = 0, arr = []; i < num; arr.push(++i));
return arr;
};
var numberOfRuns = 1000000;
var timeUnit = "s";
var messagePrefix = " ";
var funcs = [
function duplicateConcat(arr) {
var arrCopy = arr.slice(0);
return arrCopy.concat(arrCopy);
},
function duplicatePush(arr) {
var arrCopy = arr.slice(0);
arrCopy.push.apply(arrCopy, arrCopy);
return arrCopy;
},
function duplicateLoop(arr) {
var arrCopy = arr.slice(0);
for(var i = 0, len = arrCopy.length; i < len; i++) {
arrCopy[len + i] = arrCopy[i];
}
return arrCopy;
},
function duplicateSplice(arr) {
var arrCopy = arr.slice(0);
arrCopy.splice.apply(arrCopy, [arrCopy.length, 0].concat(arrCopy));
return arrCopy;
}
].map(function(func, index, arr) {
return func.makeTestReady(numberOfRuns, timeUnit, messagePrefix, func.name + ": ");
});
for(var i = 5; i < 25; i+= 5) {
console.log(i + "-element array:");
funcs.forEach(function(func) {
func(genArray(i));
});
}
And, here are the results of 1,000,000 runs of each function for arrays of sizes 5, 10, 15, and 20:
5-element array:
duplicateConcat: 0.236
duplicatePush: 0.228
duplicateLoop: 0.372
duplicateSplice: 0.45
10-element array:
duplicateConcat: 0.241
duplicatePush: 0.273
duplicateLoop: 0.433
duplicateSplice: 0.48
15-element array:
duplicateConcat: 0.261
duplicatePush: 0.293
duplicateLoop: 0.5
duplicateSplice: 0.522
20-element array:
duplicateConcat: 0.24
duplicatePush: 0.311
duplicateLoop: 0.602
duplicateSplice: 0.558
For instance, a variable named arrayElements of type array contains:
[{id:1, value:5},{id:2, value:6},{id:3, value:7},{id:4, value:8}].
How do I get the position of the array element with id === 3(3rd element) in the arrayElements variable besides using loop?
thanks.
You have to loop at one point. But you can abstract it to look like you're not looping
function indexOfCallback(arr, callback, startIndex) {
if (typeof startIndex == 'undefined') {
startIndex = 0;
}
for(var i=startIndex; i < arr.length; i ++) {
if (callback(arr[i])) {
return i;
}
}
return -1;
}
var array = [{id:1, value:5},{id:2, value:6},{id:3, value:7},{id:4, value:8}];
// Search on id === 3
console.log(indexOfCallback(array, function(obj){
return obj.id === 3;
}));
// Search on value === 6
console.log(indexOfCallback(array, function(obj){
return obj.value === 6;
}));
As mentioned by Anthony, this is proposed for ECMAScript 6. Here's the more complete polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
if (!Array.prototype.findIndex) {
Array.prototype.findIndex = function(predicate) {
if (this == null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return i;
}
}
return -1;
};
}
console.log(array.findIndex(function(obj){
return obj.id === 3;
}));
arrayElements.map(o => o.id).indexOf(3);
Notes:
Possibly slower than a loop because transforms whole array prior to
search. But with high-level languages like Javascript, you never
know.
Infinitely more readable than a loop.
IE compatible (unlike findIndex as of 2017).
In an array like this, you cant access elements by id. So using a loop is the best solution you have. However, depending on your use case you could also consider using an object instead of an array for direct access.
var container = { 1: {id:1, value:5}, 2: {id:2, value:6}, 3: {id:3, value:7} }
const arrayElements = [
{ id: 1, value: 5 },
{ id: 2, value: 6 },
{ id: 3, value: 7 },
{ id: 4, value: 8 }
]
console.log(arrayElements.findIndex((item) => item.id === 3))
You can use an array filter but I think that you will get a better solution using a loop.
var array = [{id:1, value:5},{id:2, value:6},{id:3, value:7},{id:4, value:8}];
var result = array.filter(condition);
function condition(value, index){
if (value.id === 3) return index;
}
console.log(result);
I wrote a function for you that you can use get the job done, but it uses a loop:
var yourObjArray = [{id:1, value:5},{id:2, value:6},{id:3, value:7},{id:4, value:8}];
function objArrayIndex(objArray){
for(var i = 0; i < objArray.length; i++){
if(objArray[i]['id'] == 3){
return i;
}
}
return -1;
}
console.log(objArrayIndex(yourObjArray));
I have an array of arrays as follows:
[[3, 4], [1, 2], [3, 4]]
I wish to create a new array of arrays that has no duplicates, and has a count of the number of occurrences of each element in the first array:
[[3,4,2], [1,2,1]]
here is what I have so far:
var alreadyAdded = 0;
dataset.forEach(function(data) {
From = data[0];
To = data[1];
index = 0;
newDataSet.forEach(function(newdata) {
newFrom = newData[0];
newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});
I am very new to Javascript, can someone help explain to me what I'm doing wrong? I'm sure there is a more concise way of doing this, however I wasn't able to find an example in javascript that dealt with duplicate array of arrays.
Depending on how large the dataset is that you're iterating over I'd be cautious of looping over it so many times. You can avoid having to do that by creating an 'index' for each element in the original dataset and then using it to reference the elements in your grouping. This is the approach that I took when I solved the problem. You can see it here on jsfiddle. I used Array.prototype.reduce to create an object literal which contained the grouping of elements from the original dataset. Then I iterated over it's keys to create the final grouping.
var dataSet = [[3,4], [1,2], [3,4]],
grouping = [],
counts,
keys,
current;
counts = dataSet.reduce(function(acc, elem) {
var key = elem[0] + ':' + elem[1];
if (!acc.hasOwnProperty(key)) {
acc[key] = {elem: elem, count: 0}
}
acc[key].count += 1;
return acc;
}, {});
keys = Object.keys(counts);
for (var i = 0, l = keys.length; i < l; i++) {
current = counts[keys[i]];
current.elem.push(current.count);
grouping.push(current.elem);
}
console.log(grouping);
Assuming order of sub array items matters, assuming that your sub arrays could be of variable length and could contain items other than numbers, here is a fairly generic way to approach the problem. Requires ECMA5 compatibility as it stands, but would not be hard to make it work on ECMA3.
Javascript
// Create shortcuts for prototype methods
var toClass = Object.prototype.toString.call.bind(Object.prototype.toString),
aSlice = Array.prototype.slice.call.bind(Array.prototype.slice);
// A generic deepEqual defined by commonjs
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (toClass(a) === '[object Date]' && toClass(b) === '[object Date]') {
return a.getTime() === b.getTime();
}
if (toClass(a) === '[object RegExp]' && toClass(b) === '[object RegExp]') {
return a.toString() === b.toString();
}
if (a && typeof a !== 'object' && b && typeof b !== 'object') {
return a == b;
}
if (a.prototype !== b.prototype) {
return false;
}
if (toClass(a) === '[object Arguments]') {
if (toClass(b) !== '[object Arguments]') {
return false;
}
return deepEqual(aSlice(a), aSlice(b));
}
var ka,
kb,
length,
index,
it;
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (eDE) {
return false;
}
length = ka.length;
if (length !== kb.length) {
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
} else {
return false;
}
} else {
ka.sort();
kb.sort();
for (index = 0; index < length; index += 1) {
if (ka[index] !== kb[index]) {
return false;
}
}
}
for (index = 0; index < length; index += 1) {
it = ka[index];
if (!deepEqual(a[it], b[it])) {
return false;
}
}
return true;
};
// Recursive function for counting arrays as specified
// a must be an array of arrays
// dupsArray is used to keep count when recursing
function countDups(a, dupsArray) {
dupsArray = Array.isArray(dupsArray) ? dupsArray : [];
var copy,
current,
count;
if (a.length) {
copy = a.slice();
current = copy.pop();
count = 1;
copy = copy.filter(function (item) {
var isEqual = deepEqual(current, item);
if (isEqual) {
count += 1;
}
return !isEqual;
});
current.push(count);
dupsArray.push(current);
if (copy.length) {
countDups(copy, dupsArray);
}
}
return dupsArray;
}
var x = [
[3, 4],
[1, 2],
[3, 4]
];
console.log(JSON.stringify(countDups(x)));
Output
[[3,4,2],[1,2,1]]
on jsFiddle
After fixing a typo I tried your solution in the debugger; it works!
Fixed the inner forEach-loop variable name to match case. Also some var-keywords added.
var alreadyAdded = 0;
dataset.forEach(function (data) {
var From = data[0];
var To = data[1];
var index = 0;
newDataSet.forEach(function (newData) {
var newFrom = newData[0];
var newTo = newData[1];
// check if the point we are looking for is already added to the new array
if ((From == newFrom) && (To == newTo)) {
// if it is, increment the count for that pair
var count = newData[2];
var newCount = count + 1;
newDataSet[index] = [newFrom, newTo, newCount];
test = "reached here";
alreadyAdded = 1;
}
index++;
});
// the pair was not already added to the new dataset, add it
if (alreadyAdded == 0) {
newDataSet.push([From, To, 1]);
}
// reset alreadyAdded variable
alreadyAdded = 0;
});
const x = [[3, 4], [1, 2], [3, 4]];
const with_duplicate_count = [
...x
.map(JSON.stringify)
.reduce( (acc, v) => acc.set(v, (acc.get(v) || 0) + 1), new Map() )
.entries()
].map(([k, v]) => JSON.parse(k).concat(v));
console.log(with_duplicate_count);
JavaScript objects have no order stored for properties (according to the spec). Firefox seems to preserve the order of definition of properties when using a for...in loop. Is this behaviour something that I can rely on? If not is there a piece of JavaScript code somewhere that implements an ordered hash type?
JavaScript in 2016, specifically EcmaScript 6, supports the Map built-in class.
A Map object iterates its elements in insertion order — a for...of loop returns an array of [key, value] for each iteration.
That's what you need. (I wonder why that is the first info in the description of this data structure, though.)
For example,
m = new Map()
m.set(3,'three')
m.set(1,'one')
m.set(2,'two')
m // Map { 3 => 'three', 1 => 'one', 2 => 'two' }
[...m.keys()] // [ 3, 1, 2 ]
or the example from the docs:
var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
myMap // Map { 0 => 'zero', 1 => 'one' }
for (var [key, value] of myMap) {
console.log(key + " = " + value);
}
for (var key of myMap.keys()) {
console.log(key);
}
for (var value of myMap.values()) {
console.log(value);
}
for (var [key, value] of myMap.entries()) {
console.log(key + " = " + value);
}
#Vardhan 's answer in plain JavaScript, using closure instead of classical OO and adding an insert() method:
function makeOrderedHash() {
let keys = [];
let vals = {};
return {
push: function(k,v) {
if (!vals[k]) keys.push(k);
vals[k] = v;
},
insert: function(pos,k,v) {
if (!vals[k]) {
keys.splice(pos,0,k);
vals[k] = v;
}
},
val: function(k) {return vals[k]},
length: function(){return keys.length},
keys: function(){return keys},
values: function(){return vals}
};
};
let myHash = makeOrderedHash();
No, since the Object type is specified to be an unordered collection of properties, you can not rely on that. (Or: You can only rely on that an object is an unordered collection of properties.)
If you want to have an ordered hash set, you will need to implement it on your own.
This question come up as the top search result. After not finding a ordered hash, i just wrote this small coffescript. Hopefully this will help folks landing on this page:
## OrderedHash
# f = new OrderedHash
# f.push('a', 1)
# f.keys()
#
class OrderedHash
constructor: ->
#m_keys = []
#m_vals = {}
push: (k,v) ->
if not #m_vals[k]
#m_keys.push k
#m_vals[k] = v
length: () -> return #m_keys.length
keys: () -> return #m_keys
val: (k) -> return #m_vals[k]
vals: () -> return #m_vals
One trick I do is to store the data in a regular unordered hash, and then store the preferred order in an array. In JS, you can even make the order array part of the hash itself.
var myHash = {
a: "2",
b: "3",
c: "1"
};
myHash.order = [ myHash.c, myHash.a, myHash.b ];
alert("I can access values by key. Here's B: " + myHash.b);
var message = "I can access also loop over the values in order: ";
for (var i=0;i<myHash.order.length;i++)
{
message = message + myHash.order[i] + ", ";
}
alert(message)
It's not exactly elegant, but it gets the job done.
Realize its late but I needed this and couldn't find it elsewhere.
*UPDATE
Added necessary non-enumerable methods and properties.
Quick ES 5 implementation (polyfill as needed):
function orderedHash(object) {
'use strict'
var obj = object || {}
Object.defineProperties(this, {
'length': {
value: 0,
writable: true
},
'keys' : {
value: [],
writable: true
},
'sortedBy': {
value: '',
writable: true
}
})
this.hash(obj)
obj = null
}
Object.defineProperties(orderedHash.prototype, {
'sortByKeys': {
value: function sortByKeys() {
var i, len, name
this.keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.keys.length; i < len; ++i) {
name = this.keys[i]
this[i] = this[name]
}
this.sortedBy = 'keys'
return null
}
},
'sortByValues': {
value: function sortByValues() {
var i, len, newIndex, name, ordered = [], names = this.keys.splice(0)
this.keys = []
for (i=0, len = this.length; i < len; ++i) {
ordered.push(this[i])
ordered.sort(function(a, b) {
return a >= b ? 1 : -1
})
newIndex = ordered.lastIndexOf(this[i])
name = names[i]
this.keys.splice(newIndex, 0 , name)
}
for (i=0, len = ordered.length; i < len; ++i) {
this[i] = ordered[i]
}
this.sortedBy = 'values'
return null
}
},
'insert': {
value: function insert(name, val) {
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
},
'remove': {
value: function remove(name) {
var keys, index, i, len
delete this[name]
index = this.keys[name]
this.keys.splice(index, 1)
keys = Object.keys(this)
keys.sort(function(a, b) {
return a >= b ? 1 : -1
})
for (i=0, len = this.length; i < len; ++i) {
if (i >= index) {
this[i] = this[i + 1]
}
}
delete this[this.length - 1]
this.length -= 1
return null
}
},
'toString': {
value: function toString() {
var i, len, string = ""
for (i=0, len = this.length; i < len; ++i) {
string += this.keys[i]
string += ':'
string += this[i].toString()
if (!(i == len - 1)) {
string += ', '
}
}
return string
}
},
'toArray': {
value: function toArray() {
var i, len, arr = []
for (i=0, len = this.length; i < len; ++i) {
arr.push(this[i])
}
return arr
}
},
'getKeys': {
value: function getKeys() {
return this.keys.splice(0)
}
},
'hash': {
value: function hash(obj) {
var i, len, keys, name, val
keys = Object.keys(obj)
for (i=0, len = keys.length; i < len; ++i) {
name = keys[i]
val = obj[name]
this[this.length] = val
this.length += 1
this.keys.push(name)
Object.defineProperty(this, name, {
value: val,
writable: true,
configurable: true
})
}
if (this.sortedBy == 'keys') {
this.sortByKeys()
} else {
this.sortByValues()
}
return null
}
}
})
What happens here is that by using Object.defineProperty() instead of assignment can we make the properties non-enumerable, so when we iterate over the hash using for...in or Object.keys() we only get the ordered values but if we check hash.propertyname it will be there.
There are methods provided for insertion, removal, assimilating other objects (hash()), sorting by key, sorting by value, converting to array or string, getting the original index names, etc. I added them to the prototype but they are also non-enumerable, for...in loops still work.
I didn't take time to test it on non-primitives, but it works fine for strings, numbers, etc.
Taking #Craig_Walker solution, if you are only interested to know in which order the properties have been inserted, an easy solution would be :
var obj ={ }
var order = [];
function add(key, value) {
obj[key] = value;
order.push(key);
}
function getOldestKey() {
var key = order.shift();
return obj[key]
}
function getNewsetKey() {
var key = order.pop();
return obj[key]
}
You can now use a native Map since it preserves the insertion order when looped over with for in
A fairly simple way is to use an array to store the order.
You need to write a custom compare function to establish the order you require.
The down side is that you have to sort the array and keep track of relations, each time you change the hash table.
var order=[];
var hash={"h1":4,"h2":2,"h3":3,"h4":1};
function cmp(a,b) {
if (hash[a] < hash[b]) return -1;
if (hash[a] > hash[b]) return 1;
return 0;
}
// Add initial hash object to order array
for(i in hash) order.push(i);
order.sort(cmp);
// h4:1 h2:2 h3:3 h1:4
// Add entry
hash['h5']=2.5;
order.push('h5');
order.sort(cmp);
// h4:1 h2:2 h5:2.5 h3:3 h1:4
// Delete entry
order.splice(order.indexOf('h5'), 1);
delete hash['h5'];
// h4:1 h2:2 h3:3 h1:4
// Display ordered hash array (with keys)
for(i in order) console.log(order[i],hash[order[i]]);
class #OrderedHash
constructor: (h_as_array=[])->
#keys = []
#vals = {}
if h_as_array.length > 0
i = 0
while i < h_as_array.length
#push(h_as_array[i], h_as_array[i+1])
i += 2
#
push: (k,v)->
#keys.push k if not #vals[k]
#vals[k] = v
length: ()-> return #keys.length
keys: ()-> return #keys
val: (k)-> return #vals[k]
vals: ()-> return #vals
each: (callback)->
return unless callback
for k in #keys
callback(#vals[k])