Snabbtips Hur slumpmässigt blanda en array i AS3

Ibland har du en uppsättning saker - kan vara strängar, kan vara siffror, kan vara Objekt, oavsett - vilken ordning du vill randomize. Det här är speciellt användbart för frågesporter och chansspel, men är till nytta för alla andra applikationer. Den enklaste metoden jag har hittat för att göra detta är att hålla alla föremål i en Array och sedan blanda den som ett kort kort. Men hur kan vi göra det? ?

Som ett enkelt exempel använder vi bokstäverna i alfabetet:

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "];

Det finns olika metoder vi kan ta för att faktiskt sortera denna array.


Den naiva metoden

Vi kunde skapa en andra array och kopiera varje element av det första till ett slumpmässigt läge i det andra:

Koden för det kan se ut så här (se denna Snabba Tips om att få ett slumpmässigt heltal inom ett visst område för mer information):

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "]; var shuffledLetters: Array = new Array (letters.length); var randomPos: int = 0; för (var i: int = 0; i < letters.length; i++)  randomPos = int(Math.random() * letters.length); shuffledLetters[randomPos] = letters[i]; 

Men det finns ett stort problem. Vad händer om slumpmässigt läge valde för C är också 6?

Här, en blir överskriven och kommer därför inte att vara i den blandade gruppen. Det är inte vad vi vill, så vi måste kontrollera att slitsen är tom innan du kopierar brevet och väljer en annan plats om den inte är:

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "]; var shuffledLetters: Array = new Array (letters.length); var randomPos: int = 0; för (var i: int = 0; i < letters.length; i++)  randomPos = int(Math.random() * letters.length); while (shuffledLetters[randomPos] != null) //repeat as long as the slot is not empty  randomPos = int(Math.random() * letters.length); //pick a different slot  shuffledLetters[randomPos] = letters[i]; 

Det fungerar. Problemet är att det är ineffektivt. Se brevet en är garanterad att passa in i slitsen plockad, eftersom det är det första bokstävet, så alla slitsarna blir tomma. Med B, Det finns 25 i 26 chans att den första slitsen som valts kommer att vara tom. När vi är halvvägs genom alfabetet faller denna chans till 50/50. När vi når V, Det finns 50/50 chans att vi inte hittar en tom slits till fjärde försök.

Det betyder att vi mycket sannolikt kommer att ringa Math.random () wayyyyy mer än 26 gånger. Och Math.random () är en relativt långsam funktion. Vad är ett annat tillvägagångssätt vi kan ta?


Middle-Man Approach

Vad händer om vi lagrade en lista med alla tomma luckor i a tredje array som skulle krympa när vi gick igenom dem?

? och så vidare, upprepa tills arrayen av tomma slitsar innehåller inga element. Koden för det skulle se ut så här:

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "]; var shuffledLetters: Array = new Array (letters.length); var emptySlots: Array = new Array (); för (var n: int = 0; n < letters.length; n++)  emptySlots.push(n);  var randomPos:Number = 0; var actualSlot:Number = 0; for (var i:int = 0; i < letters.length; i++)  randomPos = int(Math.random() * emptySlots.length); //note emptySlots.length not letters.length actualSlot = emptySlots[randomPos]; shuffledLetters[actualSlot] = letters[i]; emptySlots.splice(randomPos, 1); 

Här använder vi metoden Array.splice () för att ta bort ett enda element från listan med tomma slitsar. Detta tar bort elementet, snarare än att bara ställa in värdet till null; så, efter att ha splitsat den första luckan, emptySlots.length kommer vara 25 hellre än 26.

Detta splitsa() funktionen är stor; vi kan använda den för ett tredje tillvägagångssätt för att shuffling som skär ut denna mellersta manställning.


Splicing Approach

I stället för att ta bort element från arrayen av tomma slitsar när vi är färdiga med dem kan vi ta bort dem från den ursprungliga, oskärmade matrisen.

Det här låter inte mycket användbart först - men vad händer om vi plockade element från original- array slumpmässigt, istället för att välja deras destinationer slumpmässigt?

