3
\$\begingroup\$

I'm pretty new to algorithms, and it's my first attempt to implement sentinel linear search in JS. I'm wondering if it can be improved.

const array1 = ['lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'cras', 'eu', 'metus', 'volutpat', 'sagittis', 'nunc', 'in', 'porta', 'quam'];
let i = 0;
let x = 'elit';
let n = array1.length;
let last = array1[n - 1];
array1[n - 1] = x;

(function () {
    while (array1[i] !== x) {
        i++;
        if (array1[i] === x) {
            array1[n - 1] = last;
            if (array1[i] === x) {
                return console.log('FOUND ON INDEX', i);
            } else {
                return console.log('NOT FOUND');
            }
        }
    }
})();
\$\endgroup\$
5
  • 1
    \$\begingroup\$ For those of us that don't know, can you tell us what makes a 'sentinel linear search' special with regards to an ordinary 'linear search'? \$\endgroup\$
    – Mast
    Commented Aug 31, 2020 at 20:56
  • \$\begingroup\$ Is it related to this programming challenge? \$\endgroup\$
    – Mast
    Commented Aug 31, 2020 at 20:56
  • 1
    \$\begingroup\$ @Mast Linear search with a sentinel, en.wikipedia.org/wiki/Linear_search#With_a_sentinel \$\endgroup\$
    – mikebrsv
    Commented Aug 31, 2020 at 20:58
  • \$\begingroup\$ @Mast no, it's from the book Algorithms Unlocked by Thomas Cormen \$\endgroup\$
    – mikebrsv
    Commented Aug 31, 2020 at 21:04
  • \$\begingroup\$ @Mast basically, we put the value we're searching for into the last position of the array to avoid 2 checks on every iteration of the loop (in for loop it's evaluation of the condition expression and execution of the statement, both of which are called on each iteration). After the value is found, we restore the original value of the last position in the array and perform one additional check on the array \$\endgroup\$
    – mikebrsv
    Commented Aug 31, 2020 at 21:20

1 Answer 1

1
\$\begingroup\$

If the point of the sentinel is to, as the wiki article says:

The basic algorithm above makes two comparisons per iteration: one to check if Li equals T, and the other to check if i still points to a valid index of the list. By adding an extra record Ln to the list (a sentinel value) that equals the target, the second comparison can be eliminated until the end of the search, making the algorithm faster.

Then the approach should be to tack on the value to find onto the end of the array, instead of replacing the last value of the array with the value to find. I guess you can replace the last value, then once a match is found, replace it back, then check it, but that's unnecessarily convoluted.

Your code has a bug: if the first element of the array is the item to search for, while (array1[i] !== x) { will evaluate to false, and the IIFE will terminate immediately without logging anything.

That test is superfluous anyway, since inside the loop, the array item will either be found before the end, or at the end - either way, console.log will be called.

To add an element to the end of an array in JS, it's easier to use push:

array1.push(x);

You might consider using more precise variable names, to make the code more readable. (I'd avoid using single-letter variable names except maybe for i, which is pretty universally understood to be an index.)

const linearSearchSentinel = (array, itemToFind) => {
    const { length } = array;
    array.push(itemToFind);
    for (let i = 0;; i++) {
        if (array[i] === itemToFind) {
            array.pop(); // remove the added item from array
            if (i === length) {
                return console.log('NOT FOUND');
            }
            return console.log('FOUND ON INDEX', i);
        }
    }
};

linearSearchSentinel(['foo', 'bar', 'baz'], 'foo');
linearSearchSentinel(['foo', 'bar', 'baz'], 'baz');
linearSearchSentinel(['foo', 'bar', 'baz'], 'nope');

Or, if you wanted to use the while approach, then don't put any testing inside the loop - simply increment the index:

const linearSearchSentinel = (array, itemToFind) => {
    const { length } = array;
    array.push(itemToFind);
    let i = 0;
    while (array[i] !== itemToFind) i++;
    array.pop(); // remove the added item from array
    if (i === length) {
        return console.log('NOT FOUND');
    }
    console.log('FOUND ON INDEX', i);
};

linearSearchSentinel(['foo', 'bar', 'baz'], 'foo');
linearSearchSentinel(['foo', 'bar', 'baz'], 'baz');
linearSearchSentinel(['foo', 'bar', 'baz'], 'nope');

It's also a good idea to prefer const over let when you don't need to reassign the variable name.

I'd prefer one of the easy-to-understand versions above, but if you really want to avoid changing the length of the array for optimization purposes, I suppose you could replace the last item similar to you're originally doing instead.

const linearSearchSentinel = (array, itemToFind) => {
    const { length } = array;
    const lastItem = array[length - 1];
    array[length - 1] = itemToFind;
    let i = 0;
    while (array[i] !== itemToFind) i++;
    array[length - 1] = lastItem;
    console.log(
        (i !== length - 1 || lastItem === itemToFind)
            ? 'FOUND ON INDEX ' + i
            : 'NOT FOUND'
    );
};

linearSearchSentinel(['foo', 'bar', 'baz'], 'foo');
linearSearchSentinel(['foo', 'bar', 'baz'], 'baz');
linearSearchSentinel(['foo', 'bar', 'baz'], 'nope');

This is all for informational purposes only, of course - if you were actually facing a practical problem where you needed to find the index of an element in an array in JavaScript, it would make more sense to use the built-in indexOf method, which would be far faster and easier. Better not to reinvent the wheel unless an assignment like this one requires it.

\$\endgroup\$
3
  • \$\begingroup\$ By adding en extra record to the array using Array.push you get a nonneglegable increase in runtime for some inputs. Because a push may trigger internal data reallocation and copy which takes O(n) time. Push is only amortized O(1). Typicaly log2(n) / n of cases will exhibit the behaviour. Also Array.shift is always O(n) and you should avoid it entirely. Although the algorithm is O(n) either way, it is still a difference if you loop though n elements once and if you do it two or more times. Also pushing and shifting modifies the array which may not be desired to do with a caller provided data \$\endgroup\$
    – slepic
    Commented Sep 1, 2020 at 4:49
  • 1
    \$\begingroup\$ Dont take me wrong, but if the point of sentinel linear search is to reduce number of operations, the efforts are all wasted if you loop more then once. \$\endgroup\$
    – slepic
    Commented Sep 1, 2020 at 4:51
  • \$\begingroup\$ Oops, yeah, I meant to .pop(), not shift() \$\endgroup\$ Commented Sep 1, 2020 at 4:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.