Diskuze: Hledání opakujícího čísla v poli a jejich počet.
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.


HONZ4:10.3.2022 21:40
Vykašli na Visual Basic, je prakticky K.O. Je to ztráta času. Uč se raději C#.
Musíš si udělat pole pro každou hodnotu (1,2,3,4) a tam navyšovat hodnotu, která padla.
zelvicek:11.3.2022 7:26
- podpořil bych @Honz4 ve výběru jazyka. Pokud jen ten VB.NET vyžadován nějakým učitelem/lektorem, vyfackuj ho. Násilí se nemá podporovat, ale v tomto případě je na místě.
- doporučuju si udělat danou funkcionalitu v reálu. Napiš si pár čísel na papír a navrhni algoritmus, kterým docílíš požadovaného výsledku.
A jako bonus - C# řešení, které tě nenaučí probírané téma (smyčky a práce s poli):
Random rand = new Random();
(int Value, int Count)[] res = Enumerable.Range(0, 100).Select(x => rand.Next(0, 50)).ToArray()
.GroupBy(x => x).Select(x => (Value: x.Key, Count: x.Count())).Where(x => x.Count > 1).OrderBy(x => x.Value).ToArray();
Proc tak slozite?
Prvni cast dela random a ulozi hodnotu cisla to do pole. OK.
Druha cast muze vytvaret pole. `if exist(pole[hodnota]) pole[hodnota]++
nebo pole[hodnota]=1 `. A pak potrebujes dalsi cyklus, ktery vypisuje vsechny
hodnoty, ktere nejsou 1.
Ty to resis nejak strasne slozitou cestou, ze pokazde prohledavas cele pole
(takovy jako kdyz bubble sort). Kdybys mel milion cisel, tak to pocitas jeste
druhy den
+20 Zkušeností
+2,50 Kč

