Ahoj, v pravidelné minisoutěži Machr na C# .NET o placku a samolepky si
tento týden zkusíme naprogramovat shuffle. Shuffle je metoda, které přijde
na vstupu pole a ona ho pozmění tak, aby v něm byly původní prvky
zcela náhodně přeházené. Půjde o soutěž v
rychlosti. Odevzdává se jen kód metody, kterou si pojmenujte
podle vaší přezdívky. Níže je uvedená moje metoda s poněkud naivním
algoritmem:
UPDATE zadání: můžete místo generického pole brát pole
intů.
publicvoid SdracoShuffle<T>(T[] input)
{
Random r = new Random();
for (int i = 0; i < input.Length / 2; i++)
{
int a = r.Next(input.Length);
int b = r.Next(input.Length);
var c = input[a];
input[a] = input[b];
input[b] = c;
}
}
Vaše metody vyzkouším na různě velkých polích a změřím jak jsou
rychlé. Můžete používat vlákna. Z výsledků sestavím tabulku. Autor
nejrychlejšího algoritmu získává ocenění v podobě placky a také do
svého profilu. Soutěž je omezena samozřejmě jen na C# .NET. Budu to
překládat asi pod .NET 3.5.
Zkusil jsem si to jen tak nahrubo kolik by asi při normálním zpřeházení
vyšel výsledek, ale na 20 intů to vrací počet tiků v rozmezí 100k - 200k,
jak to chceš hodnotit když využití procesoru různě stoupá a kolísá?
Výkonové testy se dělají na velkém množství náhodně generovaných
dat. Když změříš seřazení milionu polí a ještě uděláš průměr z
několika měření, je to přesné. Samozřejmě si musíš vypnout spuštěné
aplikace a měřit to na stejném stroji.
Že to pole vypadá vždy jinak a je proházené po celé své délce.
Můžeme tu filosofovat o tom, co to znamená náhodně a dojdeme k tomu, že
náhodné není nikdy nic.
Ještě dodám že je nutné to pole uchovávat, tedy předgenerovat si je
třeba do listu. Jinak VM udělá optimalizaci a tu metodu třeba ani nespustí,
protože se její výsledek nepoužije.
Myslím, že pokud třeba v poli o 100 prvcích bude náhodou jeden prvek na
stejném místě, tak se vůbec nic neděje, tohle bych nehlídal, jde asi
hlavně o to, aby pořadí bylo po promíchání opravdu (pseudo)náhodné.
Dal bych třeba nějaké pravidlo, třeba že o nové pozici každého prvku
musí nějak rozhodnout metoda Next() třídy Random apod., aby byla u všech
řešení ta náhodnost stejná.
Také nerozumím, o co vlastně jde...
Navrhnout vlastní náhodný generátor, který bude rychlejší než
Random?
Napsat algoritmus, který prohází pouze část pole (viz příklad v zadání
pro pole liché délky) a doufat, že bude o něco rychlejší než řešení
ostatních?
Procvičit se v psaní vícevláknových aplikací?
Zadání je aby to pole vypadalo náhodně přeházené. Představ si to
třeba jako že mícháš karty. S mým algoritmem se teoreticky může stát
že pole bude vypadat úplně stejně a je korektní. Jde o to, aby to ve
většině případů fungovalo. Občas něco může zůstat kde to je, nemělo
by se ale stát, že ti to bude ve většině případů shufflovat jen polovinu
pole a druhá bude stejná. Nebo že tam bude nějaký hodně viditelný
pattern.
Statisticky by průměrně jeden prvek pole měl zůstat na svém původním
místě. Někdy žádný, jindy třeba pět. Není to tak těžké udělat.
Algoritmus, který použil David Čápka Hartinger, je úmyslně chybný.
Zkus si jako výstup udělat obrázek třeba 1000×1000 bodů. Pokud budu
považovat a[0], a[1], a[2], ... jako hodnoty na indexech 0, 1, 2, ..., tak si
jako dvojice [x, y] nakresli bod na souřadnice
[a[0], a[1]]
[a[1], a[2]]
[a[2], a[3]]
atd.
Testuji na tom generátory náhodných čísel. Měla by vzniknout "hvězdná
obloha". Pokud na ní budeš nacházet mohutné galaxie, je to špatně.
To už je asi moc do detailů Neplánuji to takhle zkoumat. Budu asi jen porovnávat jak se to
liší od původního pole + nějaký test stejného pole a porovnání
výsledků mezi sebou.
Není zde zadáno to, kolik se z pole musí projet. Určil bych to alespoň
na polovinu. Jinak je výsledek malo promychany. Zároveň si myslím, ze to
bude spis soutěž o nejrychlejší random metodě, ale kdo ví. Třeba někdo
vymyslí něco revolučního.
Jsou to milisekundy. Navíc závisí dost na tom, co mám spuštěné za
procesy. Když zrovna oteviram photoshop, asi to bude vice + release mode oproti
normálnímu spuštění bez VS
Při měření se snažím udržovat stálé podmínky, aby měření bylo co
nejpřesnější. Nejhorší a nejlepší měření škrtám, zbytek průměruji
nebo vyberu nejlepší.
publicstaticvoid Sidecek123Shuffle<T>(this IList<T> list)
{
Random rng = new Random();
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
Ono jde něco jiného? Já to mám taky podobné, jen jsem si udělal svůj
Random. Je to profláklý
algoritmus (Fisher-Yates shuffle), už od roku 1964 se nevymyslelo nic lepšího
Má to časovou
náročnost O(n). Co jiného by jsi chtěl? Možná změnit pole na něco
jiného, ale pod tuhle časovou náročnost se myslím ani nedá dostat, pokud
chci manipulovat s celým polem. Tím, že projdu jen část, se samozřejmě
dostanu níže a můžu použít multi-threading, ale samotný algoritmus
zůstane z větší části nezměněný.
Jo, bude to spíše o štěstí než takhle včera jsem nad tím přemýšlel,
zkoušel a googloval snad 5 hodin a nakonec jsem zůstal u toho, co jsem
vymyslel jako první
publicvoid LibcoShuffle<T>(T[] input)
{
Random r = new Random();
for (int i = 0; i < input.Length; i++)
{
int a = r.Next(input.Length);
if (i != a)
{
var b = input[i];
input[i] = input[a];
input[a] = b;
}
}
}
Koukám na to. Jsem na tom podobně. Když ten algoritmus snad napadne
každého. Nebo alespoň já ho tak měl ještě předtím, než jsem ho našel.
Sice jsou asi tři druhy, ale rozdíl v tom moc nevidím. Spíš to bude o tom,
kdo má nejchytřejší Random a jak kdo vyřeší ty operace s čísly. Ještě
jsem zkoušel LINQ, je to několikanásobně rychlejší, ale OutOfMemory při kopírování zpět z
IEnumerable do pole.
publicvoid LibcoShuffle<T>(T[] input)
{
Random r = new Random();
for (int i = 0; i < input.Length; i += 2)
{
int a = r.Next(input.Length);
i != a ? var b = input[i], input[i] = input[a], input[a] = b : 0;
}
}
publicvoid SilviniosShuffle<T>(T[] input)
{
// Fisher–Yates shuffle// Linear congruential generator: r = (a * r + c) % m, m = 2^31, a=1103515245, c=12345int r = (int) DateTime.Now.Ticks;
for (int i = input.Length - 1; i > 0; i--) {
r = (1103515245 * r + 12345) & 0x7FFFFFFF;
int j = r % (i + 1);
T c = input[j];
input[j] = input[i];
input[i] = c;
}
}
Nevadí, že mám naprosto "blbou" metodu random? Jakože není vůbec
vymakaná, ale náhodná čísla to jsou A je to zatím nejrychlejší
řešení. Ještě jsem našel něco podobného jako Silvinios, ale to jsem 1.
nevymyslel a 2. to bylo pomalejší.
Jsem si říkal, kde jsi sakra vytáhl ta čísla a on je to standart Céčka
pro rand() Ale pěkná
práce. U mě zatím nejrychlejší. Schválně, co vymyslí Luboš Běhounek Satik. To
bude zase síla. Už se těším.
No omezil bych to na nějaký daný typ, protože já fakt googloval snad 4
hodiny různé způsoby jak se dá dostat nějak níže a optimalizovat takhle,
ale nic jsem nenašel - a v IL to psát opravdu nebudu
Jenže ona je vážně hrozně blbá taková odvozená, ale
zároveň náhodná. Generuje to nicméně docela čisté mapy (beztvaré), tak
to snad nebude problém. O unsafe kódu jsem také přemýšlel, ale C# ho
spíše "nedoporučuje" a v některých případech jeho použití i zpomaluje
kód.
Myslím, že pro úkol jako je tento by bylo vhodnější C++ nebo C. Tam by
ta generika nebyla takový problém, C++ má šablony, v C se to dá nasimulovat
pomocí typedef nebo maker.
Toto je právě příklad, kde to reálně zkusíš já v C# nikdy s unsafe nepracoval,
tak jsem se "těšil" že si to zkusím a vylepším své znalosti a místo toho
jsem se akorát nasral, že u generiky to nejde
Môj posledný pokus. Podmienka je pole väčšie ako 255 prvkov.
publicvoid LibcoShuffle<T>(T[] input)
{
Random r = new Random((int)DateTime.Now.Ticks);
Byte[] b = new Byte[input.Length];
r.NextBytes(b);
for (int i = 0; i < input.Length; i += 2)
{
var c = input[i];
input[i] = input[b[i]];
input[b[i]] = c;
}
}
publicvoid LibcoShuffle(int[] input)
{
Random r = new Random((int)DateTime.Now.Ticks);
Byte[] b = new Byte[input.Length];
r.NextBytes(b);
for (int i = 0; i < input.Length; i += 2)
{
int c = input[i];
input[i] = input[b[i]];
input[b[i]] = c;
}
}
Zde je můj pokus. Nefunguje pro pole obsahující záporná čísla a není
ideální pro míchání malých polí a polí obsahujících pouze jedničky a
nuly.
staticvoid Shuffle(int[] arr)
{
Random rnd = new Random();
int size = arr.Length;
int num = rnd.Next() % (size / 2 - 1) + 1;
for (int i = 0; i < size; i++)
{
int pos = (arr[i] + num + i * 2) % size;
int temp = arr[i];
arr[i] = arr[pos];
arr[pos] = temp;
}
}
staticvoid Sidecek123Shuffle(int[] array)
{
Random r = new Random();
for (int i = array.Length - 1; i > 0; i--)
{
int index = r.Next(i);
int tmp = array[index];
array[index] = array[i];
array[i] = tmp;
}
}
Předběžné výsledky jsou následující (viz screenshot) a velmi různé
pro různá pole. Pracuji ještě na testu pro míchaná pole. Také chci zkusit
jestli to opravdu shuffluje. Nevím, jestli to dnes stihnu vyhlásit
privatestaticvoid PakoShuffle(int[] input)
{
Random r = new Random();
int n, t;
for (int i = input.Length - 1; i > 0; i--)
{
n = r.Next(5);
t = input[n];
input[n] = input[i];
input[i] = t;
}
}
Můžeš zkusit toto ? byl tam improve skoro 350% oproti tomu předtím.
Kdyby se ti nechtělo, hodil bys mi zdroják a já bych si to pustil u sebe ?
Proměřil jsem i náhodná pole. Vypadá to na bitvu mezi Brisingrem a
Davidomilem. Snad jsem tam někde neudělal chybu, jsem docela unavený.
Zdroják prozatimního testu je zde: http://www.itnetwork.cz/dev-lighter/208
Vypadá to tedy, že z testu vyšel nejlépe Lukáš Hruda, získává tedy
Machra na C# Druhý je
Silvinios. Metody Brisingra a Davidomila mi nějak nedělaly to, co měly. Pro
případ přikládám celý zdroják testů: http://www.itnetwork.cz/dev-lighter/209 .
Sakra. To je dost trapný a to sem tomu algoritmu tak veril... Stacilo pritom pridat jednu
nahodnou navic... A ten cas by se skoro nezvedl. A vubec. mel jsem to nechat na
svem prvnim pokusu. Vypada to, ze by to s tim casem nebylo tak spatne, kdyz to
davidomilovi take nefunguje. Mam na tyhle machry teda stesti... doufam, ze priste to vyjde.
Omlouvam se, za klamavy aloritmus. Nahodnost pentia nebyla schvalne.
Byly tam blbe chyby. Já jsem posílal dnes ráno ještě opravenou poslední
verzi, která byla už dostatečně náhodná, jen jsem nějak přehlédl, že
to padá při poli menším než 10 prvků. Proto ji tam David Čápka Hartinger asi nedal.
Škoda.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.