psychopy.org | Reference | Downloads | Github

Random Function to Shuffle Lists

I am trying to replace the “random.shuffle” function so that I can upload my experiment to Pavlovia as I cannot import anything when I want to use it in Pavlovia.

So far I have found the Knuth Shuffle to do this in JS language:

function shuffle(array) {
  var currentIndex = array.length, temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}

When I change the code setting to ‘both’ and try and insert this, it disappears after running the experiment. I want to shuffle 3 lists so that my stimuli appear randomly. It worked perfectly with random.shuffle().

Apologies for the many posts lately. Hopefully someone will be able to help with this!

Does anybody have any suggestions for this?

Best regards,

Snoek

If you add shuffle = util.shuffle; to code_JS then you should be able to simply shuffle(array) unless I’m not understanding your issue.

Best wishes,

Wakefield

1 Like

Apologies, it is most likely me that does not understand. I cannot use the random.shuffle() function in Python as I cannot import the random package (because you cannot import anything for it to work on Pavlovia).

So I put a hashtag in front of this part of the code and now I am trying to replace it by something else?

This is my code, I have added the shuffle function to the right top (JS) side and the left side is the Python side.

When I run my code the picture1, picture2 and picture3 lists are empty

You should console log picture as well to check that they have something to shuffle.

The way I am approaching Pavlovia is to edit the Python as much as possible and only edit the JavaScript directly as a last resort. I am noting cases where I need to do this on my crib sheet.

If picture is an array when logged, perhaps you should try the simpler shuffle function of shuffle = util.shuffle;

Have you defined win=psychoJS.window;? I note that the next command after the console logging mentions win.

Thank you for your answer @wakecarter . I did look at your crib sheet, perhaps it is me that just does not understand exactly how to use it. I would gladly remove the shuffle function and replace it by the util.shuffle.

Pictures is a list:

pictures = [...]

My pictures are loaded in correctly. I have tried implementing util.shuffle(). Where do I insert this exactly for the code to work? And how do I use it on my pictures stimuli?

For example like this?

shuffle = util.shuffle

picture1 = shuffle(picture)
picture2 = shuffle(picture)
picture3 = shuffle(picture)

print(picture1)
print(picture2)
print(picture3)

You add the util.shuffle at the beginning of the experiment in an only JS code:

shuffle = util.shuffle

and then you use shuffle. Also, you don’t need to assign the shuffle call because the function changes the order of the array you pass into it. So:

shuffle(picture)

picture1 = picture[0]
picture2 = picture[1]
picture3 = picture[2]

print(picture1)
print(picture2)
print(picture3)
2 Likes

Thank you very much @niclai! This is incredibly helpful.

I changed it a little bit as I do not want one random number but I want the list to be randomized. I am using the same pictures for 3 different blocks and I want the order of the pictures to be random.

picture1 = picture
picture2 = picture
picture3 = picture

shuffle(picture1)
shuffle(picture2)
shuffle(picture3)

print("picture1")
print(picture1)
print("picture2")
print(picture2)
print("picture3")
print(picture3)

It works, however, now that I look closely at the lists I see that all three lists have the exact same order. So it is shuffling the list in exactly the same order.

Do you perhaps have any insight into how to get these three lists to be random and different? It change when I run the experiment again, meaning that between participants it does seem random.

Best regards and thank you,

Snoek

I think is because you are just assigning 3 new names for the list pictures, not really creating new unliked copies of the object. If you put this in the python side it should work (the [:] is going to be translated as .slice(0) by the AutoJS translator).

picture1 = picture[:]
picture2 = picture[:]
picture3 = picture[:]

shuffle(picture1)
shuffle(picture2)
shuffle(picture3)
2 Likes

Thank you @niclai for this life saving answer. This part of the experiment works perfectly now.

Thank you @wakecarter also for your great tips.

After getting rid of the ‘imports’ the experiment is still stuck on ‘initialising the experiment…’, so I will go investigate further where the next problem lies.

Kindest regards,

Snoek

Side question-- is there a similar strategy to be able to use random.seed() ? I am trying to have a reproducible shuffle.

A quick Google suggests that the simple answer is no but there’s at least one possible tool out there:

http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html

I’d be interested to know if this was usable in PsychoPy.

