SepWithN + random item from template

PennController for IBEX Forums Support SepWithN + random item from template

Viewing 2 posts - 1 through 2 (of 2 total)
  • Author
    Posts
  • #10890
    bta
    Participant

    I am trying to do some fancy work with my Sequence in my experiment, and there are essentially two parts to what I’m trying to do, and I can’t seem to put them together to do what I actually want.

    First, I’m trying to take a certain number of trials (e.g., 8) and presented them in a random order, and then have those randomly presented trials be separated at a fixed interval with a “take a break” style trial. I know that this part of the work can be done by: (i) putting the 8 trials in a Template, (ii) randomizing the order with randomize(NAMEOFTRIAL), and (iii) using a function like sepWithN (as documented in several places on these forums, e.g., [this post](https://www.pcibex.net/forums/topic/how-to-add-page-breaks-in-between-randomized-items/#post-8514)).

    Second, I’m trying to have the “take a break” trial be one of a certain number of trials (e.g., 4), where each of the breaks is a random (and different) break trial. To achieve this, I put the 4 break items into a csv and created a Template of trials, and I tried using the randomProportion function (as defined in [this post](https://groups.google.com/g/ibexexperiments/c/ap4qV9TUJ3Q/m/OOU7pQBtAwAJ)) to show only one of the 4 items in the csv after 2 stimuli.

    Using these ideas together, I defined the sequence through Sequence( sepWithN(randomProportion("theSeparator", .25), randomize("theStimuli"), 2) ). **The problem is** that this shows the same random trial from the Template for theSeparator for all 4 separator trials (when I would hope to have each separator trial use a different trial for theSeparator).

    For example, if my stimuli are [abc, def, ghi, jkl, mno, pqrs, tuv, wxyz] and my separators are [separatorA, separatorB, separatorB, separatorD], I am getting outputs like ghi, mno, separatorB, abc, wxyz, separatorB, def, jkl, separatorB, tuv, pqrs, separatorB. Instead, what I would like is an output like ghi, mno, separatorA, abc, wxyz, separatorD, def, jkl, separatorB, tuv, pqrs, separatorC.

    Anyone have any thoughts on how to get the desired output? I’ve created a demo experiment at this URL: [https://farm.pcibex.net/r/gdHZAR/](https://farm.pcibex.net/r/gdHZAR/).

    #10916
    bta
    Participant

    I have figured out a solution, which I’m sharing below (I’ve put this code in a .js file that I added under “modules”).

    I use it in my Sequence() as in Sequence( sepChunkWithChunk("theSeparator", 1, "theStimuli", 2) ), which does what I described in my previous post (*except that it doesn’t add a separator after the final stimulus, which I did on purpose*).

    
    // this code below lets you put something like sepChunkWithChunk("separators", 1, "stimuli", 2) into the Sequence
    //      this works so that, if you have a Trial (in a Template) named "separators" and a Trial (in a Template) named "stimuli", …
    //      …it will present trials from the "stimuli" in chunks of 2, with each chunk separated by 1 trial from "separators"
    //		(NOTE: there will be a "separators" chunk between chunks of "stimuli" — meaning if there are 4 chunks of "stimuli", only 3 "separators" chunks will be alreadyUsed)
    function  SepChunkWithChunk(sep, nSep, main, nMain) {
    	assert(typeof(nSep)=="number" && nSep>0, "nSep should be a number greater than 0");
    	assert(typeof(nMain)=="number" && nMain>0, "nMain should be a number greater than 0");
    
        this.args = [sep, main];
    
        this.run = function(arrays) {
    		var alreadyUsedSep = [];
    		var alreadyUsedMain = [];
    		let sep = arrays[0];
    		let main = arrays[1];
    		var numChunksForMain = Math.ceil(main.length / nMain); // calculates the number of chunks of 'main' are necessary to show all items from 'main'
    		var totalNumUsedFromMain = 0; // counter for tallying number of items used from 'main'
    		
            var chunkedAndSeparatedArray = []; // this array will house the order of all trials, randomized into chunks of 'main' separated by chunks of 'sep'
    
    		for (let i = 1; i <= numChunksForMain; ++i) { // run this loop as many times as there are chunks of 'main'
    			counterMain = 0;
    			counterSep = 0;
    
    			// create a chunk with 'nMain' elements by looping 'nMain' times
    			//		STOP LOOPING if it's already pulled out all trials from 'main'
    			//		(this will happen if, e.g., you have 8 trials in 'main' and 'nMain' is set to something like 5 — two chunks are needed, but the second chunk should only have 3 elements)
    			while (counterMain < nMain && totalNumUsedFromMain < main.length) {
    				// generate a random number between 0 and (main.length-1):
    				r = Math.floor(Math.random()*main.length);
    				
    				// keep generating a new random number until the random number generated hasn't been used before
    				//		(this has the effect of blocking the repetition of items from 'main' that have already been added to 'chunkedAndSeparatedArray')
    				while (alreadyUsedMain.includes(r)) { r = Math.floor(Math.random()*main.length); }
    				
    				// now that a unique random number has been generated, add it to the array of random numbers that have been used
    				alreadyUsedMain.push(r);
    				
    				// add the rTH trial from 'main' to 'chunkedAndSeparatedArray'
    				chunkedAndSeparatedArray.push(main[r]);
    				
    				// increase counters 
    				++counterMain;
    				++totalNumUsedFromMain;
    			}
    
    			// create a chunk with 'nSep' elements by looping 'nSep' times
    			//		DON'T RUN THIS LOOP if the last chunk of 'main' has just been added to 'chunkedAndSeparatedArray'
    			if (i != (numChunksForMain)){
    			    while (counterSep < nSep) {
    				// generate a random number between 0 and (sep.length-1):
    				r = Math.floor(Math.random()*sep.length);
    				
    				// keep generating a new random number until the random number generated hasn't been used before
    				//		UNLESS all the trials from 'sep' have already been used
    				//		(this has the effect of allowing separator trials to be re-used just in case the number of separators needed is greater than the number of unique trials in 'sep')
    				while (alreadyUsedSep.includes(r) && sep.length > alreadyUsedSep.length ) { 
    					r = Math.floor(Math.random()*sep.length);
    				}
    
    				// now that a unique random number has been generated, add it to the array of random numbers that have been used
    				alreadyUsedSep.push(r);
    
    				// add the rTH trial from 'sep' to 'chunkedAndSeparatedArray'
    				chunkedAndSeparatedArray.push(sep[r]);
    
    				// increase counter
    				++counterSep;
    			    }
    			}
    		}
    
            // return the finalized Array, which will have all the trials of 'main' in a raondomized order
            // moreover, between every chunk of 'nMain' trials, there is a chunk of random 'nSep' trials from 'sep'
            return chunkedAndSeparatedArray;
        }
    }
    function sepChunkWithChunk(sep, nSep, main, nMain) { return new SepChunkWithChunk(sep, nSep, main, nMain); }
    
Viewing 2 posts - 1 through 2 (of 2 total)
  • You must be logged in to reply to this topic.