0

Let me begin with the statement that no, this question is not a duplicate of How can I check If a nested array contains a value?. I read that question first and determined that it does not fit my requirements. This problem involves an array X of arrays Y, where the search needs to happen in any Y without knowing which Y to search beforehand.


I currently have an algorithm as part of an "app" that does the following:

const controls = [
    ["f", "Foo the bar", function(unused){ ... }],
    ["b", "Bar the baz", function(ev){ ... }],
    ["?", "Show this help", function(unused){ ... }],
    ...
];
...
function ev_keyboard(ev){
    controls.forEach(function([key, unused, callback]){
        if(ev.key === key)
            callback(ev);
    });
}

This makes it possible for the user to add their own key-bindings by just modifying controls. Super easy to understand and extend for the user who wants a little more.

This is all well and good, but I'm slightly annoyed with the knowledge that it will continue skipping down the bindings even after finding the matching bind and executing its callback. Because, you know, forEach. So, I tried improving it.

I could just go old school and use a for loop:

for(let i = 0; i < controls.length; i++)
    if(controls[i][bind_key] === ev.key)
        return controls[i][bind_callback](ev);

Or perhaps more appropriately:

function find_bind(key){
    for(let i = 0; i < controls.length; i++)
        if(controls[i][bind_key] === key)
            return controls[i][bind_callback];

    return ()=>null;
}
...
function ev_keyboard(ev){
    find_bind(ev.key)(ev);
}

But I'm told that for loops are bad for some reason (if I had to guess, it's because of JS's esoteric lock-up behavior when in an infinite loop). So here's the alternate fix:

function ev_keyboard(ev){
    let match = keyboard_controls.indexOf(ev.key); // what am I doing...

    if(match !== -1)
        keyboard_controls[match][bind_callback](ev);
}

You probably caught the error before even getting to the self-aware comment. I did, too. The problem is that there's a layer of separation here, and changing the structure to something like...

{
    "f": ["Foo the Bar", function(unused){ ... }],
    ...
}

...seems a little sloppy to me.

Is there some kind of efficient, non-library, idiomatic, recursive equivalent to indexOf for this purpose? Should I go with the C-like for loop search-and-return? Is it even worth worrying about since there are only so many bindings a reasonable person would make/be willing to remember? Or should I just stick with the original approach in spite of the previous sentence?

Braden Best
  • 8,830
  • 3
  • 31
  • 43
  • Why should an object be sloppy? `let controls = { "f": { description: ..., handler: ... }, "b": { ... }, ... }; if (typeof controls[ev.key] !== "undefined") { controls[ev.key].handler(ev); }` – Andreas Jun 10 '17 at 07:20
  • For the same reason something like `[{key: "f", description: ..., handler: ...}, ...]` would be somewhat sloppy: because every change to the design is an overhead for the user. For the user, who may not be experienced with any kind of programming, `[a, b, c]` may be easier to read, write, understand and maintain than something like `{a: [b, c]}` or `[{key: a, description: b, handler: c}]`. KISS Principle, in short. – Braden Best Jun 10 '17 at 07:29
  • Also, just a note: `controls` is meant to point at one object for its entire lifetime. It would be an error to point it to something else (which `let` allows). To reflect (and enforce) this intent, `controls` is declared as a `const`. – Braden Best Jun 10 '17 at 07:48
  • That's just an example... O.o – Andreas Jun 10 '17 at 07:50
  • I meant no offense. – Braden Best Jun 10 '17 at 08:53

1 Answers1

1

Use Array's find method. See MDN Array.prototype.find

function ev_keyboard(ev) {
    const control = controls.find(([key, unused, callback]) => ev.key === key)
    // control could be undefined
    if (control && typeof control.callback === 'function') {
        control.callback()
    }
}
  • Welcome to Stack Overflow. Your answer answers my question. Just one note: the lambda can be written as `(...) => return_value` instead of `(...) => {return return_value}`. However, I see that you are a project manager, so I figure there is a stylistic reason for that syntax and won't pester you about it. Have an upvote and an accept. – Braden Best Jun 10 '17 at 07:36
  • 1
    @BradenBest thanks for your kind reply. I'm aware of the shorthand syntax (working with node.js besides my role as project manager) but wasn't sure about the line length as the line was wrapped in the editor. However, I can see that it perfectly fits in one line, so I've updated the answer according to your feedback. –  Jun 10 '17 at 08:00