2

I am trying to describe a Pokemon move "database" with javascript to practice data description.

I have a bunch of moves, identified by an id(number)., name(string), and type(string).

I want to create a javascript array of moves such as this(use array index as id for O(1) lookup by id):

const moves = [
    {name: 'Astonish', type: 'Ghost'},
    {name: 'Constrict', type: 'Normal'},
    {name: 'Acid', type: 'Poison'},
    {name: 'Ingrain', type: 'Grass'}
];

However, I want to ensure that the index of these entries is strictly positioned(to avoid pasting an entry into the start of the array, invalidating the indexes, as well as making it more explicit what the entries are so you don't need to do a linear search to find the item each time you look at the list.

What I want to do is this, but it is not supported by JavaScript syntax:

const moves = [
    0: {name: 'Astonish', type: 'Ghost'},
    1: {name: 'Constrict', type: 'Normal'},
    2: {name: 'Acid', type: 'Poison'},
    3: {name: 'Ingrain', type: 'Grass'}
];

Doing this by explicitly inserting each item seems expensive and visually annoying:

const moves = [];

/* anonymous scope: setup moves */
{
    moves[0] = {name: 'Astonish', type: 'Ghost'};
    moves[1] = {name: 'Constrict', type: 'Normal'},
    moves[2] = {name: 'Acid', type: 'Poison'},
    moves[3] = {name: 'Ingrain', type: 'Grass'}
}

I could use an object, but objects are more expensive than arrays for this kind of behavior. I could also create an object then map it into an array like this:

const moves = (() => {
    let res = [];
    let tmp = {
        0: {name: 'Astonish', type: 'Ghost'},
        1: {name: 'Constrict', type: 'Normal'},
        2: {name: 'Acid', type: 'Poison'},
        3: {name: 'Ingrain', type: 'Grass'}
    };
    Object.keys(tmp).map ((key) => {
        res[key] = tmp[key];            
    });

    return res;
})();

but it seems a tad silly.

The reason why I want to have array index as id is because it would both be fast to access, and make it easy to make another object to map back the move names to their id(for O(1) move by name lookup).

Is there a way to describe this kind of array in a more coherent way, or are these the only options I have without inventing my own javascript convention, preprocessor?

Thanks ahead of time.

Related:

Objects vs arrays in Javascript for key/value pairs

Community
  • 1
  • 1
Dmytro
  • 5,068
  • 4
  • 39
  • 50
  • 2
    You have a bunch of wrong assumptions and syntax errors in your question. Long story short, just use the simple array you started with - it's perfect. – Amit Jul 14 '16 at 22:41
  • Can you please say what syntax I made? I tested the code and it seems perfectly fine.. – Dmytro Jul 14 '16 at 22:44
  • You can't do `[0: 'xx', 1: 'yy']`. – Amit Jul 14 '16 at 22:46
  • 1
    @Dmitry It seems people are having trouble understanding your question, but I think it's well asked. Unfortunately, I don't have any suggestions for you beyond the options you've already described. – user94559 Jul 14 '16 at 22:47
  • @Dmitry One drawback to the options you've already considered is that they're still susceptible to copy/paste issues... if you accidentally add something with a duplicate index, these solutions will just cause you to lose one of the duplicates. – user94559 Jul 14 '16 at 22:49
  • @Amit that was an example of "what I wish i could do", it's not an error, but I guess I can edit the question to make it clearer. – Dmytro Jul 14 '16 at 22:49
  • Ahhh... so that wasn't clear. Now it's still unclear what's the issue you're trying to tackle. What is "not good enough" with a simple array? – Amit Jul 14 '16 at 22:50
  • @Amit it makes the index not obvious from looking at the code, so if I want to find id of a move, I can't do so without counting offset or using a name to id map to determine it for me. If I place the index into the array entry, then the search for it will be linear rather than O(1), and having both is redundant data that can desynchronize/become invalidated when I decide to change id of a move and forget to change the inner id. Comments do what I want but it's a bit weird to comment every line. – Dmytro Jul 14 '16 at 22:51
  • So you basically want a "visual cue" for the entry index in your array setup? – Amit Jul 14 '16 at 22:52
  • @Amit yeah that's exactly it, comment would suffice but commenting each line of the array is weird. – Dmytro Jul 14 '16 at 22:53
  • I was about to suggest a comment... I agree with you that it's weird, but your "requirement" isn't typical either... – Amit Jul 14 '16 at 22:54
  • @Dmitry while weird, I'd have to agree that comments are probably the best option here. OR a quick hack if you're in a position to do so, it would be pretty easy to calculate those indexes by subtracting the line number of your const statement from the line number of the object whose index you want to know (assuming they're all one line and your editor has line numbers) – Zach Buttram Jul 14 '16 at 22:58
  • I guess it's just something javascript can't express the way I want right now. It's easy to write a preprocessor for my own array expressions and convert them into valid javascript, ensuring the array is generated with proper indexes in mind. – Dmytro Jul 14 '16 at 22:59
  • Seems to me if you want O(1) lookup and to use the ID as the look up value you should be using a [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) instead of an Array. – ashwell Jul 14 '16 at 23:00
  • @ashwell it's silly to use a map for numerically keyed items because it's more expensive to index a map than an array. it's misuse of maps. converting from map to object solves this, and i mentioned this, but it costs additional setup work. – Dmytro Jul 14 '16 at 23:01
  • I'd stay away from such an adventure. Do you really want to introduce a preprocessor to your flow? Will you be able to thoroughly test it? Will you be able to share your code afterwards? – Amit Jul 14 '16 at 23:01
  • @Amit It's fairly easy to test and make, it's just a consideration, I merely mention it because it's something that would be an interesting feature in javascript but I digress. It's good to be able to express a preprocessor for something that really bothers you enough, but it's more of a reason to practice than to try to make a revolution. – Dmytro Jul 14 '16 at 23:03
  • Regarding the O(1) concern, why do you even care? Do you plan on having 10^100 entries, or is 15 a more likely figure? Avoid premature optimizations. – Amit Jul 14 '16 at 23:03
  • @Amit because experimental problems are interesting to reason about. You don't have such flexibility when under pressure of real problems and it's good to develop a subconscious understanding of such things while I can, appreciating such things is an enjoyable experience. – Dmytro Jul 14 '16 at 23:04
  • I agree to that, but don't confuse experiments and interest with value and maintainability – Amit Jul 14 '16 at 23:05
  • @Amit the resulting javascript is as maintainable as one i would write by hand, the difference is that while I am writing the code, I have a tool to increase my expressiveness. Stuff like this has no impact on maintainability if it is written correctly(no messy output from generator means it would look the same as I would, in this case basically strip the '[digit]:' from each array entry lol). It would hurt maintainability if people were FORCED to use this generator to do it, but that's not the intent, they can modify the array directly. – Dmytro Jul 14 '16 at 23:07
  • That's the point... once anybody wants to touch that code, they must adhere to your proprietary preprocessing step. That means you need to share that preprocessor and enforce a "build step". That's not a decision I would take lightly... – Amit Jul 14 '16 at 23:12

2 Answers2

1

The array in JavaScript preserves the order. Your first solution is the simplest, the most elegant and should work perfectly.

Note also that objects do not guarantee the order of its name/value pairs.

You could maybe do something like :

let tmp = [
    {"0": {name: 'Astonish', type: 'Ghost'}},
    {"1": {name: 'Constrict', type: 'Normal'}},
    {"2": {name: 'Acid', type: 'Poison'}},
    {"3": {name: 'Ingrain', type: 'Grass'}}
];

But I don't think it would be worth it

C.Champagne
  • 5,381
  • 2
  • 23
  • 35
  • I was just curious if there was a way other than commenting each entry to have the id of each move explicitly specified so that if I forget it I don't have to do a map search to find the id by the name(which is faster than manually counting the offset from start). – Dmytro Jul 14 '16 at 22:48
  • @Amit well the example in the question is not JSON but from the title of the question you can expect this format will used later. – C.Champagne Jul 14 '16 at 22:55
  • It isn't JSON but it's a related issue to ensure JSON file containing similar data is properly indexed, which has similar problem of making it not obvious what index each entry is, and prone to mis-indexing. – Dmytro Jul 14 '16 at 22:56
  • @ I mentioned why that is not good in the discussion of original post. If I decide to change index of one item, and forget to change an inner index, there is a problem, this means one more invariant to maintain. – Dmytro Jul 14 '16 at 23:10
1

There are more drawbacks:

  • If your move IDs are implicitly given by their array index (and you want to keep your array an ordinary linear storage array) you are not allowed to have 'holes' in the ID range without wasting space.
  • If you remove a move, you have to slice the array which is (assuming a linear storage array) no longer in O(1) and shifts all succeeding IDs.
  • If you allow gaps in your ID range e.g. by inserting moves[3] = ...; moves[5] = ...; or by deleting an array entry, your array might be 'downgraded', i.e. no longer a linear storage array but a map.

To avoid all these issues and still have O(1) comp. complexity (unless your JS engine is really quirky) for insertion, access and removal, use a map. Either the ES6 Map or a plain old Object.

le_m
  • 19,302
  • 9
  • 64
  • 74
  • You're right, the holes are a serious problem and using Maps or converting my Object to array for heavily indexed arrays is probably better than using an array. I also have no evidence that accessing objects is much slower than arrays so perhaps it is justified to just keep it as an object to make insertion easier and more safe(in case holes do occur, although this kind of data is fairly permanent and deletions are not intended without creating a new table). If there were anticipated deletions, I would go with an object without questions though. – Dmytro Jul 14 '16 at 23:13
  • @Dmitry If you really care about performance only and you guarantee no holes will occur, an array is the fastest, since the constant overhead to access an element is still smaller than for a map. However, the computational complexity of inserting or accessing elements in an array and a map is constant O(1) - depending on how the map is implemented and a few conditions which usually are of no concern. – le_m Jul 14 '16 at 23:17
  • @ie_m that is why I was asking, since there are no holes, and any reordering/redesign would be a new table of moves. The object overhead seems excessive, but javascript arrays are not c arrays, they are more of sparse arrays, so they should be capable of dealing with holes easily, and they do as long as I set indexes explicitly, but as I mentioned that is visually annoying and likely much slower than explicitly setting the arrays/splicing array ranges together. It's an unusual situation that is almost enough to warrant objects, but javascript arrays should be able to handle it. – Dmytro Jul 14 '16 at 23:28
  • 1
    Well, you were asking for an O(1) solution - both maps and arrays are in O(1) - just the constant is different. JS arrays are usually (modern JS engine) backed by a linear piece of easily adressed storage - but only as long as you never have a hole in your indices and your indices are valid array indices (pos. integers not exceeding the max array index) etc (depending on your optimising JS compiler). Otherwise, they will be backed by a map - in which case you better use a map explicitly. Unless you perform math. optimisations / visual computing etc. the constant perf. difference is negligible. – le_m Jul 14 '16 at 23:35
  • @Ie_m I was curious about that; whether javascript arrays are always sparse arrays, in which case objects are basically the same as arrays, or are implicitly treated as objects as soon as they get holes in them, in which case their performance should drop as soon as there is a hole. That said, I wouldn't expect it to perform a conversion in the middle of a script since I would imagine there are cases when such a conversion would be detrimental to the user experience if the array is large enough and starts converting as soon as something is deleted. – Dmytro Jul 14 '16 at 23:40
  • 1
    Oh, JS engines indeed downgrade your array to a map at runtime. Read e.g. http://jayconrod.com/posts/52/a-tour-of-v8-object-representation (search for 'array', 'downgrade', 'dictionary mode'). Interestingly, it says that Chrome's V8 optimising compiler *can* handle holes as long as they are not too big. Would be curious if other engines do the same. – le_m Jul 14 '16 at 23:50