0

i have something like

var keyVals = Array;
keyVals['23'] = 234;
keyVals['58'] = 'sunshine';
keyVals['172'] = 'lolipops';

newVar = 76;

how can i find which of the keys in the array is closest in value (once made into numbers) to my newVar? which of 23, 58, 172 is closest to my 76?

thanks

skrite
  • 317
  • 4
  • 18
  • You aren't using the array as an array. – vcsjones Aug 16 '12 at 14:11
  • 1
    This isn't an array, but an object... Also `var keyVals = Array`? if you _want_ to use the constructor write `var keyVals = new Array();` or -in your case- `var keyVals = new Object();`. But you're best of using `var keyVals = [];` for arrays and `var keyVals = {}` for objects – Elias Van Ootegem Aug 16 '12 at 14:12
  • Why is he not using it as an Array? All the properties are numeric. Arrays can be sparse. – gray state is coming Aug 16 '12 at 14:14
  • 1
    @user1600680: open your console, paste the snippet and type `keyVals.length` (returns `1`), that's why it's not an array. After that try `foo = []; foo['123'] = bar; foo.length;` the latter returns `123`... – Elias Van Ootegem Aug 16 '12 at 14:17
  • Do you mean because he doesn't call the constructor? Then yes, that is right. I think that must be a typo. It would be a very strange way to use a function object. – gray state is coming Aug 16 '12 at 14:18
  • Not only that, the length property of an array is actually a function, with an override on its `valueOf` property, which returns _the highest key + 1_. Since an Array is just an augmentation of the object, all keys are converted to strings internally so JS has no quarrel with keys set using strings. This does mean that `foo = new Array(); foo['123']='bar'; foo.length;` returns `124` (123 +1). so iterating an array like that using a `for` loop will attempt to access 122 undefined keys before _finally_ getting to the one key that was set. – Elias Van Ootegem Aug 16 '12 at 14:29
  • Then, just try `foo.sort()`, bar will now be at index `0`, but the array length remains unchanged, so `foo.push('Index1?')` is pushed to index `124`. no _biggy_ you might say but that's just code waiting to break (the moment you `JSON.parse(foo)` for example). Better to use arrays and objects right, and keep them apart from day one, before you find yourself scratching what little you have left of your head from debugging. – Elias Van Ootegem Aug 16 '12 at 14:32

4 Answers4

1

First, a note that you're technically creating an object, so the preferred assignment for keyVals would be keyVals = {}.

var newVar = 31,
    keyNum,
    key,
    diff,
    keyVals = {},
    closest = {
        key: null,
        diff: null
    };

keyVals['23'] = 234;
keyVals['58'] = 'sunshine';
keyVals['172'] = 'lolipops';

for(key in keyVals) {
    if(keyVals.hasOwnProperty(key)) {
        keyNum = parseInt(key,10);
        diff = Math.abs(newVar - keyNum);

        if(!closest.key || diff < closest.diff) {
            closest.key = key;
            closest.diff = diff;
        }
    }
}

When the for loop is complete, closest.key will contain the index of the nearest match to newVar. For added protection, use hasOwnProperty to make sure you don't accidentally iterate over a property of one of keyVals prototypes (granted, that's pretty unlikely in this specific scenario).

jackwanders
  • 15,612
  • 3
  • 40
  • 40
0

First of all:
If you're using number-type values as your keys, they must remain numbers.
It should be 23, not '23'
Otherwise 23 will be treated as the string '23', which cannot be mathematically tested against a number.

var keyVals = Array;
keyVals[23] = 234; // keyVals[23], not keyVals['23']
keyVals[58] = 'sunshine';
keyVals[172] = 'lolipops'

To find the closest key,
Just loop through your keys and search for the closest one.

dist = Number.POSITIVE_INFINITY; // set the distance from key to value
closestkey = -1; // closest key variable
for(i in keyVals) {
    // i is the key, keyVals[i] is the value
    newdist = Math.abs(newVar - i); // distance from current key to value
    if (newdist < dist) {
        // we found a key closer to the value
        dist = newdist; // set new smallest distance
        closestkey = i; // set the value to the current key
    }
}
// Now closestkey is the key to your closest variable
Overcode
  • 4,074
  • 1
  • 21
  • 24
0

But be aware that there can be 2 values which are the closest to newVar.

For example:

keyVals[23] = 234;
keyVals[129] = 'aaa';
keyVals[172] = 'lolipops';

Both 23 and 129 are the closest to 76.

Then,

var keyVals = Array;
keyVals[23] = 234;
keyVals[129] = 'aaa';
keyVals[172] = 'lolipops';
newVar = 76;
var closest=new Object();
for(var i in keyVals){
    if(typeof closest.dif=='undefined'){
        closest.dif=Math.abs(newVar-i);
        closest.val=[i];
    }else{
        if(closest.dif==Math.abs(newVar-i)){
            closest.val.push(i);
        }else if(closest.dif>Math.abs(newVar-i)){
            closest.dif=Math.abs(newVar-i);
            closest.val=[i];
        }
    }
}
alert("The closest keys to "+newVar+" are ["+closest.val.join(',')+"], with a difference of "+closest.dif);

will alert "The closest keys to 76 are [23,129], with a difference of 53"

Or with your array,

keyVals[23] = 234;
keyVals[58] = 'sunshine';
keyVals[172] = 'lolipops';

will alert "The closest keys to 76 are [58], with a difference of 18"

Oriol
  • 274,082
  • 63
  • 437
  • 513
0

As others have pointed out, Objects (aka hashes) are the best data-structure to use when you want to map a key to a value; with that in mind you will see a performance benefit for pre-sorting the maps keys into numeric order.

// Helper method, you could also use underscore.js's `_.keys`
function getKeys(object) {
    var keys = [];
    for (var key in object) {
        if (object.hasOwnProperty(key)) {
            keys.push(key);
        }
    }
    return keys;
};

// Lookup table.
var keyVals = {
    23: "234",
    58: "sunshine",
    172: "lollypops"
};

// Extract the keys from the lookup table and sort them into numerical order.
var sortedKeys = getKeys(keyVals).sort(function (a, b) {
    return a - b;
});

// Returns the closest key in the `keyVals` lookup table for the supplied value.
function getClosestIndex(value) {
    var i;

    // Walk through the sorted keys array and stop when the next value is greater.
    for (i = 0; i < sortedKeys.length; i++) {
        if (sortedKeys[i] > value) {

            // Either return the previous key, or zero if this was the first.
            return (i === 0) ? sortedKeys[0] : sortedKeys[i - 1];
        }
    } 

    // We reached the end, so the value is greater than the highest key we have.
    return sortedKeys[i];
}

Obviously, if your keyVals map is small (under 1000 entries) then this sort of optimisation is pretty academic (but still quite fun) :)

JonnyReeves
  • 6,119
  • 2
  • 26
  • 28
  • Thank you for all of your help, gents. Found the answer to my question, and learned a lot here thanks to your well commented code. I appreciate it all. – skrite Aug 16 '12 at 15:53