What's the most efficient way for picking two *different* colours out of four?

Started by bx83, Sun 20/06/2021 13:40:15

Previous topic - Next topic

bx83

I have blue, red, green and black.
If I pick eg. black for my first choice, my second colour must be any colour *but* black.
What's a nice algorithm that takes up five lines and doesn't involve a for loop? (which could, however unlikely, take an inordinate or infinite time to calculate)

Crimson Wizard

I think the most universal solution may be this:

1. Get Random element of array from 0 to N. Remember the index.
2. Move the chosen element to the end of array, swapping with whatever was there. EDIT: or just copy the last element there overwriting chosen color, no need to do full swap.
3. Get Random element of array from 0 to (N-1).

Basically, you are sorting array each time, removing chosen elements or moving them to the end of array (in case you must maintain all values), and next time pick element from reduced number of items.

Code: ags

int MyArray[N];
// fill array here
int index1 = Random(N - 1); // because Random is inclusive, and N is length
int color1 = MyArray[index1];
MyArray[index1] = MyArray[N - 1]; // copy last element to the chosen index, as we don't need it anymore
int color2 = MyArray[Random(N - 2)]; // choose among all but last one


I guess this may be redone as a function that supports arbitrary number of selections too.

Something like...
Code: ags

int Array[ARR_NUM];
int Selections[SEL_NUM];

for (int i = 0; i < SEL_NUM; i++)
{
    int index = Random(ARR_NUM - i - 1);
    Selections[i] = Array[index];
    Array[index] = Array[ARR_NUM - i - 1];
    // Array[ARR_NUM - i - 1] = Selections[i]; // if you need to keep all values in array intact
}

bx83

Cool, I came up with something similar:

Code: ags
  String numbertxt="0123";
  String hold;
  String first_col;
  String second_col;
  
  first_col=numbertxt.Substring(Random(numbertxt.Length), 1);
  numbertxt=numbertxt.Replace(first_col, "");
  
  second_col=numbertxt.Substring(Random(numbertxt.Length), 1);
  numbertxt=numbertxt.Replace(second_col, "");
  
  cDoor1.LockView(274); //door view
  cDoor1.Animate(first_col.AsInt, 1, eOnce, eNoBlock, eForwards);
  cDoor2.LockView(274); //door view
  cDoor2.Animate(second_col.AsInt, 1, eOnce, eNoBlock, eForwards);

Crimson Wizard

You need to do "Random(numbertxt.Length - 1)", because Random is inclusive, and elements go from 0 to numbertxt.Length - 1.

eri0o

Vincent made a similar question not so long ago: https://www.adventuregamestudio.co.uk/forums/index.php?topic=59207.0

I am curious why people don't seem to use Set for this, it has all the things for this exact problem.

Crimson Wizard

Quote from: eri0o on Sun 20/06/2021 16:17:31
I am curious why people don't seem to use Set for this, it has all the things for this exact problem.

Well, the question was "what's the most efficient way", and frankly Set would be an overkill for choosing 2 ints out of 4, while above problem may be solved by a simple array of ints.

With a Set you create a Set object, create and format String objects for a set, then create array of strings from this set, and if there are several selections with element removal in between then you will recreate array multiple times. Plus you have to convert strings back and forth...

But I guess many people don't care, so yes Set could be used too. Probably not a lot of users know about Set and what it does yet.

fernewelten

If the only thing you need really is two distinct integers in the range from 0 to 3, then the shortest would probably be:
Code: ags

int i1 = Random(3);
int i2 = Random(2);
if (i2 >= i1)
    i2++ ;


As far as I can determine, that would make each pair equally likely to be drawn (if the sequence is immaterial and the random number generator is good).

If you need to convert these integers to Strings, then the pain is in initializing the structure that holds the data. This needs to be done element by element. For this purpose, arrays are as good as any IMO, so one way to continue the code from above would be:
Code: ags

String col_array[4];
col_array[0] = "Red";
col_array[1] = "Green";
col_array[2] = "Blue";
col_array[3] = "Yellow";
String colour1 = col_array[i1];
String colour2 = col_array[i2]; 


That's completely untested code. :-[ Feel free to fix any and all bugs that you might find.  :P

Just my 2 cents

PS. eric0o's suggestion is sound to move colour1 and colour2 into a Set for further handling.

Crimson Wizard

Quote from: fernewelten on Sun 20/06/2021 17:59:25
If the only thing you need really is two distinct integers in the range from 0 to 3, then the shortest would probably be:
Code: ags

int i1 = Random(3);
int i2 = Random(2);
if (i2 >= i1)
    i2++ ;


Ah, yes, this looks like the simpliest one for the particular case.
But should not it be "i2 == i1" in the condition?

eri0o

I love the simplicity of Fernewelten's method, that is great. Can even just slap enums for the colors!

A small note, AGS Script is really slow, I use Set to find unique elements in an array of int and converting all ints to string, pushing in the Set and getting the resulting array and converting back to ints ended up being faster than all attempts I had in AGS Script. Maybe one day we could have Set for ints so they get more use.

Crimson Wizard

Quote from: eri0o on Sun 20/06/2021 19:56:46
A small note, AGS Script is really slow, I use Set to find unique elements in an array of int and converting all ints to string, pushing in the Set and getting the resulting array and converting back to ints ended up being faster than all attempts I had in AGS Script.

This is very bad. There were changes to script interpreter done in the past, for the reasons of backwards compatibility and 64-bit memory support, they slowed it down, especially the dynamic arrays. Some of these were probably even unnecessary, as they originate from a wrong idea of how the code should be developed further. These may be reverted or done differently.

fernewelten

Quote from: Crimson Wizard on Sun 20/06/2021 19:07:29
But should not it be "i2 == i1" in the condition?

I found that trick a long time ago in some program, copied and learned it, and have never given it much thought since. But come to think of it, "i2 >= i1" should be correct:

Assume that i1 turns out to be 1. So i2 must be randomly chosen from 0, 2, 3. But the second random number generator chooses from 0, 1, 2. So the random number generator's 1 must become 2 and the random number generator's 2 must become 3.

SMF spam blocked by CleanTalk