Platforma.NET laboratorium 3 Prowadzący: mgr inż. Tomasz Jaworski Strona WWW: http://tjaworski.kis.p.lodz.pl/ Visual Basic.NET kolekcje danych, technologia LINQ Poniższa instrukcja laboratoryjna zawiera omówienie zagadnień kolekcji danych w języku VB.NET oraz podstawy technologii LINQ 1. Kolekcje Kolekcja, zwana również kontenerem, jest strukturą danych (np. klasą), która posiada możliwość przechowywania ogólnie pojętych danych, np. tekstów, liczb, obiektów lub innych kolekcji. Dane w kolekcji przechowywane są w sposób zorganizowany wg. konkretnych założeń (np. są zawsze posortowane). Kolekcja posiada również charakterystyczny dla siebie zestaw operacji, pozwalających na przetwarzania zawartych w niej danych, np. dodawanie, usuwanie, wyszukiwanie duplikatów. Warto pamiętać, że podstawowe kontenery i kolekcje platformy.net Framework zostały umieszczone w przestrzeni nazw System.Collections. Przykłady kontenerów z różnych języków programowania: array list map Tablica (wym. erej /əˈreɪ/) to jedno- lub wielo-wymiarowa tablica dostępna popularnych językach programowania. Przeważnie wbudowana w język, jednak w VB jest klasą Array. Nie posiada możliwości dodawania nowych oraz usuwania istniejących elementów, jednak istnieje statyczna metoda Array.Resize. Przykład: Dim wartosci() As Double = New Double(10) {} Array.Resize(Of Double)(wartosci, 20) Lista (wym. lyst /lɪst/) posiada możliwość dodawania i usuwania elementów (Add, Remove), oraz m.in.: wyszukiwania (IndexOf, LastIndexOf) i konwersji na tablicę (ToArray). Dim str As List(Of String) = New List(Of String) str.add("ala") str.add("ma") str.add("kota") str.addrange(new String() {"a", "kot", "ma", "Alę"}) Dim pozycja = str.indexof("kot") Dim slowa() As String = str.toarray() Mapa, zwana Tablicą Asocjacyjną, jest kolekcją par uporządkowanych (klucz, wartość). Klucz jest najczęściej unikalny, choć nie jest to wymóg konieczny zależy to od rodzaju tablicy asocjacyjnej. Przykładem tablicy asocjacyjnej może być słownik służący do szyfrowania wiadomości. Łatwo sobie wyobrazić sytuację, w której dwie osoby uzgadniają, że będą przesyłały sobie wiadomości składające się ze skończonego zbioru słów (np. 10,000). W trakcie ustalania szczegółów każdemu z tych słów przypisują jakąś wartość numeryczną (na podstawie klucza). Zatem, kiedy nadawca chce wysłać wiadomość, korzysta z tablicy asocjacyjnej, mapując słowa wiadomości na wartości liczbowe, które następnie przesyła do odbiorcy. 1 Wymagana wersja środowiska.net Framework 3.5 1
Przykład: Dim slownik As Dictionary(Of String, Integer) = _ New Dictionary(Of String, Integer) slownik.add("ala", 1) slownik.add("ma", 2) slownik.add("kota", 3) slownik.add("kot", 4) slownik.add("alę", 5) slownik.add("a", 6) Dim kod_slowa As Integer If slownik.containskey("kot") Then kod_slowa = slownik("kot") ' kod_slowa = 4 End If A zatem, przy tak zdefiniowanym słowniku, tekst Ala ma kota a kot ma Alę zostanie wysłany jako ciąg liczb: 1 2 3 6 4 2 5 queue Kolejka (wym. kju /kjuː/) to kolejka FIFO (ang. First In, First Out). Dwie najważniejsze metody to: Enqueue dodaje nowy element na ostatnie miejsce w kolejce, Dequeue pobiera element z pierwszego miejsca kolejki (i usuwa go), Dim kol As Queue(Of String) = New Queue(Of String) kol.enqueue("ala") kol.enqueue("ma") kol.enqueue("kota") MessageBox.Show(kol.Dequeue()) ' "Ala" MessageBox.Show(kol.Dequeue()) ' "ma" MessageBox.Show(kol.Dequeue()) ' "kota" set stack table Zbiór zbiór elementów przechowywanych w sposób dowolny. Najważniejszą cechą zbioru jest unikalność jego elementów. Dim a As HashSet(Of Integer) = New HashSet(Of Integer) Dim w As Boolean w = a.add(1) ' true w = a.add(2) ' true w = a.add(3) ' true w = a.add(2) ' false w = a.add(1) ' false Stos (wym. stak /stæk/) to kolejka LIFO (ang. Last In, First Out). Dwie najważniejsze metody to: Push kładzie nowy element na wierzchołek stosu, Pop zdejmuje element z wierzchołka stosu (i usuwa go), Dim stos As Stack(Of Double) = New Stack(Of Double) stos.push(1) stos.push(2) stos.push(3) wartosc = stos.pop() ' wartosc=3 wartosc = stos.pop() ' wartosc=2 wartosc = stos.pop() ' wartosc=1 Tabela danych, podobnie jak typowa tabela bazy danych, może posiadać dowolną ilość kolumn oraz wierszy. Każda z kolumn posiada m.in. nazwę oraz typ przechowywanych danych. Przykład: Dim dt As DataTable = New DataTable() dt.columns.add("id", GetType(Integer)) dt.columns.add("imie", GetType(String)) 2
dt.columns.add("nazwisko", GetType(String)) dt.rows.add(new Object() {1, "Esmerelda", "Weatherwax"}) dt.rows.add(new Object() {2, "Moist", "von Lipwig"}) dt.rows.add(new Object() {3, "Havelock", "Vetinari"}) dt.rows.add(new Object() {4, "Mustrum", "Ridcully"}) Dim wiersz = dt.newrow() wiersz("id") = 5 wiersz("imie") = "Samuel" wiersz("nazwisko") = "Vimes" Linq LINQ (ang. Language Integrated Query) jest funkcjonalnością platformy.net Framework, tworzącą warstwę abstrakcji między różnymi źródłami danych (np. kontenery, bazy danych, XML) a kodem programu. Mówiąc prościej, LINQ pozwala na intuicyjne, podobne do SQLa, podejście do wyszukiwania i przetwarzania danych w kolekcjach, np. tablicach. Uwaga! LINQ dostępny jest dopiero od wersji 3.5 środowiska.net Podczas tworzenia nowego projektu należy wybrać odpowiednią wersję środowiska: Poniżej znajduje się kilka przykładów wykorzystania technologii LINQ wraz z komentarzami. 3
Przykład: Wybranie wszystkich liczb mniejszych od 6 Dim liczby() As Integer = New Integer() {0, 9, 2, 8, 3, 7, 4, 6, 5} Dim wyn As IEnumerable(Of Integer) wyn = From liczba In liczby Where liczba < 6 Select liczba wyn = liczby.where(function(liczba) liczba < 6) Dim lista As List(Of Integer) = New List(Of Integer)(wyn) For Each i As Integer In wyn Console.Write(i.ToString() + " ") For Each i As Integer In lista Console.Write(i.ToString() + " ") Wynik: 0 2 3 4 5 0 2 3 4 5 Opis przykładu: Tworzona jest tablica (kolekcja) liczby typu Integer oraz zmienna wyn typu IEnumerable(Of ) typu najczęściej zwracanego przez metody zapytań LINQ (np. Where oraz Select). Zmienna tego typu dostarcza enumerator pozwalający na iterowanie 2 po pewnej kolekcji. W tym przypadku będzie to kolekcja zwrócona przez klauzulę Where/Selekt. Typ zapytania określa typ wyniku przykład wykonuje zapytanie na liczbach typu Integer a w klauzuli select nie jest dokonywana żadna konwersja typów. Zatem wynik (wyn) będzie typu: IEnumerable(Of Integer). W przypadku poniższej linii kodu: Dim wyn = From liczba As Integer In num Where liczba < 6 Select tekst = liczba.tostring() wynik będzie typu IEnumerable(Of String), ponieważ w klauzuli select dokonano konwersji liczby na ciąg znaków (metoda.tostring()) Konstrukcja For Each o następującej składni: For Each nazwa_zmiennej [ As typ_danych ] In grupa [ kod ] [ Exit For ] [ kod ] [ nazwa_zmiennej ] wykorzystuje fakt, że grupa (np. lista, tablica) zawiera implementację interfejsu IEnumerable(Of typ_danych), który posiada zdolność zwracania iteratora podstawy działania pętli For Each. Zatem poniższe dwie pętle są sobie równoważne: For Each liczba As Integer In num Console.WriteLine(liczba) For i As Integer = 0 To num.length - 1 Console.WriteLine(num(i)) Wersja alternatywna, oparta o wyrażenia Lambda Konwersja wyniku LINQ na listę liczb Integer Przykład alternatywny zawiera wyrażenie lambda (λ-expression) wykonywane dla każdego elementu kolekcji wejściowej (tutaj liczby). Wyrażenie lambda to funkcja bez nazwy, tzw. delegat anonimowy. Function(liczba As Integer) liczba < 6 2 Iterowanie przetwarzanie elementu po elemencie aż do napotkania końca (ostatniego lub pustego elementu) 4
Lewa strona zawiera listę parametrów lub ich brak. Prawa strona zawiera operacje wykonywane w ramach wyrażenia lambda. Wyrażenia lambda wykorzystywane są najczęściej do wykonywania prostych obliczeń czy testów logicznych. Powyższy przykład sprawdza, czy liczba jest mniejsza od 6 i zwraca prawdę lub fałsz w zależności od wartości liczba. Przykład: Pobranie imienia osoby o identyfikatorze id=2 z tabeli dt zdefiniowanej we wcześniejszym tekście. Dim w = From row As DataRow In dt.rows _ Where row("id") = 2 Select row("imie") Dim w = dt.select().where(function(row As DataRow) row("id") = 2) Wynik: w = Moist Opis przykładu: Wykorzystana została (przedstawiona wcześniej) tabela dt typu DataTable, posiadająca kolekcję wierszy Rows. Powyższy przykład wykorzystuje tę kolekcję w następujący sposób: Dla każdego wiersza z kolekcji wykonywane jest wyrażenie logiczne row("id") = 2. Jeśli wynik jest fałszem, to wiersz jest odrzucany. Jeśli wynik jest prawdą, to z wiersz jest dodawany do wewnętrznej kolekcji (tymczasowej). Następnie kolekcja ta przekazywana jest do klauzuli select, wykonującej konwersję typów z DataRow na String. Konwersja dobywa się za pomocą wyrażenia row("imie"), które pobiera wartości pola imie z wiersza row. Zatem wynik jest kolekcją tekstów (String) o typie określonym jako: IEnumerable(Of String). Przykład: Obliczenie ilości miejsca zajmowanego przez wszystkie pliki z katalogu i jego podkatalogów. Imports System.IO Dim pliki() As String = Directory.GetFiles("c:\ATI Stream", "*.*", _ SearchOption.AllDirectories) Dim info = From plik As String In pliki Select New FileInfo(plik) Dim info = pliki.select(function(nazwa) New FileInfo(nazwa)) Dim razem = Aggregate plik As FileInfo In info _ Into suma = Sum(plik.Length) Dim razem = info.aggregate(0, Function(licznik As Long, _ plik As FileInfo) licznik + plik.length) Wersje alternatywne z wyrażeniem lambda. MessageBox.Show("Bajtów: " + razem.tostring()) Opis przykładu: Wykorzystana jest metoda statyczna GetFiles do pobrania listy plików z wybranego katalogu i jego podkatalogów. Wynikiem jest kolekcja (tablica) obiektów typu String. Pierwsze wyrażenie, tworzące zmienną info, wykorzystuje klauzulę select do zamiany kolekcji typu String na kolekcję typu FileInfo (informacje o pliku) w następujący sposób: Dla każdego elementu z kolekcji pliki (element został nazwany jako plik) tworzony jest nowy obiekt operatorem New. Obiekt taki zapisywany jest do kolekcji wyjściowej info. Drugie wyrażenie wykonuje agregację polegającą na przeiterowaniu przez wszystkie elementy kolekcji info i wyznaczenie dla każdego elementu (plik) wartości 5
liczbowej (plik.length). Operator sum zapewnia ostateczne zsumowanie wygenerowanej kolekcji długości plików. Przykład: Wyszukanie największego pliku lub plików w katalogu. Dim wielkosci = From plik As FileInfo In info Select plik.length Dim wielkosci = info.select(function(plik As FileInfo) plik.length) Dim dlug = wielkosci.max() Dim lista_najwiekszych = From plik As FileInfo In info Where _ plik.length = dlug Select plik Dim lista_najwiekszych = info.where(function(plik As FileInfo) _ plik.length = dlug) For Each plik As FileInfo In lista_najwiekszych Console.WriteLine(plik.Name) Zastąpienie zmiennej dlug wyrażeniem wielkości.max() spowodowało znaczny spadek prędkości wykonywania programu Opis przykładu: Przykład wykorzystuje klauzulę select do wygenerowania kolekcji dlugości poszczególnych plików (zmienna wielkości jest typu IEnumerable(of Long)). Operator.Max() określa największą długość pliku (i zapisuje do zmiennej a). Drugie wyrażenie zwraca listę plików (ponieważ może być kilka plików o tym samym rozmiarze) poprzez porównanie długości każdego pliku (plik) z kolekcji info ze zmienną dlug. Ostateczny wynik w postaci listy plików jest wyświetlany na konsoli. Dalsze informacje na temat technologii LINQ [1] http://msdn.microsoft.com/en-us/netframework/aa904594 - strona główna [2] http://msdn.microsoft.com/en-us/library/bb394939.aspx - operatory zapytań (select, where, itp.) [3] http://msdn.microsoft.com/en-us/library/bb308959.aspx - przegląd najważniejszych funkcjonalności [4] http://msdn.microsoft.com/en-us/vcsharp/aa336746-101 przykładów LINQ Zadanie 1 a) Stworzyć kolekcję napisów dla dowolnych 10 słów i wyświetlić ją na ekranie. b) Korzystając z klauzuli select stworzyć dodatkową kolekcję zawierającą długości poszczególnych słów Zadanie 2 Stworzyć prostą tabelę kontaktów zawierającą kolumny imie, nazwisko, email a następnie konstrukcją from where select wybrać tylko te wiersze, w których osoba ma na imię Anna. Zadanie 3 wymagana znajomość złączeń LEFT/RIGHT JOIN Stworzyć dwie tabele typu DataTable, gdzie pierwsza zawiera: imie typ String nazwisko typ String 6
email typ String typ_kontaktu typ Integer, 0-prywatny, 1-domowy, 2-praca plec typ Integer, 1-kobieta, 2-mężczyzna Druga natomiast zawiera dwie kolumny i trzy wiersze: id typ Integer, identyfikator typu kontaktu (0, 1, 2) nazwa typ String, zawiera nazwę typu ( prywatny, domowy, praca ) Korzystając z dokumentacji klauzuli JOIN: http://msdn.microsoft.com/en-us/library/bb531340(vs.90).aspx Wygenerować wynik, w którym kolumna typ_kontaktu z tabeli pierwszej zostanie zastąpiona kolumną nazwa z tabeli drugiej. a) Wykonać podobną operację dla kolumny plec tabeli pierwszej. Zadanie 4 Stworzyć program (może być konsolowy), który wylosuje 100,000 liczb (funkcja Rnd()), policzy ich średnią arytmetyczną: 1 X = N korzystając: a) metody.average() lub.sum() kolekcji b) konstrukcję Aggregate As N x i i= 1 7