Wiem, że instancyjne tablice typów wartości w języku C # są automatycznie zapełniane domyślną wartością typu (np. False dla bool, 0 dla int itp.).
Czy istnieje sposób na automatyczne wypełnienie tablicy wartością początkową, która nie jest domyślna? Zarówno podczas tworzenia, jak i później wbudowanej metody (np. Java Arrays.fill () )? Powiedzmy, że chciałem tablicę boolowską, która domyślnie była prawdziwa, a nie fałszywa. Czy istnieje wbudowany sposób, aby to zrobić, czy wystarczy iterować po tablicy za pomocą pętli for?
// Example pseudo-code:
bool[] abValues = new[1000000];
Array.Populate(abValues, true);
// Currently how I'm handling this:
bool[] abValues = new[1000000];
for (int i = 0; i < 1000000; i++)
{
abValues[i] = true;
}
Konieczność iteracji po tablicy i „resetowania” każdej wartości do wartości true wydaje się nieefektywna. Czy jest coś takiego? Może przerzucając wszystkie wartości?
Po wpisaniu tego pytania i przemyśleniu go, domyślam się, że wartości domyślne są po prostu wynikiem tego, jak C # radzi sobie z alokacją pamięci tych obiektów za kulisami, więc wyobrażam sobie, że prawdopodobnie nie jest to możliwe. Ale nadal chciałbym wiedzieć na pewno!
źródło
Odpowiedzi:
Nie znam metody ramowej, ale możesz napisać szybkiego pomocnika, który zrobi to za Ciebie.
źródło
int[] arr = new int[16].Populate(-1);
void
doT[]
, a następnie można zrobićvar a = new int[100].Polupate(1)
źródło
Enumerable.ToArray
nie zna wielkości sekwencji, którą można wyliczyć, więc musi zgadnąć, jaki jest rozmiar tablicy. Oznacza to, że otrzymasz alokacje tablic za każdym razemToArray
, gdy bufor zostanie przekroczony, oraz jeszcze jedną alokację na końcu dla wykończenia. Istnieje również narzut związany z wymiennym obiektem.Utwórz nową tablicę z tysiącem
true
wartości:Podobnie możesz generować sekwencje całkowite:
źródło
W przypadku dużych tablic lub tablic o zmiennej wielkości powinieneś prawdopodobnie użyć:
W przypadku małej tablicy można użyć składni inicjowania kolekcji w C # 3:
Zaletą składni inicjalizacji kolekcji jest to, że nie trzeba używać tej samej wartości w każdym gnieździe i można użyć wyrażeń lub funkcji do zainicjowania grona. Ponadto uważam, że unikasz kosztów inicjowania gniazda tablicy na wartość domyślną. Na przykład:
źródło
float[] AlzCalDefault = new float[] {(float) 0.5, 18, 500, 1, 0};
bool[] vals = { false, true, false, !(a || b) && c, SomeBoolMethod() };
Jeśli twoja tablica jest tak duża, powinieneś użyć BitArray. Używa 1 bitu na każdy bool zamiast bajtu (jak w tablicy boolów), a także możesz ustawić wszystkie bity na prawdziwe za pomocą operatorów bitów. Lub po prostu zainicjuj na true. Jeśli musisz to zrobić tylko raz, będzie to kosztować więcej.
źródło
Możesz używać
Array.Fill
w .NET Core 2.0+ i .NET Standard 2.1+.źródło
niestety nie sądzę, że istnieje bezpośredni sposób, ale myślę, że możesz napisać metodę rozszerzenia dla klasy tablicowej, aby to zrobić
źródło
Po trochę więcej googlingu i przeczytaniu znalazłem to:
Co jest z pewnością bliższe temu, czego szukam. Ale nie jestem pewien, czy jest to lepsze niż iteracja oryginalnej tablicy w pętli for i zmiana wartości. W rzeczywistości po szybkim teście wydaje się wolniejszy około 5 razy, więc nie jest to dobre rozwiązanie!
źródło
Co z implementacją równoległą
Podczas inicjalizacji tablicy nie widać mocy tego kodu, ale myślę, że zdecydowanie powinieneś zapomnieć o „czystym” dla.
źródło
Poniższy kod łączy prostą iterację dla małych kopii i Array.Copy dla dużych kopii
Testy porównawcze dla różnych długości tablicy przy użyciu tablicy int [] to:
Pierwsze kolumny to rozmiar tablicy, a następnie czas kopiowania przy użyciu prostej iteracji (implementacja @JaredPared). Czas tej metody jest potem. Są to testy porównawcze przy użyciu tablicy o strukturze czterech liczb całkowitych
źródło
Lub ... możesz po prostu użyć odwróconej logiki. Niech
false
oznaczatrue
i na odwrót.Próbka kodu
źródło
bool[] isVisible
tegobool[] isHidden
to również działa ... ale może być niepotrzebne
źródło
Wiele przedstawionych tutaj odpowiedzi sprowadza się do pętli, która inicjuje tablicę jeden element na raz, co nie wykorzystuje instrukcji procesora zaprojektowanych do działania na bloku pamięci jednocześnie.
.Net Standard 2.1 (w wersji zapoznawczej w tym tekście ) udostępnia Array.Fill () , który nadaje się do wysokowydajnej implementacji w bibliotece wykonawczej (choć na razie .NET Core nie wydaje się korzystać z tej możliwości) .
W przypadku wcześniejszych platform następująca metoda rozszerzenia przewyższa trywialną pętlę o znaczny margines, gdy rozmiar tablicy jest znaczący. Stworzyłem go, gdy moje rozwiązanie problemu z kodem online było o około 20% wyższe niż przydzielony budżet czasowy. Skróciło czas działania o około 70%. W tym przypadku wypełnienie tablicy zostało wykonane w innej pętli. BLOCK_SIZE był nastawiony raczej na przeczucie niż na eksperyment. Możliwe są pewne optymalizacje (np. Kopiowanie wszystkich bajtów już ustawionych na pożądaną wartość zamiast bloku o stałym rozmiarze).
źródło
Jeśli planujesz ustawić tylko kilka wartości w tablicy, ale chcesz uzyskać (niestandardową) wartość domyślną przez większość czasu, możesz spróbować czegoś takiego:
Prawdopodobnie będziesz musiał zaimplementować inne interfejsy, aby były użyteczne, takie jak te w samej tablicy .
źródło
Nie ma możliwości ustawienia wszystkich elementów w tablicy jako pojedynczej operacji, PONIEWAŻ, ta wartość jest wartością domyślną typów elementów.
Na przykład, jeśli jest to tablica liczb całkowitych, możesz ustawić je wszystkie na zero za pomocą jednej operacji, na przykład:
Array.Clear(...)
źródło
Zdaję sobie sprawę, że spóźniłem się na przyjęcie, ale oto pomysł. Napisz opakowanie, które ma operatory konwersji do i od wartości opakowanej, aby można było jej użyć jako stand-in dla typu opakowanego. Zostało to zainspirowane głupią odpowiedzią @ l33t.
Po pierwsze (pochodzące z C ++) zdałem sobie sprawę, że w języku C # domyślny ctor nie jest wywoływany, gdy budowane są elementy tablicy. Zamiast tego - nawet w obecności domyślnego konstruktora zdefiniowanego przez użytkownika! - wszystkie elementy tablicy są inicjowane zerem. To mnie zaskoczyło.
Tak więc klasa opakowania, która po prostu zapewnia domyślny ctor z pożądaną wartością, działałaby dla tablic w C ++, ale nie w C #. Obejściem tego problemu jest umożliwienie odwzorowania typu opakowania 0 na pożądaną wartość początkową po konwersji. W ten sposób wszystkie zainicjowane wartości zerowe wydają się być inicjowane z ziarnem dla wszystkich praktycznych celów:
Ten wzór dotyczy wszystkich typów wartości. Można na przykład odwzorować 0 do 4 dla ints, jeśli pożądana jest inicjalizacja z 4 itd.
Chciałbym zrobić szablon tego, co byłoby możliwe w C ++, podając wartość początkową jako parametr szablonu, ale rozumiem, że nie jest to możliwe w języku C #. A może coś mi brakuje? (Oczywiście w mapowaniu w C ++ wcale nie jest konieczne, ponieważ można podać domyślny ctor, który będzie wywoływany dla elementów tablicy).
FWIW, oto odpowiednik C ++: https://ideone.com/wG8yEh .
źródło
Jeśli możesz odwrócić logikę, możesz użyć
Array.Clear()
metody, aby ustawić tablicę boolowską na false.źródło
Jeśli korzystasz z .NET Core, .NET Standard> = 2.1 lub zależysz od pakietu System.Memory, możesz również użyć
Span<T>.Fill()
metody:https://dotnetfiddle.net/UsJ9bu
źródło
Oto kolejna wersja dla nas Użytkownicy platformy porzuceni przez Microsoft. Jest 4 razy
Array.Clear
szybszy i szybszy niż rozwiązanie Panos Theof i równoległe rozwiązanie Erica J i Petara Pietrowa - do dwóch razy szybsze w przypadku dużych tablic.Najpierw chcę przedstawić przodka funkcji, ponieważ ułatwia to zrozumienie kodu. Pod względem wydajności jest to prawie na równi z kodem Panos Theof, a dla niektórych rzeczy, które mogą już wystarczyć:
Jak widać, jest to oparte na wielokrotnym podwajaniu już zainicjowanej części. Jest to proste i wydajne, ale działa w porównaniu z nowoczesnymi architekturami pamięci. Stąd narodziła się wersja, która wykorzystuje podwajanie tylko do utworzenia przyjaznego dla bufora bloku początkowego, który jest następnie wysadzany iteracyjnie nad obszarem docelowym:
Uwaga: wcześniejszy kod potrzebny
(count + 1) >> 1
jako limit dla pętli dublującej, aby upewnić się, że w końcowej operacji kopiowania jest wystarczająca ilość paszy, aby pokryć wszystko, co pozostało. Nie byłoby tak w przypadku nieparzystych zliczeń, gdybycount >> 1
zamiast nich zastosowano. W obecnej wersji nie ma to znaczenia, ponieważ pętla kopiowania liniowego wykryje luz.Rozmiar komórki tablicowej musi zostać przekazany jako parametr, ponieważ - umysł zadziwia - nie można używać rodzajów ogólnych,
sizeof
chyba że zastosują ograniczenie (unmanaged
), które może, ale nie musi, stać się dostępne w przyszłości. Błędne szacunki nie są wielkim problemem, ale wydajność jest najlepsza, jeśli wartość jest dokładna, z następujących powodów:Niedocenianie rozmiaru elementu może prowadzić do rozmiarów bloków większych niż połowa pamięci podręcznej L1, a tym samym zwiększa prawdopodobieństwo eksmisji źródłowych danych z L1 i konieczności ponownego pobrania z wolniejszych poziomów pamięci podręcznej.
Przeszacowanie rozmiaru elementu powoduje niepełne wykorzystanie pamięci podręcznej L1 procesora, co oznacza, że pętla kopiowania bloku liniowego jest wykonywana częściej niż przy optymalnym wykorzystaniu. W ten sposób powstaje więcej narzutu związanego z ustaloną pętlą / wywołaniem niż jest to absolutnie konieczne.
Oto test porównawczy mojego kodu
Array.Clear
i pozostałe trzy wspomniane wcześniej rozwiązania. Czasy dotyczą wypełniania tablic liczb całkowitych (Int32[]
) o podanych rozmiarach. W celu zmniejszenia zmienności spowodowanej błędami pamięci podręcznej itp. Każdy test był wykonywany dwukrotnie, z powrotem do tyłu, a czasy były brane pod uwagę przy drugim wykonaniu.Jeśli wydajność tego kodu nie będzie wystarczająca, obiecującą ścieżką będzie równoległa pętla kopiowania liniowego (wszystkie wątki będą korzystały z tego samego bloku źródłowego) lub nasz stary dobry przyjaciel P / Invoke.
Uwaga: czyszczenie i wypełnianie bloków jest zwykle wykonywane przez procedury wykonawcze, które rozgałęziają się do wysoce wyspecjalizowanego kodu przy użyciu instrukcji MMX / SSE i tak dalej, więc w każdym przyzwoitym środowisku można po prostu nazwać odpowiedni moralny ekwiwalent
std::memset
i być pewnym poziomu profesjonalnej wydajności. IOW, zgodnie z prawem funkcja bibliotekiArray.Clear
powinna pozostawić wszystkie nasze ręcznie zwijane wersje w pył. Fakt, że jest na odwrót, pokazuje, jak dalece rzeczy są naprawdę zepsute. To samo dotyczy konieczności rzucania własnymFill<>
, ponieważ wciąż jest tylko w Core i Standard, ale nie w Framework. .NET istnieje już od prawie dwudziestu lat i nadal musimy P / Invoke w lewo i prawo, aby uzyskać najbardziej podstawowe rzeczy lub rzucić własne ...źródło
Istnieje kilka odpowiedzi na to (zduplikowane?) Pytanie: Jaki jest odpowiednik memset w C #?
Ktoś przetestował alternatywy (obejmował niebezpieczną wersję, ale nie próbował
memset
): http://techmikael.blogspot.co.uk/2009/12/filling-array-with-default-value.htmlźródło
Oto kolejna ocena, z
System.Collections.BitArray
którą ma takiego konstruktora.lub
źródło
Stwórz prywatną klasę w środku, w której tworzysz tablicę, a do tego masz getter i setter. Jeśli nie potrzebujesz, aby każda pozycja w tablicy była czymś wyjątkowym, na przykład losowym, użyj int? jako tablicę, a następnie pobierz, jeśli pozycja jest równa null, wypełnij tę pozycję i zwróć nową losową wartość.
Albo użyj
Jako seter.
źródło
źródło