? och så vidare tills den första raden innehåller inga element.

Till skillnad från de andra två tillvägagångssätten hamnar vi utan den ursprungliga matrisen. Oavsett om detta är ett problem eller inte beror på detta projekt.

Koden kan se ut så här:

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "]; var shuffledLetters: Array = new Array (letters.length); var randomPos: Number = 0; för (var i: int = 0; i < shuffledLetters.length; i++) //use shuffledLetters.length because splice() will change letters.length  randomPos = int(Math.random() * letters.length); shuffledLetters[i] = letters[randomPos]; //note this the other way around to the naive approach letters.splice(randomPos, 1); 

Faktum är att splitsa() returnerar en Array av alla de element du splitsar kan vi förenkla koden lite:<

 var bokstäver: Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K" "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X "," Y "," Z "]; var shuffledLetters: Array = new Array (letters.length); var randomPos: Number = 0; för (var i: int = 0; i < shuffledLetters.length; i++)  randomPos = int(Math.random() * letters.length); shuffledLetters[i] = letters.splice(randomPos, 1)[0]; //since splice() returns an Array, we have to specify that we want the first (only) element 

Det är snyggare. Jag har ett tillvägagångssätt att dela med mig den här är väldigt annorlunda än de andra vi har använt hittills.


Sorteringsmetoden

Arrays har en metod, sort (), som som standard omarrangerar alla element i arrayen i stigande alfanumerisk ordning, så här:

 var mixedLetters: Array = ["G", "B", "P", "M", "F"]; mixedLetters.sort (); // mixedLetters är nu ["B", "F", "G", "M", "P"]

Du kan överföra alternativ till den här metoden, som Array.DESCENDING, som vänder sortens ordning:

 var mixedLetters: Array = ["G", "B", "P", "M", "F"]; mixedLetters.sort (Array.DESCENDING); // mixedLetters är nu ["P", "M", "G", "F", "B"]

(Det finns andra alternativ, och du kan skicka så många av dem som du vill.)

Du kan också skicka en referens till a fungera, som berättar för Flash hur man bestämmer vilken ordning som helst för två föremål. Till exempel kan den här funktionen användas för numerisk sortering:

 funktion numericalSort (a: Nummer, b: Nummer): Nummer om (a < b) return -1; if (a == b) return 0; if (a > b) returnera 1; 

Flash tittar på varje par av angränsande objekt i arrayen och omarrangerar dem enligt denna funktion: det byter dem om värdet 1 returneras och lämnar dem ensamma annars. (Om du inte passerar Array.DESCENDING liksom funktionen, i vilket fall det byter dem om värdet -1 returneras och lämnar dem ensamma annars.) Flash upprepar detta över hela matrisen om och om igen tills alla par återvänder 0 eller -1 (0 eller 1 om du använder Array.DESCENDING).

Vi kan röra med det här. I stället för att ge det en verklig anledning till varför två delar ska bytas, kan vi bara säga att de ska slumpmässigt bytas ut med en slags funktion som denna:

 funktion randomSort (a: *, b: *): Number // * betyder vilken typ av inmatning if (Math.random () < 0.5) return -1; else return 1; 

Lätt! Nu kan vi använda den i vår kod så här:

 funktionen randomSort (a: *, b: *): Nummer if (Math.random () < 0.5) return -1; else return 1;  var letters:Array = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]; letters.sort(randomSort); //(no need for the shuffledLetters[] Array)

Observera att den resulterande matrisen inte kommer att vara så slumpmässigt blandad som de andra tillvägagångssätten vi har använt - i detta tillvägagångssätt finns det inte någon jämn 1/2 chans att några givet brev kommer att hamna i några given slits. Detta beror på att vi bara byter tillgränsande par element, och gör inte mer än det. Ändå är det en snygg metod :)

Det finns många andra metoder, jag vet. Har du något som du gillar bättre än dessa?

Redigera: Här är ett bra inlägg med några väldigt coola visualisationer, förklarar Fisher-Yates shuffle, som fungerar på plats. Jag rekommenderar det starkt!