Zestaw (abstrakcyjny typ danych) - Set (abstract data type)
W informatyce , wykorzystując zestaw jest abstrakcyjny typ danych , które można przechowywać unikalne wartości, bez żadnego konkretnego celu . Jest to komputerowa implementacja matematycznego pojęcia zbioru skończonego . W przeciwieństwie do większości innych typów kolekcji , zamiast pobierania określonego elementu z zestawu, zazwyczaj testuje się wartość członkostwa w zestawie.
Niektóre struktury danych zestawu są przeznaczone dla zestawów statycznych lub zamrożonych , które nie zmieniają się po ich skonstruowaniu. Zestawy statyczne umożliwiają tylko operacje zapytań na ich elementach — takie jak sprawdzanie, czy dana wartość znajduje się w zestawie lub wyliczanie wartości w dowolnej kolejności. Inne warianty, zwane zestawami dynamicznymi lub zmiennymi , pozwalają również na wstawianie i usuwanie elementów ze zbioru.
Multiset to specjalny rodzaj zestawu, w którym element może dowiedzieć się kilka razy.
Teoria typów
W teorii typów zbiory są na ogół utożsamiane z ich funkcją wskaźnika ( funkcją charakterystyczną): odpowiednio zbiór wartości typu może być oznaczony przez lub . (Podtypy i podzbiory mogą być modelowane przez typy uściślenia , a zbiory ilorazowe można zastąpić setoidami .) Charakterystyczną funkcję zbioru definiuje się jako:
Teoretycznie wiele innych abstrakcyjnych struktur danych można postrzegać jako struktury zbioru z dodatkowymi operacjami i/lub dodatkowymi aksjomatami nałożonymi na standardowe operacje. Na przykład abstrakcyjna sterta może być postrzegana jako struktura zestawu z operacją zwracającą element o najmniejszej wartości.
min(S)
Operacje
Operacje na zbiorach podstawowych
Można zdefiniować działania algebry zbiorów :
-
union(S,T)
: zwraca sumę zbiorów S i T . -
intersection(S,T)
: zwraca część wspólną zbiorów S i T . -
difference(S,T)
: zwraca różnicę zbiorów S i T . -
subset(S,T)
: predykat sprawdzający, czy zbiór S jest podzbiorem zbioru T .
Zestawy statyczne
Typowe operacje, które mogą być zapewnione przez statyczną strukturę zbioru S to:
-
is_element_of(x,S)
: sprawdza, czy wartość x znajduje się w zbiorze S . -
is_empty(S)
: sprawdza, czy zbiór S jest pusty. -
size(S)
lub : zwraca liczbę elementów w S .cardinality(S)
-
iterate(S)
: zwraca funkcję, która zwraca jeszcze jedną wartość S przy każdym wywołaniu, w dowolnej kolejności. -
enumerate(S)
: zwraca listę zawierającą elementy S w dowolnej kolejności. -
build(x1,x2,…,xn,)
: tworzy strukturę zbioru z wartościami x 1 , x 2 ,..., x n . -
create_from(collection)
: tworzy nową strukturę zbioru zawierającą wszystkie elementy danej kolekcji lub wszystkie elementy zwrócone przez dany iterator .
Zestawy dynamiczne
Struktury zestawów dynamicznych zazwyczaj dodają:
-
create()
: tworzy nową, początkowo pustą strukturę zestawu.-
create_with_capacity(n)
: tworzy nową strukturę zestawu, początkowo pustą, ale zdolną pomieścić do n elementów.
-
-
add(S,x)
: dodaje element x do S , jeśli jeszcze go nie ma. -
remove(S, x)
: usuwa element x z S , jeśli jest obecny. -
capacity(S)
: zwraca maksymalną liczbę wartości, które S może przechowywać.
Niektóre struktury zestawów mogą zezwalać tylko na niektóre z tych operacji. Koszt każdej operacji będzie zależał od implementacji, a być może także od poszczególnych wartości zapisanych w zestawie i kolejności ich wstawiania.
Dodatkowe operacje
Istnieje wiele innych operacji, które można (w zasadzie) zdefiniować w kategoriach powyższych, takich jak:
-
pop(S)
: zwraca dowolny element S , usuwając go z S . -
pick(S)
: zwraca dowolny element S . Funkcjonalnie mutatorpop
można interpretować jako parę selektorów,(pick, rest),
gdzierest
zwraca zestaw składający się ze wszystkich elementów z wyjątkiem dowolnego elementu. Można interpretować w kategoriachiterate
. -
map(F,S)
: zwraca zestaw odrębnych wartości wynikających z zastosowania funkcji F do każdego elementu S . -
filter(P,S)
: zwraca podzbiór zawierający wszystkie elementy S, które spełniają dany predykat P . -
fold(A0,F,S)
: zwraca wartość A | S | po zastosowaniu dla każdego elementu e z S, dla jakiejś operacji binarnej F. F musi być asocjacyjne i przemienne, aby było to dobrze zdefiniowane.Ai+1 := F(Ai, e)
-
clear(S)
: usuń wszystkie elementy S . -
equal(S1', S2')
: sprawdza, czy dwa podane zestawy są równe (tzn. zawierają wszystkie i tylko te same elementy). -
hash(S)
: zwraca wartość skrótu dla statycznego zbioru S taką, że jeśli wtedyequal(S1, S2)
hash(S1) = hash(S2)
Inne operacje można zdefiniować dla zestawów z elementami specjalnego typu:
-
sum(S)
: zwraca sumę wszystkich elementów S dla pewnej definicji "sumy". Na przykład nad liczbami całkowitymi lub rzeczywistymi może być zdefiniowany jako .fold(0, add, S)
-
collapse(S)
: dany zestaw zestawów, zwróć unię. Na przykładcollapse({{1}, {2, 3}}) == {1, 2, 3}
. Można uznać za rodzajsum
. -
flatten(S)
: dany zestaw składający się z zestawów i elementów niepodzielnych (elementów, które nie są zestawami), zwraca zestaw, którego elementy są niepodzielnymi elementami oryginalnego zestawu najwyższego poziomu lub elementami zestawów, które zawiera. Innymi słowy, usuń poziom zagnieżdżenia – jak,collapse,
ale zezwalaj na atomy. Można to zrobić jednorazowo lub rekursywnie spłaszczając, aby uzyskać zestaw tylko elementów atomowych. Na przykładflatten({1, {2, 3}}) == {1, 2, 3}
. -
nearest(S,x)
: zwraca element S, który jest najbliższy wartości x (według jakiejś metryki ). -
min(S)
, : zwraca minimalny/maksymalny element S .max(S)
Realizacje
Zestawy mogą być implementowane przy użyciu różnych struktur danych , które zapewniają różne kompromisy czasowe i przestrzenne dla różnych operacji. Niektóre implementacje mają na celu poprawę wydajności bardzo wyspecjalizowanych operacji, takich jak nearest
lub union
. Implementacje opisane jako „ogólnego użytku” zazwyczaj starają się optymalizować element_of
, add
oraz delete
operacje. Prostą implementacją jest użycie list , ignorując kolejność elementów i uważając, aby uniknąć powtarzających się wartości. Jest to proste, ale nieefektywne, ponieważ operacje takie jak przynależność do zestawu lub usuwanie elementów to O ( n ), ponieważ wymagają one skanowania całej listy. Zestawy są często implementowane przy użyciu bardziej wydajnych struktur danych, w szczególności różnych odmian drzew , prób lub tablic mieszających .
Ponieważ zbiory można interpretować jako rodzaj mapy (przez funkcję indykatora), zbiory są zwykle implementowane w taki sam sposób jak mapy (częściowe) ( tablice asocjacyjne ) – w tym przypadku, gdy wartość każdej pary klucz-wartość ma typ jednostki lub wartość wskaźnikowa (np. 1) – mianowicie samobalansujące drzewo binarne wyszukiwania dla posortowanych zbiorów (które ma O(log n) dla większości operacji) lub tablica haszująca dla nieposortowanych zbiorów (która ma O(1) średni przypadek, ale O(n) najgorszy przypadek, dla większości operacji). Posortowana liniowa tablica mieszająca może służyć do zapewnienia deterministycznie uporządkowanych zestawów.
Co więcej, w językach, które obsługują mapy, ale nie zestawy, zestawy można zaimplementować jako mapy. Na przykład popularnym idiomem programistycznym w Perlu, który konwertuje tablicę na hash, której wartości są wartością wartowniczą 1, do użycia jako zestaw, to:
my %elements = map { $_ => 1 } @elements;
Inne popularne metody to tablice . W szczególnym podzbiorze liczby całkowite 1 .. n może być skutecznie zaimplementowane w postaci n -bitowych tablica bitów , które także obsługuje bardzo wydajnego działania związków i przecięcia. Mapa Blooma implementuje zestaw probabilistycznie, używając bardzo zwartej reprezentacji, ale ryzykując małą szansę fałszywych trafień w zapytaniach.
Wartość logiczna zestaw działań może być realizowana w warunkach bardziej elementarnych operacji ( pop
, clear
i add
), lecz wyspecjalizowane algorytmy mogą dawać niższy asymptotyczne granic czasowych. Jeśli zbiory są zaimplementowane jako posortowane listy, naiwny algorytm for zajmie czas proporcjonalny do długości m z S razy długość n z T ; natomiast wariant algorytmu scalania list wykona zadanie w czasie proporcjonalnym do m + n . Ponadto istnieją wyspecjalizowane struktury danych (takie jak union-find data structure ), które są zoptymalizowane pod kątem jednej lub więcej z tych operacji kosztem innych.
union(S,T)
Wsparcie językowe
Jednym z najwcześniejszych języków obsługujących zestawy był Pascal ; wiele języków teraz go zawiera, czy to w języku podstawowym, czy w standardowej bibliotece .
- W C ++ , Template Library Standardowy (STL) zapewnia
set
klasę szablonu, który jest zazwyczaj realizowane za pomocą drzewa przeszukiwania binarnego (np drzewo czerwono-czarne ); STL SGI dostarcza równieżhash_set
klasę szablonu, która implementuje zestaw za pomocą tablicy mieszającej. C++11 posiada wsparcie dlaunordered_set
klasy szablonu, która jest zaimplementowana za pomocą tablicy mieszającej. W zestawach same elementy są kluczami, w przeciwieństwie do kontenerów sekwencjonowanych, w których dostęp do elementów odbywa się za pomocą ich (względnej lub bezwzględnej) pozycji. Elementy zestawu muszą mieć ściśle słabą kolejność. -
Java oferuje
Set
interfejs do obsługi zestawów (HashSet
klasa implementująca to za pomocą tablicy mieszającej) orazSortedSet
podinterfejs do obsługi posortowanych zestawów (TreeSet
klasa implementująca to za pomocą drzewa wyszukiwania binarnego). -
Jabłko „s Fundacja ramy (część kakao ) zapewnia Objective-C klasy
NSSet
,NSMutableSet
,NSCountedSet
,NSOrderedSet
, iNSMutableOrderedSet
. W CoreFoundation API zapewnić CFSET i CFMutableSet rodzaju do stosowania w C . -
Python ma wbudowane
set
ifrozenset
typy od wersji 2.4, a od wersji 3.0 i 2.7 obsługuje niepuste literały zestawów przy użyciu składni z nawiasami klamrowymi, np.:{x, y, z}
; puste zestawy muszą być tworzone za pomocąset()
, ponieważ Python używa{}
do reprezentowania pustego słownika. - .NET Framework zapewnia rodzajowe
HashSet
iSortedSet
klas, które implementują rodzajowyISet
interfejs. -
Biblioteka klas Smalltalk zawiera
Set
iIdentitySet
, używając odpowiednio równości i tożsamości do testu włączenia. Wiele dialektów zapewnić wariacje na sprężone składowania (NumberSet
,CharacterSet
), dla zamawiającego (OrderedSet
,SortedSet
etc.) lub dla słabych referencji (WeakIdentitySet
). -
Rubin średnia biblioteka jest zawiera
set
moduł, który zawieraSet
iSortedSet
klasy implementujące zestawów z tabel mieszania, ten ostatni umożliwiający iteracji w uporządkowanej kolejności. -
OCaml standardowa biblioteka „s zawiera
Set
moduł, który implementuje funkcjonalną strukturę zestaw danych za pomocą binarne drzewo poszukiwań. - GHC realizacja Haskell zapewnia
Data.Set
moduł, który implementuje niezmienne zestawy używając binarne drzewo poszukiwań. - Pakiet Tcl Tcllib zawiera moduł zestawu, który implementuje strukturę danych zestawu opartą na listach TCL.
- Swift biblioteka standardowa zawiera
Set
typ, ponieważ Swift 1.2. -
JavaScript wprowadzony
Set
jako standardowy obiekt wbudowany w standardzie ECMAScript 2015. -
Standardowa biblioteka Erlanga zawiera
sets
moduł. - Clojure ma dosłowną składnię dla zestawów mieszanych, a także implementuje posortowane zestawy.
- LabVIEW posiada natywną obsługę zestawów od wersji 2019.
Jak zauważono w poprzedniej sekcji, w językach, które nie obsługują bezpośrednio zestawów, ale obsługują tablice asocjacyjne , zestawy można emulować za pomocą tablic asocjacyjnych, używając elementów jako kluczy i używając fikcyjnej wartości jako wartości, które są ignorowane.
Multiset
Uogólnienie pojęciem zestawu jest to, że z MultiSet lub worka , który jest podobny do odbiornika, ale umożliwia wielokrotnym ( „równości”) wartości (duplikaty). Jest to używane w dwóch różnych znaczeniach: albo równe wartości są uważane za identyczne i są po prostu liczone, albo równe wartości są uważane za równoważne i są przechowywane jako odrębne elementy. Na przykład mając listę osób (według nazwiska) i wieku (w latach), można by skonstruować multizestaw wieków, który po prostu zlicza liczbę osób w danym wieku. Alternatywnie można skonstruować multizbiór osób, w którym dwie osoby są uważane za równoważne, jeśli ich wiek jest taki sam (ale mogą być różnymi osobami i mieć różne imiona), w którym to przypadku każda para (imię, wiek) musi być zapisana i wybierając w danym wieku daje wszystkim ludziom w danym wieku.
Formalnie możliwe jest, aby obiekty w informatyce były uważane za „równe” w pewnej relacji równoważności, ale nadal odrębne w innej relacji. Niektóre typy implementacji wielozestawowych będą przechowywać różne równe obiekty jako oddzielne elementy w strukturze danych; podczas gdy inni zwijają go do jednej wersji (pierwszej napotkanej) i zachowają dodatnią liczbę całkowitą krotności elementu.
Podobnie jak w przypadku zestawów, zestawy wielokrotne można naturalnie zaimplementować za pomocą tablicy mieszającej lub drzew, które dają różne charakterystyki wydajności.
Zbiór wszystkich toreb nad typem T jest określony wyrażeniem torebka T. Jeśli przez multiset uważa się równe pozycje za identyczne i po prostu je liczy, to multiset może być interpretowany jako funkcja od dziedziny wejściowej do nieujemnych liczb całkowitych ( naturalne liczb ), uogólniając identyfikację zbioru z jego funkcją wskaźnikową. W niektórych przypadkach multiset w tym sensie może być uogólniony, aby umożliwić wartości ujemne, tak jak w Pythonie.
- Standardowa biblioteka szablonów C++ implementuje zarówno posortowane, jak i nieposortowane zestawy wielokrotne. Udostępnia
multiset
klasę dla posortowanego zbioru multiset, jako rodzaj kontenera asocjacyjnego , który implementuje ten multiset za pomocą samobalansującego binarnego drzewa wyszukiwania . Dostarczaunordered_multiset
klasę dla unsorted multiset, jako rodzaj nieuporządkowanych kontenerów asocjacyjnych , które implementują ten multiset za pomocą tablicy mieszającej . Niesortowany multiset jest standardem od C++11 ; poprzednio SGI dostarczahash_multiset
klasę, która została skopiowana i ostatecznie ustandaryzowana. - W przypadku Java , biblioteki innych firm zapewniają funkcjonalność wielu zestawów:
-
Apache Commons kolekcje zapewnia łatwy
Bag
iSortedBag
interfejsy z realizacji zajęć jakHashBag
iTreeBag
. -
Google Guava udostępnia
Multiset
interfejs z implementacją klas takich jakHashMultiset
iTreeMultiset
.
-
Apache Commons kolekcje zapewnia łatwy
- Apple udostępnia
NSCountedSet
klasę jako część Cocoa ,CFBag
aCFMutableBag
typy i jako część CoreFoundation . -
Standardowa biblioteka Pythona zawiera
collections.Counter
, która jest podobna do multisetu. -
Smalltalk zawiera
Bag
klasę, która może być skonkretyzowana w celu użycia tożsamości lub równości jako predykatu dla testu włączenia.
Tam, gdzie wielozestawowa struktura danych nie jest dostępna, obejściem jest użycie zwykłego zestawu, ale zastąpienie predykatu równości jego elementów, aby zawsze zwracać „nie równe” dla różnych obiektów (jednak takie nadal nie będą w stanie przechowywać wielu wystąpień tego samego obiektu) lub użyj tablicy asocjacyjnej mapującej wartości na ich wielokrotności liczb całkowitych (nie będzie to w ogóle w stanie odróżnić równych elementów).
Typowe operacje na workach:
-
contains(B, x)
: sprawdza, czy element x jest obecny (co najmniej raz) w torbie B -
is_sub_bag(B1, B2)
: sprawdza, czy każdy element w torbie B 1 występuje w B 1 nie częściej niż w torbie B 2 ; czasami oznaczane jako B 1 ⊑ B 2 . -
count(B, x)
: zwraca ile razy element x wystąpił w torbie B ; czasami oznaczane jako B # x . -
scaled_by(B, n)
: podana liczba naturalna n , zwraca worek, który zawiera te same elementy co worek B , z tym wyjątkiem, że każdy element, który występuje m razy w B występuje n * m razy w wynikowym woreczku; czasami określane jako n ⊗ B . -
union(B1, B2)
: zwraca worek zawierający tylko te wartości, które występują w torbie B 1 lub torbie B 2 , z wyjątkiem tego, że liczba wystąpień wartości x w otrzymanym torbie jest równa ( B 1 # x) + ( B 2 # x); czasami oznaczane jako B 1 ⊎ B 2 .
Wielozbiory w SQL
W relacyjnych bazach danych tabela może być zbiorem (matematycznym) lub wielozbiorem, w zależności od obecności ograniczeń jedności w niektórych kolumnach (co zamienia ją w klucz kandydujący ).
SQL pozwala na wybór wierszy z tabeli relacyjnej: ta operacja generalnie da multiset, chyba że słowo kluczowe DISTINCT
jest używane do wymuszenia, aby wszystkie wiersze były różne, lub wybór zawiera klucz główny (lub kandydujący).
W ANSI SQLMULTISET
kluczowe mogą być wykorzystane do przekształcenia podkwerenda do ekspresji kolekcji:
SELECT expression1, expression2... FROM table_name...
jest ogólnym wyborem, który może być użyty jako wyrażenie podzapytania innego bardziej ogólnego zapytania, podczas gdy
MULTISET(SELECT expression1, expression2... FROM table_name...)
przekształca podzapytanie w wyrażenie kolekcji, którego można użyć w innym zapytaniu lub w przypisaniu do kolumny o odpowiednim typie kolekcji.