@wakecarter that looks very interesting, but I’m not as experienced with JS, and I’m not sure how one would incorporate this code into an experiment-- would we need to simply paste the code from http://davidbau.com/encode/seedrandom.min.js into a script/component, or is it more complex than that? I’m willing to be a guinea pig. :slight_smile:

I’ve just tested pasting the contents of https://github.com/davidbau/seedrandom/blob/released/seedrandom.js and then in a second test the minified code you linked to code_JS (I actually used a separate JS code block to keep things clear)
Then I added

Math.seedrandom("hello.");
randomnum1 = Math.random(); 
randomnum2 = Math.random(); 

If you try my template experiment https://pavlovia.org/Wake/brookes-template-2020 you’ll now see the following two “random” numbers on the second screen.

0.9282578795792454
0.3752569768646784

Please could you test shuffle?

@wakecarter I am able to run through your experiment and get those numbers; thanks!
Still working on making it work in my experiment. I had previously been setting the seed like this:

shuffSeed = np.random.randint(9999, dtype = 'int64')

… and then using it like this:

np.random.seed(shuffSeed)

So, in the JS script, I have the following code so far:

np.random.rand = Math.random;
np.random.seed = Math.seedrandom;
randint = function(min, max) {
    return Math.floor(Math.random() * (max - min) ) + min;
}

However, I am a bit stuck because I need the dtype = 'int64' argument to randint() make the numpy call work. This confuses JS, since it doesn’t expect such an argument.
I think this will no longer be necessary with the next version of python (see this stackoverflow thread), but I think I will need to upgrade numpy first?

Are you trying to get the same seed working online as offline?

What you’ve typed there doesn’t match how you should use David Bau’s seedrandom.

Add the following code to code_JS.

!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=s&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a–;)b=h[f=s&f+1],c=cd+h[s&(h[f]=h[g=s&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&“object”==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:“string”==e?a:a+"\0"}function l(a,b){for(var c,d=a+"",e=0;e<d.length;)b[s&e]=s&(c^=19b[s&e])+d.charCodeAt(e++);return n(b)}function m©{try{return o?n(o.randomBytes(d)):(a.crypto.getRandomValues(c=new Uint8Array(d)),n©)}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o,p=c.pow(d,e),q=c.pow(2,f),r=2*q,s=d-1,t=c[“seed”+i]=function(a,f,g){var h=[];f=1==f?{entropy:!0}:f||{};var o=l(k(f.entropy?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(f.pass||g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=p,c=0;q>a;)a=(a+c)d,b=d,c=s.g(1);for(;a>=r;)a/=2,b/=2,c>>>=1;return(a+c)/b},o,"global"in f?f.global:this==c)};if(l(ci,b),g&&g.exports){g.exports=t;try{o=require(“crypto”)}catch(u){}}else h&&h.amd&&h(function(){return t})}(this,[],Math,256,6,52,“object”==typeof module&&module,“function”==typeof define&&define,“random”);

You can then seed the random numbers by adding Math.seedrandom(“hello.”); to code_JS.

If you use the seed “hello.” then the first two numbers generated by random() will be 0.9282578795792454 and then 0.3752569768646784. seedString = Math.seedrandom(); will seed based on current time, dom state, and other accumulated local entropy. The seed generated is saved to seedString.

If you want to create random numbers with a custom seed without affecting Math.random, add var myrng = new Math.seedrandom(‘hello.’); to code_JS and then retrieve random numbers using myrng().

I don’t need to have the exact same seed online and offline, but I’d like the experiment to work both places, of course.
What I really need is to be able to set the seed to something random for each participant, and then use the same seed a few different times in the experiment

… maybe there’s a better way for me to do what I’m trying to do. But I’m still interested in knowing how to set and use the same seed during an experiment.

You could have a code_Py containing the Python only code.

If you start with seedString = Math.seedrandom(); then you should be able to reseed using Math.seedrandom(seedString);

@wakecarter I have come back to this problem now that I’m online and the function definition for seedrandom is throwing an error. I have changed randomization strategies so this no longer affects me (I’ve just taken the function out altogether), but I thought you and the readers of this thread should know.

I have a test of seedrandom in my template file https://run.pavlovia.org/Wake/brookes-template-2020/html/

I generate the same random number twice using:

seedString = Math.seedrandom();
randomnum1 = Math.random(); 
Math.seedrandom(seedString);
randomnum2 = Math.random();