1

I have a object with data like this:

var data = {
    '4': [1, 2, 3],
    '5': [1, 2, 3],
    '6': [1, 2, 3],
    '7': [1, 2, 3],
    '8': [1, 2, 3],
    '9': [1, 2, 3],
    '10': [1, 2, 3],
    '11': [1, 2, 3],
    '12': [1, 2, 3],
    '15': [1, 9, 3],
    '18': [1, 2, 3],
    '21': [1, 8, 3],
    '24': [1, 2, 3],
    '30': [1, 2, 3],
    '36': [1, 2, 3],
    '42': [1, 20, 3]
}

Now I want to access the data like

var result = data[i][1];

This would give me a result of 9 if i = 15.

But I need to get always the next lower index, if the given index isn't existing. So, if i = 16 the result should also be 9. If i = 23 the result is 8 and for i = 999, the result is 20. How can I do that?

Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
user3848987
  • 1,627
  • 1
  • 14
  • 31
  • Is there any reason you use an object with numerical indexes instead of an array? – Matt Burland Oct 12 '15 at 19:19
  • How is the object populated? Is it ever changed after first assignment? – JNF Oct 12 '15 at 19:22
  • 1
    First of all, `i` is not an index, it's a property name, or a key, if you will. Don't use object like arrays. It might be more prudent to ask why you want to achieve the fall-down logic, and how sparse you dataset is going to be, before deciding on the best representation of that data. – Jens Neubauer Oct 12 '15 at 19:29
  • @JNF No, the object won't be changed at all. – user3848987 Oct 12 '15 at 19:29
  • @MattBurland If I would use an array with these keys, there would be many null-elements. So I decided to use an object, as a matter of memory :-) – user3848987 Oct 12 '15 at 19:31
  • @user3848987: See [here](http://stackoverflow.com/questions/4524067/if-i-set-only-a-high-index-in-an-array-does-it-waste-memory), plus with only a maximum of `42` that's a micro optimization. – Matt Burland Oct 12 '15 at 19:34
  • @MattBurland Just getting from here: http://stackoverflow.com/questions/33071051/saving-data-as-object-or-array – user3848987 Oct 12 '15 at 19:37
  • @user3848987: What that doesn't tell you is if the *order of iteration* is important, then iterating over an object is dangerous because the order of keys is NOT guaranteed. – Matt Burland Oct 12 '15 at 19:39
  • @MattBurland Oh, understand. So I should use an array with a key. The key value should be what to object name is right now... – user3848987 Oct 12 '15 at 19:41
  • 1
    Sort Object.keys(data) (use a compare function that sorts those indexes for you), and then use a simple search algorithm where you return the key equal or smaller to your search value, starting with the last index of the sorted array. – samsonthehero Oct 12 '15 at 19:43
  • @user3848987 - for your given example data structure, what is the next lower index for each of e.g. "2", "0", "-1", "x" supposed to be? – Peter Seliger Oct 13 '15 at 11:14

5 Answers5

4

You're going to have to loop downwards searching for that property in your array. Assuming input is the one you're trying to find,

for (var i = input; i > 0; i--) { // Looping incrementing down
    if (data.hasOwnProperty(i)) {
        result = data[i][1];
        break;
    }
}

The hasOwnProperty method checks if your data array has that index available, and breaks out of the loop after setting result if it does.

David Li
  • 1,250
  • 8
  • 18
0

You need to look for the index request, and if you don't find it, subtract one from the index and try again. Something like:

function getItem(i,j) {
    if (i < 0) {
        // assuming no negative keys exists
        return null;   // or throw an error if you prefer
    }
    if (data[i]) {
        return data[i][j];
    }
    else {
         return getItem(i-1,j);
    }
}

Usage:

getItem(16,1);    // should give you 9
Matt Burland
  • 44,552
  • 18
  • 99
  • 171
0

To round it to the closest number ether way, try using this:

var data = {
    '4': [ 1, 2, 3 ],
    '5': [ 1, 2, 3 ],
    '6': [ 1, 2, 3 ],
    '7': [ 1, 2, 3 ],
    '8': [ 1, 2, 3 ],
    '9': [ 1, 2, 3 ],
    '10': [ 1, 2, 3 ],
    '11': [ 1, 2, 3 ],
    '12': [ 1, 2, 3 ],
    '15': [ 1, 9, 3 ],
    '18': [ 1, 2, 3 ],
    '21': [ 1, 8, 3 ],
    '24': [ 1, 2, 3 ],
    '30': [ 1, 2, 3 ],
    '36': [ 1, 2, 3 ],
    '42': [ 1, 20, 3 ]
}

var keys = $.map( data, function ( element, index ) {
    return index
} );

function closest( number ) {
    var closest = null;
    $.each( keys, function () {
        if ( closest == null || Math.abs( this - number ) < Math.abs( closest - number ) ) {
            closest = this;
        }
    } );
    return closest;
}

console.log( data[closest( 16 )][1] );

Thanks to: https://stackoverflow.com/a/3561328/5414240 for the closest function.

Hope this helps.

Community
  • 1
  • 1
Damion
  • 49
  • 4
0

For a valid key I would suggest

  1. try if the key exists, or
  2. get all keys, map them to Number, perform a numerical sort and at least reduce the data to a key, which is smaller than the next bigger value.

var data = { '4': [1, 2, 3], '5': [1, 2, 3], '6': [1, 2, 3], '7': [1, 2, 3], '8': [1, 2, 3], '9': [1, 2, 3], '10': [1, 2, 3], '11': [1, 2, 3], '12': [1, 2, 3], '15': [1, 9, 3], '18': [1, 2, 3], '21': [1, 8, 3], '24': [1, 2, 3], '30': [1, 2, 3], '36': [1, 2, 3], '42': [1, 20, 3] };

function getKey(key) {
    return key in data ? key : Object.keys(data).map(Number).sort(function (a, b) { return a - b; }).reduce(function (r, a) {
        return a <= key ? a : r;
    });
}

document.write('3 ' + getKey(3) + '<br>');
document.write('15 ' + getKey(15) + '<br>');
document.write('16 ' + getKey(16) + '<br>');
document.write('23 ' + getKey(23) + '<br>');
document.write('999 ' + getKey(999) + '<br>');

Edit: For even better performance, avoids inspection of all items in the keys array. The solution works for sparse arrays as well, like

var data = [];
data[4] = [1, 2, 3];
data[5] = [1, 2, 3];
data[6] = [1, 2, 3];

var data = { '4': [1, 2, 3], '5': [1, 2, 3], '6': [1, 2, 3], '7': [1, 2, 3], '8': [1, 2, 3], '9': [1, 2, 3], '10': [1, 2, 3], '11': [1, 2, 3], '12': [1, 2, 3], '15': [1, 9, 3], '18': [1, 2, 3], '21': [1, 8, 3], '24': [1, 2, 3], '30': [1, 2, 3], '36': [1, 2, 3], '42': [1, 20, 3] },
    keys = Object.keys(data).map(Number).sort(function (a, b) { return a - b; }),
    i;

function getKey(key) {
    var lower = 0,
        upper = keys.length - 1,
        index;

    if (key in data) {
        return key;
    }
    if (key < keys[0]) {
        return; // this is not specified in question
    }
    if (key > keys[upper]) {
        return keys[upper];
    }
    while (lower !== upper) {
        index = lower + upper >> 1;
        if (key > keys[index]) {
            lower = index + 1;
            continue;
        }
        upper = index;
    }
    return keys[lower - 1];
}

for (i = -5; i < 50; i++) {
    document.write('value: '+ i + ', key: ' + getKey(i) + '<br>');
}
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • your first approach already should cover the sparse array case. Object.keys as well as map and filter are implemented in a way that will skip omitted index based keys for arrays. – Peter Seliger Oct 13 '15 at 13:28
0

If the data-structure is similar to a sparse array and if the indices of this data-structure tend to be bigger integer representatives, then a "try-and-error count-down approach" with stepwise decreasing a given index by 1 might not perform anymore that well.

The following example tries to take this into account ...

var getNextLowerOrSameIndex = function (obj, idx) {
  var
    indexCount,
    listOfIndices
  ;
  idx = parseInt(idx, 10);

  if (!(idx in obj)) {
    listOfIndices = Object.keys(obj);

    idx = (listOfIndices.every(function (index, count/*, listOfIndices*/) {

      indexCount = count;
      index = parseInt(index, 10);

      listOfIndices[indexCount] = index;

      return (idx > index);

    }) && Math.max.apply(null, listOfIndices)) || listOfIndices[indexCount - 1];
  }
//return idx;
  return (Number.isFinite(idx) && idx) || (void 0);
};

...

var data = {
  '4': [1, 2, 3],
  '5': [1, 2, 3],
  '6': [1, 2, 3],
  '7': [1, 2, 3],
  '8': [1, 2, 3],
  '9': [1, 2, 3],
  '10': [1, 2, 3],
  '11': [1, 2, 3],
  '12': [1, 2, 3],
  '15': [1, 9, 3],
  '18': [1, 2, 3],
  '21': [1, 8, 3],
  '24': [1, 2, 3],
  '30': [1, 2, 3],
  '36': [1, 2, 3],
  '42': [1, 20, 3]
};

console.log(data[getNextLowerOrSameIndex(data, 4)][1]);     // 2
console.log(data[getNextLowerOrSameIndex(data, "4")][1]);   // 2
console.log(data[getNextLowerOrSameIndex(data, "5")][1]);   // 2

console.log(data[getNextLowerOrSameIndex(data, 15)][1]);    // 9
console.log(data[getNextLowerOrSameIndex(data, 16)][1]);    // 9
console.log(data[getNextLowerOrSameIndex(data, "15")][1]);  // 9
console.log(data[getNextLowerOrSameIndex(data, "17")][1]);  // 9

console.log(data[getNextLowerOrSameIndex(data, "23")][1]);  // 8
console.log(data[getNextLowerOrSameIndex(data, 999)][1]);   // 20
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37