Search code examples
javascriptarraysindexofqualtrics

indexOf / includes don't do an exact match and return false positives


I want to build an if statement in which the if criteria is based on an equality test of whether a variable equals any of several values. However, I do not want to hardcode the test values, but to pass an array of values that had been randomly subset earlier.

First, I get the set of randomized values by subsetting/sampling 5 values out of an array of 15 values. Basically, I'm using this excellent solution.

function getRandomSubarray(arr, size) {
    var shuffled = arr.slice(0), i = arr.length, temp, index;
    while (i--) {
        index = Math.floor((i + 1) * Math.random());
        temp = shuffled[index];
        shuffled[index] = shuffled[i];
        shuffled[i] = temp;
    }
    return shuffled.slice(0, size);
}

var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);

Then, I want to pass fiveRandomMembers to test whether a variable is equal to any of the values in fiveRandomMembers's array. Then do something. To this end, I want to use this solution.

var L = function()
{
    var obj = {};
    for(var i=0; i<arguments.length; i++)
        obj[arguments[i]] = null;

    return obj;
};

if(foo in L(fiveRandomMembers)) {
/// do something
};

Unfortunately, this doesn't work for me. I must admit that the implementation of this code is within a Qualtrics survey, so the problem might be nuanced to the Qualtrics platform, and that's the reason it isn't working for me. I'm newbie to JavaScript so I apologize if this is a trivial question. But I believe that my code is problematic even in plain JavaScript (that is, regardless of Qualtrics), and I want to figure out why.

UPDATE 2020-05-24

I've been digging into this more deeply, and I have some insights. This looks more like a qualtrics problem rather than plain JS issue. However, the underlying problem might still have to do with some JS mechanism, and that's why I bother to update it here -- maybe someone will know what's causing this behavior.

To recap -- I want to condition an action based on whether a given variable's content matches either of the values in an array. I've tried using both includes and indexOf, but either method fails. The problem boils down to the functions not doing an exact match. For example, if I have an array of 5 numbers such as 8, 9, 12, 13, 14, and I want to test whether 4 exists in the array, then an exact match should return FALSE. However, both indexOf and contains return TRUE because 14 has 4 in it. This is not an exact matching then. Furthermore, I've tried to investigate what is the position indexOf would return for such a false-positive match. Typically, it would return a position that is even larger than the total length of the array, making no sense whatsoever. Here's an example from my Qualtrics survey, demonstrating the problem:

qualtrics

The code giving this is comprised of two qualtrics questions:

(-) First piece

Qualtrics.SurveyEngine.addOnReady(function()
{
    /*Place your JavaScript here to run when the page is fully displayed*/

    function getRandomSubarray(arr, size) {
    var shuffled = arr.slice(0), i = arr.length, temp, index;
    while (i--) {
        index = Math.floor((i + 1) * Math.random());
        temp = shuffled[index];
        shuffled[index] = shuffled[i];
        shuffled[i] = temp;
    }
    return shuffled.slice(0, size);
}

var x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];
var fiveRandomMembers = getRandomSubarray(x, 5);

if (Array.isArray(fiveRandomMembers)) Qualtrics.SurveyEngine.setEmbeddedData('is_array', "TRUE");


Qualtrics.SurveyEngine.setEmbeddedData('length', fiveRandomMembers.length);


Qualtrics.SurveyEngine.setEmbeddedData('five_sampled_numbers', fiveRandomMembers);

});

(-) Second piece

Qualtrics.SurveyEngine.addOnReady(function()
{
    jQuery("#"+this.questionId).find('.QuestionText:first').css("padding-bottom", "0px");


var currentLoopNum = "${lm://CurrentLoopNumber}";
    // var currentLoopNum = parseInt(currentLoopNum, 10); // tried converting to numeric but it doesn't solve the problem
var fiveSampledNumbers = "${e://Field/five_sampled_numbers}";

if (fiveSampledNumbers.includes(currentLoopNum)) {
   Qualtrics.SurveyEngine.setEmbeddedData('does_loop_number_appear', "Yes");
} else {
    Qualtrics.SurveyEngine.setEmbeddedData('does_loop_number_appear', "No");

}
    Qualtrics.SurveyEngine.setEmbeddedData('index_of', fiveSampledNumbers.indexOf(currentLoopNum));

});

Here is a link to the Qualtrics survey, demonstrating the problem, in case it's helpful for troubleshooting: link


However, when testing the same code outside of Qualtrics, the problem doesn't replicate.

js_testing

Does someone have a clue or even a hypothesis what could be the problem with the matching? Even if you're not necessarily familiar with Qualtrics...


Solution

  • I've never worked with Qualtrics before, but to me it is clear that the line

    var fiveSampledNumbers = "${e://Field/five_sampled_numbers}";
    

    will assign a string value to fiveSampledNumbers, not an array value.

    Indeed, if you attempt to run the checks you are making on a string rather than an array, you get the unexpected results you saw above, because you are doing string operations rather than array operations:

    var fiveSampledNumbers = "6,4,10,11,15";
    console.log(fiveSampledNumbers.includes(5));  // logs true (string ends with the character "5")
    console.log(fiveSampledNumbers.indexOf(5));   // logs 11   (index of the character "5")
    

    To get around this, you will have to split the string by commas and parse each number within it:

    var fiveSampledNumbers = "6,4,10,11,15";
    fiveSampledNumbers = fiveSampledNumbers.split(",").map(function (n) { return parseInt(n, 10); });
    console.log(fiveSampledNumbers.includes(5));  // logs false
    console.log(fiveSampledNumbers.indexOf(5));   // logs -1