Matúš Olejník:11.3.2022 11:29
Presne ako spomenul Peter, číslo ktoré padne môžeš rovno použiť ako index do poľa kde zvýšiš hodnotu o 1. Takto sa robí aj histogram pre výskyt písmen atď.
Module VBModule
'13) Je dána posloupnost kladných celých čísel (ne lineární, tedy čísla budou nějak na přeskáčku).
'Zjistěte, zda se v dané posloupnosti nějaké číslo opakuje.
'13 b) zjistěte, která čísla se opakují.
'13c) přidejte ještě informaci, kolikrát se opakují.
Sub Main()
Dim randomIntervalFrom, randomIntervalTo, i, randomNumber, randomNumbersCount As Integer
Dim histogram() As Integer
Dim output As String
Dim someNumberIsRepeated As Boolean
output = ""
someNumberIsRepeated = False
'toto keby si chcel niekedy dynamicke velkosti
randomNumbersCount = 20
randomIntervalFrom = 1
randomIntervalTo = 20
ReDim histogram(randomIntervalTo + 1)
Randomize()
For i = 0 To randomNumbersCount - 1
randomNumber = Int((randomIntervalTo - randomIntervalFrom + 1) * Rnd + randomIntervalFrom)
histogram(randomNumber) = histogram(randomNumber) + 1
If histogram(randomNumber) > 1 Then
someNumberIsRepeated = True
End If
Console.Write(Str(randomNumber) + " ")
output = output + Str(randomNumber) + ", "
Next
Console.WriteLine
If someNumberIsRepeated Then
Console.Write("Existuje cislo/cisla ktore sa opakuje/opakuju")
Console.WriteLine
For i = randomIntervalFrom To randomIntervalTo + 1
If histogram(i) > 1 Then
Console.Write("Cislo " + Str(i) + " sa opakuje " + Str(histogram(i)) + " krat ")
Console.WriteLine
End If
Next
Else
Console.Write("Ziadne cislo sa neopakuje")
End If
End Sub
End Module
Do MsgBoxu už si to prerobíš
Řešení v C#. Jistě už se dá snadno si tam domyslet např. seřazení apod. (a není tam samozřejmě XAML a obslužná třída):
public class NajiteCislo
{
public int NumerickaHodnota { get; private set; }
public int Vyskytovost { get; private set; }
public NajiteCislo(int numerickaHodnota, int vyskyty)
{
NumerickaHodnota = numerickaHodnota;
Vyskytovost = vyskyty;
}
}
public class OpakoC
{
public int Pocitadlo { get; private set; }
public int PocetCisel { get; private set; }
public string Vypis { get; private set; }
public Random Nahoda { get; private set; }
public List<int> Cisla { get; private set; }
public List<NajiteCislo> NajitaCisla { get; private set; }
public OpakoC(int pocetCisel)
{
PocetCisel = pocetCisel;
Nahoda = new Random();
}
public string VypisOpakujiciCisla()
{
// Nové listy
Cisla = new List<int>();
NajitaCisla = new List<NajiteCislo>();
// Vytvoříme si náhodnou řadu čísel
for (int i = 0; i < PocetCisel; i++)
{
Cisla.Add(Nahoda.Next(1, 20));
}
// Procházíme řadu čísel
foreach (int prvniVyskyt in Cisla)
{
Pocitadlo = 0;
foreach (int vsechnyVyskyty in Cisla)
{
if (prvniVyskyt == vsechnyVyskyty)
{
Pocitadlo++;
}
}
NajitaCisla.Add(new NajiteCislo(prvniVyskyt, Pocitadlo));
}
// Vyprázdníme si výpis
Vypis = String.Empty;
// Vytvoříme si výpis
foreach (NajiteCislo polozka in NajitaCisla)
{
Vypis += String.Format("Číslo: {0} ---- Počet výskytů: {1}\n", polozka.NumerickaHodnota, polozka.Vyskytovost);
}
// Vrátíme výpis
return String.Format(Vypis);
}
}
Myšlenka je tam ta, že foreach
em procházím všechna čísla
a u každého přitom dalším vnořeným foreach
em srovnávám
toto číslo se všemi ostatními. Když najdu shodu (a to včetně shody
samotného se sebou), tak posunu počítadlo. Na závěr uložím údaj, ve
kterém je numerická hodnota čísla a počet jeho výskytů (počítadlo).
Tato úloha ač je primitivní je zajímavá v tom, že je zde velký prostor pro optimalizaci. Respektive ji lze napsat jak efektivně, tak velmi neefektivně. Celé to způsobuje informace, zda existuje číslo, které se opakuje, či nikoli. Nebudu se zde zabývat způsobem, který už tu padl, tedy variantou, kde se prohledává zbytek pole a hledá se ta samá hodnota. Toto řešení je velice neefektivní. Ale budu psát o způsobu, kde se aktualizuje hodnota na daném indexu, čímž vzniká informace o počtu konkrétního čísla v poli.
Efektivita zápisu je závislá na dvou hlavních veličinách. A to na počtu hodů a rozsahu, jakých hodnot mohou náhodná čísla nabývat.
Testovat existenci opakování čísla v poli (hodnota na indexu > 1) v cyklu generující náhodná čísla je pohodlné, ale provádí se nad všemi prvky pole. Tuto operaci nelze přerušit, neboť generování čísel je třeba dokončit. Skvěle se hodí, je-li počet hodů podstatně menší nežli rozsah čísel a to i přesto, že indikace opakování se přepisuje. Bude-li počet hodů blízký rozsahu čísel nikoli však větší, je lepší testovat existenci opakování čísla už mimo cyklus generování čísel, neboť lze tuto operaci předčasně ukončit a ušetřit tak drahocenný čas. A také pokračovat ve výpisu tam, kde jsme při testu prvního výskytu opakování skončili. Pokud bude počet hodů větší než rozsah čísel, pak je test existence opakování čísla naprosto zbytečný, neboť vždy existuje číslo které se opakuje.
Následující kód ukazuje optimalizaci pro případ, kdy počet hodů není v porovnání s rozsahem čísel podstatně menší a ne větší než rozsah (PH <= RC).
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define LRANGE 1
#define RRANGE 6
#define THROWS 5
int main(void) {
int vals[RRANGE + 1] = {0};
int reppos = 0;
srand((unsigned)time(NULL));
// naplneni pole nahodne vygenerovanymi cisly v rozsahu
for (int i = 0, k = RRANGE - LRANGE + 1; i < THROWS; i++) vals[(rand() % k) + LRANGE]++;
// test existence opakujiciho se cisla, prvni vyskyt
for (int i = LRANGE; i <= RRANGE; i++) {
if (vals[i] > 1) {
reppos = i;
break;
}
}
// Vypis informaci o existenci opakujicich se cisel
if(!reppos) puts("Neexistuje cislo ktere by se opakovalo");
else {
puts("Existuje cislo ktere se opakuje");
for (int i = reppos; i <= RRANGE; i++) {
if (vals[i] > 1) printf("Cislo %d se opakuje %dx\n", i, vals[i]);
}
}
return 0;
}
Výhodou tohoto způsobu je, že test existence opakujícího se čísla se nad každým indexem provádí právě a pouze jednou. Testování se ukončí s prvním výskytem opakujícího se čísla a od indexu tohoto výskytu pokračuje zbylé vypisování informací. Se vzrůstajícím poměrem rozsahu čísel ku počtu hodů efektivita tohoto způsobu klesá.
Existence opakujícího se čísla lze tedy provádět u počtu hodů nebo rozsahu čísel. Na základě poměru zvolit vhodný způsob.
Výpis informací opakování se čísla se provádí u rozsahu čísel. S možným ubývajícím množstvím opakujících se čísel lze přemýšlet nad optimalizací (neprocházet celé pole kvůli pár opakujícím se číslům, zejména pokud rozsah bude extrémně velký a možný počet opakujících se čísel minimální).
Tedy:
PH >> RC - netřeba provádět existenci opakování, vždy se nějaké
číslo opakuje, možný vysoký výskyt opakování
PH > RC - netřeba provádět existenci opakování, vždy se nějaké
číslo opakuje, počet opakování nižší, nabízí se otázka optimalizace
při závěrečném výpisu.
PH <= RC - existenci opakování provádět vně cyklu generující náhodná
čísla, počet opakování ještě nižší, optimalizace při závěrečném
výpisu rozhodně vhodná
PH << RC - existenci opakování provádět uvnitř cyklu generující
náhodná čísla, počet opakování minimální, optimalizace při
závěrečném výpisu téměř či úplně nutná.
Kde:
PH - počet hodů
RC - rozsah čísel
Spravil som pokus. Vytvoril som 4 verzie programu. Pre zoznam čísiel
používam List. Krátky popis:
Verzia 1:
zoradí List a potom stačí keď prejde celé pole iba jeden krát
Verzia 2:
prechádza List v dvoch cykloch, v prípade že nájde zhodu, zaznamená to a
položku z Listu odstráni, čím redukuje počet iterácii cyklu
Verzia 3:
Od používateľa Jakub Raida
Nevýhodou je výpis - všetky duplicitné čísla vypisuje tzn. napr. ak je
číslo 9 v poli 5x tak ho 5x vypíše
Verzia 4:
Od používateľa zelvicek
toto naštudované nemám
Tu je vysledok testu: Trvanie je v milisekundách
Počet čísiel: 10
Verzia 1 trvanie: 1,7476
Verzia 2 trvanie: 1,0081
Verzia 3 trvanie: 2,9303
Verzia 4 trvanie: 71,8895
Počet čísiel: 100
Verzia 1 trvanie: 4,7877
Verzia 2 trvanie: 2,9706
Verzia 3 trvanie: 25,2603
Verzia 4 trvanie: 85,0549
Počet čísiel: 1 000
Verzia 1 trvanie: 3,301
Verzia 2 trvanie: 10,0022
Verzia 3 trvanie: 240,0204
Verzia 4 trvanie: 133,0393
Počet čísiel: 10 000
Verzia 1 trvanie: 5,0046
Verzia 2 trvanie: 15,0028
Verzia 3 trvanie: 4649,8514
Verzia 4 trvanie: 76,2385
Počet čísiel: 100 000
Verzia 1 trvanie: 15,8993
Verzia 2 trvanie: 1187,2465
Verzia 3 trvanie: 451568,8017
Verzia 4 trvanie: 86,0043
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpakovaniaCislaVpoli
{
internal class Program
{
List<int> list = new List<int>();
string opakujuceCisla = "";
public List<NajiteCislo> NajitaCisla { get; private set; }
public int Pocitadlo { get; private set; }
public int PocetCisel { get; private set; }
public string Vypis { get; private set; }
public Random Nahoda { get; private set; }
int min = 1; // minimálna hodnota čísiel
int max = 20; // (max - 1) - maximálna hodnota čísiel
int pocet = 100000; // počet
static void Main()
{
Program program = new Program();
program.NacitajHodnoty();
program.HladajDuplicity1();
program.HladajDuplicity3();
program.HladajDuplicity4();
program.HladajDuplicity2();
Console.ReadKey();
}
void NacitajHodnoty() // Naplní List náhodnými číslami
{
Random rand = new Random();
for (int i = 0; i < pocet; i++)
list.Add(rand.Next(min, max));
}
private void HladajDuplicity1() // zoradí List a potom postupne hľadá počet opakovaní
{
opakujuceCisla = "";
int pocetOpakovani = 1;
list.Sort();
for (int i = 0; i < list.Count - 1; i++)
if (list[i] == list[i + 1])
pocetOpakovani++;
else if (pocetOpakovani > 1)
{
opakujuceCisla += $"Cislo {list[i]} sa nachádza v poli {pocetOpakovani }x\n";
pocetOpakovani = 1;
}
else
pocetOpakovani = 1;
if (pocetOpakovani > 1)
opakujuceCisla += $"Cislo {list[list.Count - 1]} sa nachádza v poli {pocetOpakovani }x\n";
// Výpis hodnôt
Console.WriteLine($"Verzia1: \n{opakujuceCisla}");
}
private void HladajDuplicity2()
{
opakujuceCisla = "";
int pocetOpakovani = 1;
for (int i = 0; i < list.Count-1; i++)
{
for (int j = i+1; j < list.Count; j++)
if (list[i] == list[j])
{
pocetOpakovani++;
list.RemoveAt(j);
j--;
}
if (pocetOpakovani > 1)
{
opakujuceCisla += $"Cislo {list[i]} sa nachádza v poli {pocetOpakovani }x\n";
pocetOpakovani = 1;
}
}
//Výpis
Console.WriteLine($"Verzia2: \n{opakujuceCisla}");
}
private void HladajDuplicity3() // Jakub Raida
{
NajitaCisla = new List<NajiteCislo>();
// Procházíme řadu čísel
foreach (int prvniVyskyt in list)
{
Pocitadlo = 0;
foreach (int vsechnyVyskyty in list)
if (prvniVyskyt == vsechnyVyskyty)
Pocitadlo++;
NajitaCisla.Add(new NajiteCislo(prvniVyskyt, Pocitadlo));
}
// Vyprázdníme si výpis
Vypis = String.Empty;
// Vytvoříme si výpis
foreach (NajiteCislo polozka in NajitaCisla)
Vypis += String.Format("Číslo: {0} ---- Počet výskytů: {1}\n", polozka.NumerickaHodnota, polozka.Vyskytovost);
//Výpis
Console.WriteLine($"Verzia3: \n{Vypis}");
}
void HladajDuplicity4() // zelvicek
{
Random rand = new Random();
(int Value, int Count)[] res = Enumerable.Range(0, pocet).Select(x => rand.Next(min, max)).ToArray()
.GroupBy(x => x).Select(x => (Value: x.Key, Count: x.Count())).Where(x => x.Count > 1).OrderBy(x => x.Value).ToArray();
for (int i = 0; i < res.Length; i++)
Console.WriteLine($"Číslo {res[i].Value} sa nachádza v poli {res[i].Count}x");
}
}
public class NajiteCislo
{
public int NumerickaHodnota { get; private set; }
public int Vyskytovost { get; private set; }
public NajiteCislo(int numerickaHodnota, int vyskyty)
{
NumerickaHodnota = numerickaHodnota;
Vyskytovost = vyskyty;
}
}
}
No, a co reseni, ze to zapisujes do pole?
1. pole1 = random cisla
2. existuje(pole2[ pole1[i] ]) ? pole2[ pole1[i] ]+1 : 1
3. vypis
Ps, ja jsem myslel, ze C# nebo C++ umi veci delat rychle. Ja si tady jako
serazuji milion cisel do par sekud a ty tam neco pocitas stovky? Pozor, u
slabsich algoritmu mam stopku pri 0.5s.
https://mlich.zam.slu.cz/…g4-pokus.htm
To od Zelvicek je jen jiny zapis s vyuzitim vestavenych funkci.
// naplneni pole random cisly, tusim value=0-50, pocet cisel 100
Random rand = new Random();
(int Value, int Count)[] res = Enumerable.Range(0, 100).Select(x => rand.Next(0, 50)).ToArray()
// pro kazde x, vyber x a preved to na pole [ Value, Count:]
.GroupBy(x => x).Select(x => (Value: x.Key, Count: x.Count())).
// a vyber z toho pole vse , kde count>1
// a serad podle x (slo by tam seradit podle count)
Where(x => x.Count > 1).OrderBy(x => x.Value).ToArray();
Coz je vlastne totez, co sem psal ja. Jen, cyklem by to bylo asi rychlejsi, protoze ty specialni funkce maji slozitejsi kod.
Jestli mu to chces urychlit, tak z posledni casti vyrat to orderovani
Where(x => x.Count > 1).ToArray();
Jo, a mimochodem, mozna to bude znit hloupe, protoze c# neznam, ale
- kdyz provedes u listu sort a on je globalni promena, tak, nezmeni se to i pro ty dalsi?
- a podobne s remove
- a u zelvicka ti to brzdi ta random cast, jeste, kterou ti ostatni delat nemusi (to se da zohlednit spocitanim sumy pro random a odecist od zelvickove sumy)
Jo, a duplicity 2,3 ti brzdi jeste tento radek
for (int j = i+1; j < list.Count; j++)
Funguje to tak, ze on v kazdem cyklu vyhodnocuje podminku a list.Count pocita
znovu a znovu. Protoze count neni konstanta.
Takto bys mohl ziskat tak spoustu casu, vic procent, cim delsi je array.
int j_end = list.Count;
for (int j = i+1; j < j_end; j++)
DarkCoder:17.3.2022 11:26
Porovnej výsledky s tímto:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define LRANGE 1
#define RRANGE 20
#define THROWS 100000
double initPerformanceCounter(void);
__int64 getTicks(void);
int main(void) {
int vals[RRANGE + 1] = { 0 };
double PCFreq;
__int64 tick1, tick2, tick3;
srand((unsigned)time(NULL));
PCFreq = initPerformanceCounter();
if (!PCFreq) {
fprintf(stderr, "Inicializace Performance counteru selhala\n");
exit(1);
}
tick1 = getTicks();
for (int i = 0, k = RRANGE - LRANGE + 1; i < THROWS; i++) (*(vals + (rand() % k) + LRANGE))++;
tick2 = getTicks();
for (int *p = vals + LRANGE, *p2 = vals + RRANGE, *p3 = p; p <= p2; p++) {
if (*p > 1) printf("Cislo %d se opakuje %dx\n", p - p3 + LRANGE, *p);
}
tick3 = getTicks();
putchar('\n');
printf("Pocet generovanych cisel: %d\n", THROWS);
printf("Rozsah generovanych cisel: %d-%d\n", LRANGE, RRANGE);
printf("Doba generovani histogramu: %f ms\n", (double)(tick2 - tick1) / PCFreq);
printf("Doba vypisu opakujicich se cisel: %f ms\n", (double)(tick3 - tick2) / PCFreq);
printf("Celkova doba (histogram + vypis): %f ms\n", (double)(tick3 - tick1) / PCFreq);
return 0;
}
double initPerformanceCounter(void) {
LARGE_INTEGER li;
if (!QueryPerformanceFrequency(&li)) return 0.0;
return (double)(li.QuadPart) / 1000.0;
}
__int64 getTicks(void) {
LARGE_INTEGER li;
if (!QueryPerformanceCounter(&li)) return 0;
return li.QuadPart;
}
Fodo:18.3.2022 13:33
Ak by som chcel do poľa zapisovať musím poznať jeho veľkosť, pri List nepotrebujem, a verzia s odstraňovaním prvkov by mi tiež nefungovala tak ako chcem.
Tie časy sú v milisekundách
Ďakujem za vysvetlenie kódu od Zelvicek
Fodo:18.3.2022 13:36
- máš pravdu zmení, preto som tuto verziu použil ako poslednú
- detto
- áno máš pravdu, musím to prerobiť aby to bolo objektívne
Fodo:18.3.2022 13:40
Myslel si Duplicity 1 a 2 - to som si neuvedomil, ale použiť to môžem iba pri Duplicite1, pretože pri Duplicite2 sa mi mení veľkosť Listu
DarkCoder:18.3.2022 15:28
Nejsem C# programátor, nikdy jsem v něm nic nenapsal.
Níže přikládám přepis mého způsobu uvedeného dříve z C do C#.
Jaké budou výsledky zde?
using System;
public class Program {
public static void Main() {
int min = 1;
int max = 20;
int throws = 100000;
int[] vals = new int[max + 1];
// generovani histogramu
Random rand = new Random();
for (int i = 0; i < throws; i++) vals[rand.Next(min, max + 1)]++;
// vypis opakujicich se cisel
for (int i = min; i <= max; i++){
if(vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
}
}
Poradi mas 1 3 4 2. Sort delas v 1. Tim padem plati i pro 3 4 2. A muze ti to
znacne zkreslit vysledek. Treba slabinou bubble-quick-sort je pole serazene
pozpatku. Slabinou insert-sortu take. Pokud to seradis, tak nemas jistotu, zda
to neovlivni rychlost. Pracuji tedy s rozdilnym vstupem a tudiz ty casy nelze
vzajemne porovnat.
A take pracuji s rozdilnym vystupem. Docilit shodneho vystupu muze ten
algoritmus znacne zatizit.
A take u mereni musis zvazit vliv okolni zateze. Pocitac ti neda vykon, kdyz
neco dela na pozadi.
Ale, jako, v zasade z kodu a cisel je jasne, ze tam rozdil je.
Sort a secteni je vyborny napad (Dupl1).
Odstranovani prvku (Dupl2), to je pro velke pole pomale. Kazdopadne to mas sortnute a timpadem to neni treba odstranovat. Kazda operace navic zere cas. Predevsim IF a odstanovani, pokud neni z konce pole nebo ze zacatku.
if (list[i] == list[j]) // pokud toto nastane, tak je pocet opakovani minimalne 2
{
pocetOpakovani++;
list.RemoveAt(j);
j--;
}
if (pocetOpakovani > 1) // tudiz nema smysl se znovu ifem ptat
{
(Dupl3) opakovane prochazi totez pole, dostava se do zatizeni n * n.
(Dupl4)
A tak jako celkove je rychlejsi a uspornejsi pracovat s array. List je mozna elegantni, ale prace s array by mela byt rychlejsi, pokud ti zalezi na vykonu. Tipnul bych to o 10-20%, podle app.
Fodo:18.3.2022 22:58
S tým Sort máš pravdu, to ovplyvnilo výsledok, som si to neuvedomil,
prerobil som to:
Varianta 1 - zmenil som list.Count na konštantu, trochu to zrýchlilo ale moc
nie, pri 100k číslach to bolo rýchlejšie o cca 2 ms (21,8550125 ms -
19,855275 ms), spravil som aj pokus s polom, ale to trvalo raz tak dlho, cez 40
ms
Varianta 2 - nechal som vytvoriť nezoradené pole a to už bolo na čase
cítiť - 559,69853125 ms pri tom istom teste ako vyššie
Varianta 3 - som vypustil
Varianta 4 - to som nechal ako bolo a čas 48,9144375 ms
Fodo:18.3.2022 23:01
Toto sa nedá porovnávať, pretože v mojich príkladoch máš polia o 100 000 prvkoch a ty máš pole o 20 prvkoch, ale hodil som to do programu a čas mi vyšiel - 36,43126875 ms
DarkCoder:19.3.2022 2:03
Samozřejmě že se to porovnávat dá, zadání úlohy je pro všechny testy stále stejné: Vygeneruj 100000 náhodných čísel v rozsahu 1-20 a vypiš na obrazovku hlášku o tom, kolikrát se opakované číslo opakuje. Jak už se s tím který způsob popere, je jedno.
Jako výchozí hodnotu budu brát čas nejrychlejší verze, tedy té první kde se využívá List, a to: 15,8993 ms.
Verze s polem, jehož hodnoty na indexech představují počty opakování čísla odpovídající indexu a výpisem pole o velikosti rozsahu generovaných náhodných čísel (1-20) s lineární složitostí na základě jedné relace, má čas: 36,4313 ms.
Nevěřím, že by tato verze s polem byla více než 2x pomalejší, než první verze s Listem, ve které se provádí tolik úkonů.
A tak uděláme test přímo v programu nad jednotlivými verzemi a uvidíme,
jak to skutečně je.
Pro zjištění času použijeme třídu Stopwatch.
using System.Diagnostics;
// ...
Stopwatch sw = new Stopwatch();
sw.Start();
// mereny usek programu
sw.Stop();
Console.WriteLine("cas = {0}", sw.Elapsed);
Tedy verze s polem doplněná o kód potřebný k měření času úseku kódu:
using System;
using System.Diagnostics;
public class Program {
public static void Main() {
int min = 1;
int max = 20;
int throws = 100000;
int[] vals = new int[max + 1];
Stopwatch sw1 = new Stopwatch();
sw1.Start();
// generovani histogramu
Random rand = new Random();
for (int i = 0; i < throws; i++) vals[rand.Next(min, max + 1)]++;
sw1.Stop();
Stopwatch sw2 = new Stopwatch();
sw2.Start();
// vypis opakujicich se cisel
for (int i = min; i <= max; i++){
if(vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
sw2.Stop();
Console.WriteLine("sw1 = {0}", sw1.Elapsed);
Console.WriteLine("sw2 = {0}", sw2.Elapsed);
}
}
U ostatních verzí tedy udělat totéž (nezapomenout na using System.Diagnostics):
using System.Diagnostics;
// ...
static void Main() {
Program program = new Program();
Stopwatch sw1 = new Stopwatch();
sw1.Start();
program.NacitajHodnoty();
sw1.Stop();
Stopwatch sw2 = new Stopwatch();
sw2.Start();
program.HladajDuplicity1();
// program.HladajDuplicity3();
// program.HladajDuplicity4();
// program.HladajDuplicity2();
sw2.Stop();
Console.WriteLine("sw1 = {0}", sw1.Elapsed);
Console.WriteLine("sw2 = {0}", sw2.Elapsed);
Console.ReadKey();
}
Zajímají mě výpisy naměřených hodnot sw1 a sw2 všech verzí.
Nad každou verzí spustit program alespoň 5x.
Ještě jeden dodatek k funkci generující náhodné číslo.
int min = 1;
int max = 20;
// ...
Random rand = new Random();
for (int i = 0; i < pocet; i++) list.Add(rand.Next(min, max));
rand.Next(min, max) způsobí vygenerování náhodného čísla v rozsahu 1-19, nikoli v rozsahu 1-20.
Je tedy třeba upravit zdrojový kód na rand.Next(min, max + 1)
int max = 20;
rand.Next(min, max + 1)
// lepe asi zvetsit predem nez to pokazde scitat az v Next()
int max = 20;
max++;
rand.Next(min, max)
DarkCoder - C neovladam, ale, vim, ze spousta jazyku nama rada, kdyz je promena undefined a tvuj historogram zvysuje promenou ++, ktera jeste nebyla vytvorena, neobsahuje nulu nebo ma dokonce nahodnou hodnotu. To by mohlo cely proces brzdit. Ale treba to funguje jinak, treba, kdyz mas pole int, tak je tam nula jakoby od zacatku.
vals[rand.Next(min, max + 1)]++
Peter Mlich:19.3.2022 23:20
ups, ja jsem ti dal spatny link, na pokusnou verzi. tam ted testuji jeden
algoritmus, tak to mam omezene na 15 prvku
https://mlich.zam.slu.cz/…g4-pokus.htm
- dej si test 2 (nebo 3), klikni 100.000 prvku, klikni na "All" u algoritmu a klikni "start"
- vsude, kde se neobjevi t0, tak byl algoritmus stopnut pro cas>500ms (to
sem tam dal zamerne stopku, kdyby nekdo pustil algoritmus, co by to pocital
hodinu
)
- u mne trvalo 45ms nez se provedl tim-sort pole o velikosti 100.000 timsortem, a jednalo se o 3.397.537 cyklu, coz je 34x vic nez tvych 100.000 cyklu. Takze, tvuj kod by mel byt kolem 2-5-10 ms.
- a taky tam mas counting sort, ktery to seradil za polovicni cas, pri pouziti 300.000 cyklu (vytvari nejdriv pole pocet opakovani, to resis ty, + pak z nej generuje serazene pole - tento algoritmus se pouziva pro serazeni pole s integer hodnotami); cili, mel bys byt rychlejsi nez counting-sort Pigeonhole (ale mam pomaly pocitac, tak, treb na tvem to bude 2ms a ne 20ms)
Pokud tam tedy mas 1000 a vic, tak je neco zasadne spatne. A take je jasne, u zelvicka, ze dela nejspis nejake operace navic, kdyz nedosahl tech 15ms jako ten prvni kod.
DarkCoder:19.3.2022 23:29
Pokud bychom se bavili o C, pak globální proměnné a statické proměnné jsou inicializovány na 0. Co se týká lokálních proměnných, tam proměnná skutečně obsahuje náhodnou hodnotu.
Co se týká C#, tak defaultní hodnota pro numerické typy je 0. Alespoň co jsem se v rychlosti dočetl. To byl důvod, proč jsem deklaroval pole, ale neprovedl jsem nastavení jeho prvků na 0. Spolehl jsem se na defaultní 0 hodnotu a tu jsem si pak při testování ještě ověřil. A skutečně to tak je.
V C bych musel udělat:
int vals[RRANGE + 1] = { 0 };
Tím inicializuji všechny prvky pole vals na hodnotu 0.
Co se týká generování histogramu
// generovani histogramu
Random rand = new Random();
for (int i = 0; i < throws; i++) vals[rand.Next(min, max + 1)]++;
Ano, dobrý postřeh. Bylo by rozhodně lepší provést výpočet výrazu dříve než v každém volání funkce. Výraz max + 1 je konstanta, kterou lze určit a zejména v tolika volání (100000) se to rozhodně vyplatí. Úryvek kódu by pak mohl vypadat např. takto:
// generovani histogramu
Random rand = new Random();
for (int i = 0, max++; i < throws; i++) vals[rand.Next(min, max)]++;
Ušetří se tím další čas.
Zkusim ti ten zelvickuz zapis prepsat trochu jinak. On vyuziva funkce z toho enumerate, kdete se podobaji sql prikazum a lambda zapisum z pythonu
Random rand = new Random();
(int Value, int Count)[] res =
Enumerable
.Range(0, pocet)
.Select(x => rand.Next(min, max))
.ToArray()
.GroupBy(x => x)
.Select(x => (Value: x.Key, Count: x.Count()))
.Where(x => x.Count > 1)
.OrderBy(x => x.Value)
.ToArray();
---
.Range(0, pocet)
.Select(x => rand.Next(min, max))
.ToArray() // aplikuj predchozi kroky na kazdy prvek v array
cyklus(i = 0 az pocet)
x = rand.Next(min, max)
array[i] = x
---
.GroupBy(x => x) // spojuj podle x
.Select(x => (Value: x.Key, Count: x.Count())) // pro kazde takto vnizkle x aplikuj funkci, ktera vytvori pole
// radek = array(value: radek.index, count: radek.count() )
.Where(x => x.Count > 1) // ponech radky, ktere maji count>1
.OrderBy(x => x.Value) // serad podle radek.value
.ToArray(); // zavolanim to array se vsechny predchozi kroky aplikuji na kazde X v array
To jsou proste nejake funkce toho enumerate, ktere se aplikuji jako call back a provedou se az v okamziku zavolani ToArray. Jestli to spravne chapu. V php bys to napsal takto, treba
.Range(0, pocet)
.ToArray()
<?php
$array = array();
foreach (range(0, pocet, 1) as $value) {
}
?>
---
.GroupBy(x => x)
.Select(x => (Value: x.Key, Count: x.Count()))
.ToArray();
<?php
$new_array = array();
foreach ($array as $row) {
$group_key = $row; // shodou okolnosti je to i hodnota v tom poli $row=$value=$group_key (x => x)
// ale dal ti to kvuli srozumitelnosti budu psat oddelene
if (!isset($new_array[$group_key]))
{ $new_array[$group_key] = array( 'group-key' => $group_key, 'group-values' => array($row)); continue; }
$new_array[$group_key]['group-values'][] = $row;
}
foreach ($new_array as &$value) { // ten and dela to, ze pracuje s handle, hodnotu vrati zpet do array
$value = array('Value' => $value['group-key'], 'Count' => count($value['group-values']))
}
return $new_array;
/*
bez toho & by se to muselo naksat jako
foreach ($new_array as $key=>$value) {
$new_array[$key] = array('Value' => $value['group-key'], 'Count' => count($value['group-values']))
}
*/
?>
Bohuzel C neovladam a nechce se mi premylset, jak by se to v nem zapisovat. Ale, snad je to ted jasnejsi.
Jako vstup dalsiho kroku se pouziva vystup z toho prechoziho. Vemes array1, predelas ji na array2 a pouzijes dal a udelas array3. Vetsina tech funkci prepise aktualni polozku v array pomoci funkce, kterou predas jako parametr. Group vytvari array novou. Vyhoda je, ze to mas krasne pojmenovane. Pouzivam neco podobne v javascriptu.
Fodo:28.3.2022 20:46
Odpisujem trochu neskôr, nebolo času.
Nedá sa to porovnať, som napísal preto, lebo ty si pri generovaní čísiel zrátavaš počty čísiel a priraďuješ do poľa v ktorom máš tieto čísla, ale v zadaní je že už máš postupnosť čísel, tzn. ja som to bral tak, že generovanie čísiel nie je zadaním úlohy, to je tam len vložené aby bolo s čím pracovať.
Ja som používal na meranie času DateTime.Now, uložil som do premennej
pred a po, potom som odčítal. Nižšie výsledky z požitím Stopwatch(). V
mojich predošlých výsledkoch bola pri „verzii 1pole“ chyba, preto bol
čas cca 2 násobný.
Upravil som aj rozsah náhodných čísiel. V čase je zarátané aj generovanie
náhodných čísel, pretože vo verzii 4 ho neviem oddeliť.
VERZIA 1:
00:00:00.0031505
00:00:00.1329323
00:00:00.1973771
00:00:00.2780999
00:00:00.3260673
00:00:00.3760173
00:00:00.4156473
00:00:00.4711198
00:00:00.5554469
00:00:00.6088071
00:00:00.6478704
00:00:00.7049988
00:00:00.7551432
00:00:00.8278246
00:00:00.9268072
Priemer = 0,45170685625
VERZIA 1pole:
00:00:00.0059360
00:00:00.1386639
00:00:00.2000115
00:00:00.2817709
00:00:00.3329280
00:00:00.3776999
00:00:00.4226900
00:00:00.4731310
00:00:00.5630960
00:00:00.6104298
00:00:00.6542619
00:00:00.7069290
00:00:00.7569540
00:00:00.8359176
00:00:00.9290137
Priemer = 0,455589575
VERZIA 1 (konštanta):
00:00:00.0083590
00:00:00.1410614
00:00:00.2031389
00:00:00.2888422
00:00:00.3357108
00:00:00.3793761
00:00:00.4253759
00:00:00.4749544
00:00:00.5651181
00:00:00.6121268
00:00:00.6563505
00:00:00.7089013
00:00:00.7586835
00:00:00.8396434
00:00:00.9311945
Priemer = 0,4580523
VERZIA 2:
00:00:00.0108665
00:00:00.1433131
00:00:00.2053980
00:00:00.2907392
00:00:00.3376136
00:00:00.3813673
00:00:00.4277097
00:00:00.4767404
00:00:00.5688399
00:00:00.6138255
00:00:00.6580721
00:00:00.7108595
00:00:00.7606082
00:00:00.8540468
00:00:00.9346598
Priemer = 0,460916225
VERZIA 4:
00:00:00.1034593
00:00:00.1702963
00:00:00.2314295
00:00:00.3079321
00:00:00.3541638
00:00:00.3975004
00:00:00.4475470
00:00:00.5131053
00:00:00.5929063
00:00:00.6301712
00:00:00.6790487
00:00:00.7372219
00:00:00.7869355
00:00:00.8904254
00:00:00.9637674
Priemer = 0,48786938125
VERZIA 5:
00:00:00.1267384
00:00:00.1915236
00:00:00.2746516
00:00:00.3242064
00:00:00.3704796
00:00:00.4135950
00:00:00.4696124
00:00:00.5506552
00:00:00.6071395
00:00:00.6459967
00:00:00.6969358
00:00:00.7534134
00:00:00.8162946
00:00:00.9179602
00:00:00.9910700
Priemer = 0,509392025
DarkCoder:28.3.2022 21:43
Pošli sem pro kolik hodů a v jakém rozsahu mohou vygenerovaná čísla nabývat, ať můžeme porovnat verze. Pošlu sem verzi, jejíž vstupem bude pole o velikosti počtu hodů s vygenerovanými čísly ze kterých určím opakující se čísla která pak vypíši.
DarkCoder:28.3.2022 21:56
K výsledkům:
To vážně se doba generování náhodných čísel, zjištění opakování a
výpisu pohybuje kolem 500ms?
DarkCoder:29.3.2022 2:00
Porovnej výsledky s následující verzí:
using System;
using System.Diagnostics;
public class Program {
public static void Main() {
int min = 1;
int max = 20;
int throws = 100000;
int maxplus = max + 1;
int[] nums = new int[throws];
int[] vals = new int[maxplus];
Stopwatch sw = new Stopwatch();
sw.Start();
// naplneni pole nahodne vygenerovanymi cisly
Random rand = new Random();
for (int i = 0; i < throws; i++) nums[i] = rand.Next(min, maxplus);
// stanoveni poctu opakovani toho ktereho cisla
for (int i = 0; i < throws; i++) vals[nums[i]]++;
// vypis opakujicich se cisel a jejich pocet opakovani
for (int i = min; i <= max; i++){
if(vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
sw.Stop();
Console.WriteLine("sw = {0}", sw.Elapsed);
}
}
Fodo:29.3.2022 7:08
Dal som 1000 hodov. Rozsah 1- 20 (max = 21).
Spravil som pokus, hodil som tvoj kód do metódy HladajDuplicity6() a čas som meral pred a po volaní metódy (nie v nej) a vyšiel (1000 hodov, 15 opakovaní):
// priemer je v sekundách
VERZIA 1 - Priemer = 0,62299739375
VERZIA 1pole: - Priemer = 0,627510825
VERZIA 1 (konštanta): - Priemer = 0,632112025
VERZIA 2: - Priemer = 0,63575756875
VERZIA 4: - Priemer = 0,6608958125
VERZIA 5: - Priemer = 0,68036849375
VERZIA 6: - Priemer = 0,7052288375
//Tvoj kód, trochu upravený, ktorý som použil:
void HladajDuplicity6() // DarkCoder
{
int[] nums = new int[pocet];
int[] vals = new int[max];
// naplneni pole nahodne vygenerovanymi cisly
Random rand = new Random();
for (int i = 0; i < pocet; i++) nums[i] = rand.Next(min, max);
// stanoveni poctu opakovani toho ktereho cisla
for (int i = 0; i < pocet; i++)
vals[nums[i]]++;
// vypis opakujicich se cisel a jejich pocet opakovani
for (int i = min; i < max; i++)
if (vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
potom som spravil nový projekt a tvoj kód som dal priamo do metódy Main(), bez zmeny (100 000 hodov, 1 opakovanie)
sw = 00:00:00.0104522
Takže ten čas nerobí výkon toho kódu, ale pravdepodobne volanie metódy
DarkCoder:29.3.2022 10:16
Pro 1000 hodů a rozsah <1,20> se celková doba rozhodně nebude pohybovat v řádech stovkách milisekund. Zabalení všech operací do funkce, která se volá 1x žádné časově navýšení nezpůsobí. Výpočet průměru máš chybný. Jednotlivé časy si budou blízké a nebudou stoupat s každým opakováním.
DarkCoder:29.3.2022 12:19
Zde máš moji verzi do které byl přidán mechanismus opakování výpočtu pro eliminaci nepřesnosti. Nutno podotknout, že pro správnou funkci programu bylo třeba přidat nulování pole výsledků. Nicméně nulování pole o velikosti rozsahu generovaných čísel nezpůsobí bůhvíjaké navýšení celkového času. Počet opakování v programu byl stanoven na 10 (proměnná rep).
using System;
using System.Diagnostics;
public class Program {
public static void Main() {
int min = 1;
int max = 20;
int throws = 100000;
int maxplus = max + 1;
int rep = 10;
int[] nums = new int[throws];
int[] vals = new int[maxplus];
Stopwatch sw = new Stopwatch();
Random rand = new Random();
sw.Start();
for(int j = 0; j < rep; j++){
for (int i = min; i <= max; i++) vals[i] = 0;
for (int i = 0; i < throws; i++) nums[i] = rand.Next(min, maxplus);
for (int i = 0; i < throws; i++) vals[nums[i]]++;
for (int i = min; i <= max; i++){
if(vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
}
sw.Stop();
Console.WriteLine("sw = {0}", sw.Elapsed / rep);
}
}
// zajimalo by mne, zda je nejaky casovy rozdil mezi
for (int i = 0; i < pocet; i++) nums[i] = rand.Next(min, max);
for (int i = 0; i < pocet;) nums[i++] = rand.Next(min, max);
int i = 0; for (; i < pocet;) nums[i++] = rand.Next(min, max);
int i = 0; while (i < pocet) nums[i++] = rand.Next(min, max);
// a jestli by byl rozdil v tom, prepsat ten random do pomocne funkce
int i = 0; while (i < pocet) nums[i++] = random(); // return rand.Next(min, max);
Totiz, jako, kdyz se to prepise do pomocne funkce, procesor by na to mohl
nahodit nejake optimalizace a vygenerovat si treba nekolik cisel dopredu.
A ten random ti trva 500 ms pro 1.000 nebo 10.000 cisel? Za stejny cas mam pole
vygenerovane pres random a serazene pro n = 1.000.000 Viz, ten muj javascriptovy program.
Ten cas by se ti mel pohybovat tak do 30 ms, myslim.
Na druhou stranu, jestli pouzivas c#, tak to mozne je. Minule jsem delal nejake
pokusy v php, ktere je alternativou c# a byl dost problem pracovat s pixel mapou
obrazku. Co jsem mel v js blik, tak to tam trvalo i 5s.
A zajimalo by mne, kdyby se misto toho sortu pouzil jiny algoritmus. Treba, takovy counting-sort je jasne nejrychlejsi, ale zere spousttu pameti.
Fodo:29.3.2022 14:15
Jasne, teraz ked to pozeram tak je u mne určite niečo zle, nekontroloval som si to všetko to robím trochu v zhone, lebo mám málo času, ale ak sa mi podarí (nájdem si nejaký čas) tak sa na to ešte pozriem.
DarkCoder:29.3.2022 14:26
#include <stdio.h>
#define pocet 10
int get10(void);
int main(void) {
int nums[pocet];
for (int i = 0; i < pocet; i++) nums[i] = 10;
for (int i = 0; i < pocet;) nums[i++] = 10;
int i = 0; for (; i < pocet;) nums[i++] = 10;
int j = 0; while (j < pocet) nums[j++] = 10;
int k = 0; while (k < pocet) nums[k++] = get10();
return 0;
}
int get10(void) { return 10; }
; 10 : for (int i = 0; i < pocet; i++) nums[i] = 10;
mov DWORD PTR i$4[rbp], 0
jmp SHORT $LN4@main
$LN2@main:
mov eax, DWORD PTR i$4[rbp]
inc eax
mov DWORD PTR i$4[rbp], eax
$LN4@main:
cmp DWORD PTR i$4[rbp], 10
jge SHORT $LN3@main
movsxd rax, DWORD PTR i$4[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
jmp SHORT $LN2@main
$LN3@main:
; 11 : for (int i = 0; i < pocet;) nums[i++] = 10;
mov DWORD PTR i$5[rbp], 0
$LN5@main:
cmp DWORD PTR i$5[rbp], 10
jge SHORT $LN6@main
movsxd rax, DWORD PTR i$5[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
mov eax, DWORD PTR i$5[rbp]
inc eax
mov DWORD PTR i$5[rbp], eax
jmp SHORT $LN5@main
$LN6@main:
; 12 : int i = 0; for (; i < pocet;) nums[i++] = 10;
mov DWORD PTR i$[rbp], 0
$LN8@main:
cmp DWORD PTR i$[rbp], 10
jge SHORT $LN9@main
movsxd rax, DWORD PTR i$[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
mov eax, DWORD PTR i$[rbp]
inc eax
mov DWORD PTR i$[rbp], eax
jmp SHORT $LN8@main
$LN9@main:
; 13 : int j = 0; while (j < pocet) nums[j++] = 10;
mov DWORD PTR j$[rbp], 0
$LN11@main:
cmp DWORD PTR j$[rbp], 10
jge SHORT $LN12@main
movsxd rax, DWORD PTR j$[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
mov eax, DWORD PTR j$[rbp]
inc eax
mov DWORD PTR j$[rbp], eax
jmp SHORT $LN11@main
$LN12@main:
; 14 : int k = 0; while (k < pocet) nums[k++] = get10();
mov DWORD PTR k$[rbp], 0
$LN13@main:
cmp DWORD PTR k$[rbp], 10
jge SHORT $LN14@main
call get10
movsxd rcx, DWORD PTR k$[rbp]
mov DWORD PTR nums$[rbp+rcx*4], eax
mov eax, DWORD PTR k$[rbp]
inc eax
mov DWORD PTR k$[rbp], eax
jmp SHORT $LN13@main
$LN14@main:
Můžeš porovnat 😀
DarkCoder:29.3.2022 15:02
Když už jsme u toho srovnávání:
#include <stdio.h>
int main(void) {
int nums[10];
int i = 0;
nums[i] = 10;
i++;
int j = 0;
nums[j++] = 10;
return 0;
}
; 6 : int i = 0;
mov DWORD PTR i$[rbp], 0
; 7 : nums[i] = 10;
movsxd rax, DWORD PTR i$[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
; 8 : i++;
mov eax, DWORD PTR i$[rbp]
inc eax
mov DWORD PTR i$[rbp], eax
; 9 :
; 10 : int j = 0;
mov DWORD PTR j$[rbp], 0
; 11 : nums[j++] = 10;
movsxd rax, DWORD PTR j$[rbp]
mov DWORD PTR nums$[rbp+rax*4], 10
mov eax, DWORD PTR j$[rbp]
inc eax
mov DWORD PTR j$[rbp], eax
Žádný rozdíl, první verze delší ale čitelnější.
Fodo:29.3.2022 18:14
Problém bol že som nezresetoval stopky a tým pádom len pripočítaval čas, tu je výsledok po oprave:
VERZIA 1:
00:00:00.0078662
00:00:00.0077954
00:00:00.0015699
00:00:00.0061050
00:00:00.0040164
00:00:00.0041967
00:00:00.0042749
00:00:00.0094195
00:00:00.0048351
00:00:00.0059976
00:00:00.0128547
00:00:00.0426343
00:00:00.0085013
00:00:00.0049451
00:00:00.0085954
Priemer = 0,00835046875
VERZIA 1pole:
00:00:00.0151059
00:00:00.0027973
00:00:00.0017352
00:00:00.0111269
00:00:00.0157554
00:00:00.0023116
00:00:00.0029633
00:00:00.0023213
00:00:00.0072616
00:00:00.0030992
00:00:00.0087300
00:00:00.0160025
00:00:00.0041424
00:00:00.0204980
00:00:00.0037693
Priemer = 0,00735124375
VERZIA 1 (konštanta):
00:00:00.0149746
00:00:00.0023501
00:00:00.0018232
00:00:00.0044168
00:00:00.0066247
00:00:00.0019814
00:00:00.0026242
00:00:00.0083447
00:00:00.0043781
00:00:00.0026512
00:00:00.0030709
00:00:00.0056510
00:00:00.0135732
00:00:00.0165445
00:00:00.0029230
Priemer = 0,005745725
VERZIA 2:
00:00:00.0075594
00:00:00.0030023
00:00:00.0017847
00:00:00.0075826
00:00:00.0048605
00:00:00.0074082
00:00:00.0091671
00:00:00.0009517
00:00:00.0055804
00:00:00.0111440
00:00:00.0028649
00:00:00.0057456
00:00:00.0031519
00:00:00.0104646
00:00:00.0091387
Priemer = 0,0056504125
VERZIA 4:
00:00:00.1776225
00:00:00.0272220
00:00:00.0195320
00:00:00.0405773
00:00:00.0350710
00:00:00.0759398
00:00:00.0465790
00:00:00.0013034
00:00:00.0373368
00:00:00.0203371
00:00:00.1186381
00:00:00.0485791
00:00:00.0727754
00:00:00.1267088
00:00:00.0275542
Priemer = 0,05473603125
VERZIA 5:
00:00:00.0294297
00:00:00.0319403
00:00:00.0172996
00:00:00.0318518
00:00:00.0474263
00:00:00.0332643
00:00:00.0340469
00:00:00.0154010
00:00:00.0248230
00:00:00.0010695
00:00:00.0650704
00:00:00.0356247
00:00:00.0353870
00:00:00.0404237
00:00:00.0368345
Priemer = 0,02999329375
VERZIA 6:
00:00:00.0202313
00:00:00.0129938
00:00:00.0401931
00:00:00.0503583
00:00:00.0498097
00:00:00.0251319
00:00:00.0406313
00:00:00.0194508
00:00:00.0272151
00:00:00.0465620
00:00:00.0544188
00:00:00.0389841
00:00:00.0310209
00:00:00.0175428
00:00:00.0337978
Priemer = 0,03177135625
DarkCoder:29.3.2022 19:02
Přijdou mi ty rozdíly mezi verzemi minimální a hodnoty přiliž velké pro 1000 hodů v rozsahu 1-20.
Jaká hodnota sw se vypíše ve výše uvedeném překladači pro tento program:
using System;
using System.Diagnostics;
public class Program {
public static void Main() {
int min = 1;
int max = 20;
int throws = 1000;
int maxplus = max + 1;
int rep = 50;
int[] nums = new int[throws];
int[] vals = new int[maxplus];
Stopwatch sw = new Stopwatch();
Random rand = new Random();
sw.Start();
for(int j = 0; j < rep; j++){
for (int i = min; i <= max; i++) vals[i] = 0;
for (int i = 0; i < throws; i++) nums[i] = rand.Next(min, maxplus);
for (int i = 0; i < throws; i++) vals[nums[i]]++;
for (int i = min; i <= max; i++){
if(vals[i] > 1) Console.WriteLine("Cislo {0} se opakuje {1}x", i, vals[i]);
}
}
sw.Stop();
Console.WriteLine("sw = {0}", sw.Elapsed / rep);
}
}
Potom som ešte spravil jeden pokus, prerobil som výpočet priemeru takto:
Console.WriteLine("sw = {0}", sw.Elapsed.TotalSeconds / rep);
pretože mi to v tom tvojom tvare nefungovalo cez Visual Studio. A spustil som v NTB a aj v online compileri a:
//NTB:
sw = 0,020148322
//Online:
sw = 0.000279046
Takže problém asi bude v NTB, ak aj toto vylúčiš, tak bude problém asi
medzi stoličkou a NTB
DarkCoder:29.3.2022 20:27
Vypadá to nejspíše na to že to způsobuje rozlišení časovače. Na laptopech, používá-li se pro měření časového intervalu funkce DateTime, může dojít k nepřesnosti 10ms+. Tato funkce se volá 2x, což může způsobit nepřesnost ve výši 20ms. Na těchto krátkých časových měření může dojít k právě tomuto chování. Všude preferují pro měření časových úseků používat Stopwatch před DateTime. Můžeš si chování vyzkoušet na změření samotného výpisu na obrazovku.
Co se týká výsledku měření času programu, ano, to je správná hodnota. Čas se pohybuje ve stovkách mikrosekund.
Cili, v zasade to vygeneruje stejny asm kod, jen jsou nektere instrukce v jinem poradi. Takze by to melo asi dosahovat stejne casy. Leda ma procesor nejake optimalizace pro konkretni posloupnosti instrukci.
Spis mne jako napadlo, ze kdyz se deklaruje promena predem, tak by cyklus
mohl bezet rychleji.
Kdyz se udela i++ mimo deklaraci cyklu, tak se treba ztrati nejaky radek.
Jeste by teoreticky slo zkusit dat misto i++, ++i. To by se melo chovat
rychleji. Ale, to ma asi spis vyznam uvnitr cyklu. V zahlavi to asi
prekonvertuje stejne.
Samozrejme, kazda z tech uprav by mela byt v podstate bezvyznamna, max 0.01%
nebo tak neco Spis jsem byl
zvedavy. Ja treba stridam obcas pouziti while a for, nektere kody jsou
prehlednejsi s while.
DarkCoder:30.3.2022 10:26
Ano, pokud nemá procesor nějaké své vnitřní optimalizace, tak budou dosažené časy stejné. Samozřejmě to také může být pouze hrubý nástřel a nakonec si nějakou tu optimalizaci najde. U některých zápisů ale skutečně může dojít k navýšení výkonu, respektive některý zápis bude rychlejší. Např. indexace a ukazatelová aritmetika.
Příklad ukazuje, jak 4 způsoby uložit hodnotu 10 do druhého prvku pole.
#include <stdio.h>
int main(void) {
int nums[10], *p = nums;
nums[1] = 10;
*(nums + 1) = 10;
p[1] = 10;
*(p + 1) = 10;
return 0;
}
; 4 : int nums[10], *p = nums;
lea rax, QWORD PTR nums$[rbp]
mov QWORD PTR p$[rbp], rax
; 5 :
; 6 : nums[1] = 10;
mov eax, 4
imul rax, rax, 1
mov DWORD PTR nums$[rbp+rax], 10
; 7 : *(nums + 1) = 10;
mov DWORD PTR nums$[rbp+4], 10
; 8 : p[1] = 10;
mov eax, 4
imul rax, rax, 1
mov rcx, QWORD PTR p$[rbp]
mov DWORD PTR [rcx+rax], 10
; 9 : *(p + 1) = 10;
mov rax, QWORD PTR p$[rbp]
mov DWORD PTR [rax+4], 10
Výsledky jsou jistě zajímavé.
Nicméně je to jiný způsob zápisu než to, o čem byla řeč. Není na škodu a chybou rozepisovat kód do více řádků, nejen z důvodu čitelnosti, ale také z důvodu vedlejších efektů, které při zkrácených zápisech mohou nastat.
Ano, určitý rozdíl rychlosti v deklaraci proměnné je. Je rozdíl např. deklarovat pole staticky nebo až za běhu.
V programech se často používají cykly, ty mají své iterační proměnné, které se lokálně deklarují. Z hlediska rychlosti se nabízí možnost deklarovat proměnnou vně cyklu a používat ji pro více cyklů a vyhnout se tak nové deklaraci a zkrátit tak čas. To už jsou extrémní případy které mohou mít dopad na přehlednost programu, bezpečnost a rychlost psát kód. Ten drobný nárust rychlosti v tomto případě za to nestojí.
Co se týká inkrementace v prefixové podobě ++i proti postfixové podobě i++, pro vestavěné typy zde není žádný rozdíl. Drobný rozdíl je u tzv. compound typů, tedy typů co vytváří programátor, struktury, objekty, unie, tam je prefixová podoba zápisu nepatrně rychlejší.
Každý cyklus se pro něco hodí lépe pro něco méně. Důvodem je přehlednost a v určitých případech funkčnost, to zejména u rozdílu mezi do-while a while cykly. Ve skutečnosti jsou to jen varianty pro lidi, neboť překladače každý while a do-while cyklus zpracovávají do podoby for cyklu. Proto když jsem posílal asm výstup while cykly, nebyl tam žádný rozdíl. Já osobně preferuji for cyklus, neboť mohu dost zápisů napsat do jednoho řádku díky tomu, že inkrementační část je na stejném řádku na konci v těle cyklu.
Optimalizace je složitá, jsou místa kde to má smysl, třeba tvé
porovnání všech možných algoritmů, tam se dá ušetřit spoustu času. A
pak tyto drobné zápisy, které se hodí do extrémních situací a zabývat se
jimi může zabrat příliš mnoho času. Ale je to bezesporu zajímavé.
Takovy krasny priklad, kdy se mi vic libi while je treba opet v tom
serazovani.
Pred cyklem se generuje nejake i, ktere je nebo neni vetsi nez i_end.
Zkopiruje to zustatek v poli arr1 do arr2
while (i<i_end) arr2[k++] = arr1[i++];
// pres for je to takove divne :)
// for (; i<i_end; i++, k++) arr2[k] = arr1[i];
// for (; i<i_end; i++) arr2[k++] = arr1[i];
// for (; i<i_end; ) arr2[k++] = arr1[i++];
DarkCoder:30.3.2022 12:46
A já bych zrovna dal přednost tomu prvnímu for, abych se vyhnul případným možným vedlejším efektům, které mohou nastat v přiřazovacích příkazech.. Navíc, krásná ukázka použití operátoru čárka.
ja, treba, kdyz bych pouzil for, tak zasadne to druhe
for (; i<i_end; i++) arr2[k++] = arr1[i];
for (; i<i_end; i++) {arr2[k] = arr1[i]; k++; }
Oddelil bych cast, ktera tvori cyklus od toho ostatniho.
DarkCoder:30.3.2022 14:19
Možností je mnoho. Je dobré udržovat věci pospolu, které mezi sebou souvisí.
for (; i<i_end; i++, k++) arr2[k] = arr1[i];
for (; i<i_end; ) arr2[k] = arr1[i], i++, k++;
for (; i<i_end; ) {
arr2[k] = arr1[i];
i++;
k++;
}
for (; i<i_end; ) {
arr2[k] = arr1[i];
i++, k++;
}
Nechť si každý vybere co mu vyhovuje..
Zobrazeno 50 zpráv z 50.