Laboratorium 4: BCL, Kolekcje i Wyrażenia Lambda #
Przetwarzanie tekstu, parsowanie #
Co to jest REST API?
REST (Representational State Transfer) to architektoniczny styl projektowania usług sieciowych, który wykorzystuje standardowe operacje HTTP (
GET,POST,PUT,DELETE) do komunikacji klient‑serwer.
- Zasoby identyfikowane są przy pomocy URL (np.
https://api/users/123).- Metody HTTP określają akcję (np. pobranie, utworzenie, modyfikację).
- Każdy zapytanie zawiera wszystkie informacje potrzebne do jego przetworzenia (bezstanowość ang. stateless).
W praktyce niemal każda nowoczesna aplikacja webowa czy mobilna udostępnia REST API.
Ścieżki do zasobów (ang. resource paths)
Ścieżki w URL określają, do jakich zasobów i w jakiej hierarchii chcemy się odwołać na serwerze. Każdy segment po hoście to albo nazwa zasobu, albo jego identyfikator.
/v1/users/42: wersja1, zasóbusers, identyfikator42./v2/countries/100/cities/3: wersja2, zasóbcountries(id100), a następnie zagnieżdżony zasóbcities(id3).Parametry zapytania (ang. query parameters)
Parametry zapytania, umieszczone po znaku
?, pozwalają przekazywać dodatkowe dane do serwera:
- Każdy parametr to
klucz=wartość(np.lang=pl).- Poszczególne pary
klucz=wartośćoddzielane są znakiem&, np.?tag=new&active=true.
Opis zadania #
Twoim zadaniem jest zaimplementowanie metody:
public static (ParsedUrl url, ParsingStatus status) ParseUrl(string url);Metoda powinna:
- Przyjmować jako parametr ciąg znaków
url. - Zwracać krotkę o dwóch nazwanych elementach:
url- obiekt klasyParsedUrlzawierający szczegóły parsowanego adresu.status- wartość typu wyliczeniowegoParsingStatusinformującą o powodzeniu lub przyczynie błędu parsowania.
- W przypadku nieudanego parsowania, zawartość obiektu
ParsedUrljest nieistotna (kluczowy jest wówczasstatus).
Definicje typów pomocniczych:
// Schemat protokołu
public enum UrlScheme
{
Http,
Https,
Ftp,
Wss
}
// Segment ścieżki (nazwa zasobu + identyfikator)
public sealed class ResourceSegment
{
public string Name { get; set; } = string.Empty;
public int Id { get; set; }
}
// Status parsowania (informuje o powodzeniu lub błędzie)
public enum ParsingStatus
{
UnexpectedFormat,
Success,
InvalidScheme,
InvalidHost,
InvalidVersion,
InvalidPath,
InvalidId,
InvalidQuery,
}
// Typ zwracany przez metodę
public sealed class ParsedUrl
{
public UrlScheme Scheme { get; set; }
public string Host { get; set; } = string.Empty;
public int Version { get; set; }
public List<ResourceSegment> PathSegments { get; set; } = [];
public Dictionary<string, List<string>> QueryParams { get; set; } = [];
}Struktura oczekiwanego URL
[scheme]://[host]/v[version]/[resource1]/[id1]/[resource2]/[id2]/...?[param1]=[valueA]&[param1]=[valueB]&[param2]=[valueC]...gdzie:
scheme: jeden z elementów typu wyliczeniowegoUrlScheme,host: nazwa domeny (np.example.com),version: liczba całkowita (prefiksv, np.v2),resourceN: nazwa zasobu (ciąg znaków bez/),idN: całkowitoliczbowy identyfikator zasobu,paramN: nazwa parametru,valueN: wartość parametru.
Uwagi implementacyjne
- Parametry zapytania:
- W zadaniu przyjmujemy, że nazwa parametru może się pojawić na liście parametrów wielokrotnie.
- Wszystkie wartości dla danego parametru agregowane są w postaci listy.
- Operacje na klasie
string:
- Nie korzystaj z
System.Uri.- Używaj gotowych metod klasy
string(np.Split,IndexOf,Substring,Contains).- Status parsowania:
Success: parsowanie bez błędów,InvalidScheme: niezgodny lub nieobsługiwany scheme,InvalidHost: brak części odpowiadającej za hosta,InvalidVersion: wersja nie parsuje się naintlub jest< 1,InvalidPath: błędna liczba segmentów (np. brak identyfikatora dla ostatniego zasobu),InvalidId: identyfikator jest niepoprawny (zasady te same jak dla wersji),InvalidQuery: błędny format query (np. brak=),UnexpectedFormat: dowolny inny błąd.- Debuggowanie: Zadanie jest dobrą okazją do zaznajomienia się z działaniem debuggera.
Materiały pomocnicze:
Przykłady
Poprawny URL z jednym segmentem i jednym parametrem
var input1 = "http://example.com/v1/users/42?lang=pl";
var (parsed1, status1) = ParseUrl(input1);
/*
status1 == ParsingStatus.Success
parsed1.Scheme == UrlScheme.Http
parsed1.Host == "example.com"
parsed1.Version == 1
parsed1.PathSegments == [ { Name = "users", Id = 42 } ]
parsed1.QueryParams == { "lang": ["pl"] }
*/URL z wieloma segmentami i wielokrotnymi parametrami
var input2 = "https://api.test/v2/orders/100/items/200" +
"?tag=new&tag=discount&active=true";
var (parsed2, status2) = ParseUrl(input2);
/*
status2 == ParsingStatus.Success
parsed2.Scheme == UrlScheme.Https
parsed2.Host == "api.test"
parsed2.Version == 2
parsed2.PathSegments == [
{ Name = "orders", Id = 100 },
{ Name = "items", Id = 200 }
]
parsed2.QueryParams == {
"tag": ["new","discount"],
"active": ["true"]
}
*/Nieobsługiwany scheme
var input3 = "smtp://host/v1/res/1";
var (_, status3) = ParseUrl(input3);
// status3 == ParsingStatus.InvalidSchemeNieparzysta liczba segmentów w ścieżce
var input4 = "https://host/v1/resOnly";
var (_, status4) = ParseUrl(input4);
// status4 == ParsingStatus.InvalidPathBłędny parametr zapytania (brak '=')
var input5 = "http://host/v1/r/10?badparam";
var (_, status5) = ParseUrl(input5);
// status5 == ParsingStatus.InvalidQueryZadanie dla chętnych
- W implementacji metody
ParseUrlspróbuj wykorzystać możliwości, jakie daje przestrzeń nazwSystem.Text.RegularExpressions. Informacje o wyrażeniach regularnych możesz znaleźć m.in. w artykule Microsoft Learn: .NET regular expressions. Do pobrania w formacie.pdfjest również Regular expressions quick reference. Interaktywne tworzenie wyrażeń regularnych zgodnych ze składnią .NET umożliwia m.in. popularna strona regex101.com.
Przykładowe rozwiązanie #
Przykładowe rozwiązanie wraz z testami jednostkowymi można znaleźć w pliku Task01.cs.
Formatowanie, data i czas #
Czym charakteryzuje się format CSV?
CSV (ang. comma-separated values) to format przechowywania danych w plikach tekstowych (odpowiadający mu typ MIME to
text/csv).
- Poszczególne rekordy oddzielone są znakami końca linii
\n.- Wartości pól standardowo oddzielone są przecinkami
,.- Jako separator bywa stosowany znak średnika
;(tak będzie właśnie w naszym zadaniu).- W jednym pliku może być użyty tylko jeden rodzaj separatora.
- Wartości pól mogą być ujęte w cudzysłów (w przypadku wartości zawierających znak separatora jest to wymagane).
- Pierwsza linia może stanowić nagłówek zawierający nazwy pól rekordów.
Czego się nauczysz?
- Pracy z datą i czasem w języku
C#, odczytując dane z pliku CSV, zawierającego dzienne pomiary temperatury w wybranych europejskich stolicach.- Operacji na datach i interwałach czasowych (klasy
DateTimeiTimespan).- Formatowania wyjścia zgodnie z ustawieniami kulturowymi (
CultureInfo).- Użycia metody
ForEachstandardowej kolekcjiList<T>oraz konstruowania prostych wyrażeń lambda do filtrowania i wyświetlania danych.
Opis zadania #
Twoim zadaniem jest zaimplementowanie metody:
public static List<Measurement> ParseMeasurements(string content);Metoda powinna:
- Przyjmować jako parametr ciąg znaków
content, będący zawartością pliku CSV. - Parsować kolejne rekordy pliku i zwrócić listę obiektów typu
Measurement.
Następnie zaimplementuj funkcjonalność wypisywania obiektów klasy Measurement, tak aby ciąg znaków odpowiadający pojedynczemu obiektowi zawierał:
- Kraj i miasto,
- Datę w formacie długim zgodnie z kulturą określoną polem
Code, - Wartości pomiarów sformatowane z separatorami dziesiętnymi i tysięcznymi właściwymi dla kultury.
Przykład wypisania dla rekordu z Code = "pl-PL":
Location: Poland, Warsaw
Date: 21 czerwca 2025
Temperatures:
0,50 °C 3,20 °C 7,10 °C 12,45 °C
16,30 °C 14,10 °C 10,05 °C 1,23 °C
1,23 °C 4,56 °C 7,89 °CPo sparsowaniu zawartości pliku wypisz obiekty spełniające następujące warunki:
- Data pomiarów przeprowadzonych w przedziale od 8 czerwca do 13 września bieżącego roku.
- Data pomiarów przeprowadzonych w roku 2025.
- Data pomiarów przeprowadzonych tylko w weekendy (sobota i niedziela).
- Data pomiarów przeprowadzonych w ciągu ostatnich 7 dni.
Definicje typów pomocniczych:
public sealed class Measurement
{
public string Country { get; set; } = string.Empty;
public string City { get; set; } = string.Empty;
public string Code { get; set; } = string.Empty;
public DateTime Date { get; set; }
public double[] Temperatures { get; set; } = [];
}Format pliku CSV
Plik CSV wygląda w następujący sposób:
Location; Code; Date; Temperatures
Poland Warsaw ; pl-PL; 2025-06-21; [ 25.9, 18.0 , 9.5 , 24.3 ]Skorzystaj z następującego pliku CSV: measurements.csv.
Uwagi implementacyjne
- Parsowanie pliku CSV:
- Do odczytania zawartości pliku skorzystaj z metody
File.ReadAllText.- Tablica pomiarów zawiera wartości zapisane przy użyciu
CultureInfo.InvariantCulture, które są rozdzielone za pomocą znaku przecinka,.- Poszczególne pola rekordów mogą zawierać nadmiarowe białe znaki, których należy się pozbyć.
- Dla ułatwienia można założyć, że wszystkie rekordy i pola zawierają poprawne dane (np.
Datezawiera poprawnie zapisaną datę, aTemperaturespoprawnie zapisane liczby zmiennoprzecinkowe).- Wypisywanie obiektów:
- Należy zachować formatowanie z przykładu (format daty, szerokość wypisywania wyrównanie, liczba miejsc po przecinku, liczba pomiarów w jednej linii itp.).
- Do filtrowania obiektów wykorzystaj metodę
ForEachoraz wyrażenia lambda. Nie należy korzystać z jawnej pętli.
Materiały pomocnicze:
- Microsoft Learn: Parse strings in .NET
- Microsoft Learn: Using the StringBuilder Class in .NET
- Microsoft Learn: DateTime Struct
- Microsoft Learn: Standard date and time format strings
- Microsoft Learn: Standard numeric format strings
- Microsoft Learn: CultureInfo Class
- Microsoft Learn: List
.ForEach(Action ) Method
Zadanie dla chętnych
- Zaimplementuj wczytywanie danych z pliku CSV przy pomocy popularnej paczki nuget CsvHelper.
Instrukcje instalacji znajdziesz m.in. w dokumentacji Microsoft Learn (Visual Studio) Install and manage packages in Visual Studio using the NuGet Package Manager oraz na stronie pomocy JetBrains (Rider) Consume NuGet packages.
Przykładowe rozwiązanie #
Przykładowe rozwiązanie wraz z testami jednostkowymi można znaleźć w pliku Task02.cs.
Liczby losowe, wyrażenia lambda #
Wyrażenia Lambda
Wyrażenia lambda (ang. lambda expressions) to krótkie, anonimowe funkcje, które można zapisać z użyciem operatora
=>. Pozwalają one przekazywać logikę jako parametr do innych metod, zwracać funkcje czy przechowywać je w kolekcjach.Lambdy mogą przechwytywać (ang. closures) zmienne z otaczającego je kontekstu — np. licznik, obiekt klasy
Randomlub aktualny stan algorytmu — dzięki czemu zachowują dostęp do tych wartości nawet po wyjściu z zakresu, w którym zostały zdefiniowane.Lambdy są intensywnie wykorzystywane przez metody LINQ m.in. do filtrowania, agregowania, czy tzw. projekcji na określony typ.
Czego się nauczysz?
- Tworzenia wyrażeń lambda i przekazywania ich w postaci standardowego delegatu
Func<T>(funkcje wyższego rzędu).- Mechanizmu przechwytywania zmiennych i jego wpływu na działanie kodu.
- Generowania liczb losowych i podejmowania decyzji z określonym prawdopodobieństwem.
Opis zadania #
Zaimplementuj metodę:
public static void Fill(List<int> collection, int length, Func<int> generator);- Metoda dodaje
lengthelementów do listycollection. - Każdy kolejny element jest wynikiem wywołania funkcji
generator.
Następnie, przy pomocy metody Fill wypełnij i wypisz listy w następujący sposób:
- 10 kolejnych wyrazów ciągu arytmetycznego o pierwszym wyrazie 3 i różnicy 8.
- 10 kolejnych elementów ciągu Fibonacciego,
- 10 losowych liczb z przedziału
[5, 50], - 10 elementów o wartości
0/1z zadanym prawdopodobieństwem (np.P(1) = 0.3), - 10 losowych elementów ze zbioru 10 początkowych liczb pierwszych
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29], - Łańcuch Markowa długości 20, ze stanem początkowym
1, określony przez tablicę przejść:
1 | 2 | 3 | |
|---|---|---|---|
1 | 0.1 | 0.6 | 0.3 |
2 | 0.4 | 0.2 | 0.4 |
3 | 0.5 | 0.3 | 0.2 |
Przykładowo dla wiersza 3 i kolumny 1 przejście ze stanu
3do stanu1odbywa się z prawdopodobieństwem0.5.Tablicę przejść można przykładowo zaimplementować jako słownik list o elementach będących krotkami postaci (stan, prawdopodobieństwo).
Materiały pomocnicze:
Przykładowe rozwiązanie #
Przykładowe rozwiązanie wraz z testami jednostkowymi można znaleźć w pliku Task03.cs.
Wyrażenia regularne #
Czym są wyrażenia regularne?
Wyrażenia regularne (ang. regular expressions, regex) to potężne narzędzie do wyszukiwania i manipulowania tekstem na podstawie wzorców znaków.
- Pozwalają na dokładne dopasowanie ciągów znaków w tekście.
- Umożliwiają grupowanie i przechwytywanie fragmentów dopasowanego tekstu do dalszego przetwarzania.
- Stosowane są w wielu językach programowania, w tym w C#, gdzie obsługuje je klasa
Regex.Pomimo że ich działanie bywa mniej wydajne niż dedykowane algorytmy (np. oparte na analizie znak po znaku), ich największą zaletą jest zwięzłość i przejrzystość kodu – cała logika dopasowania i ekstrakcji danych może zostać zapisana w jednym wzorcu, a całą pracę wykonuje za nas silnik wyrażeń regularnych. Dzięki temu kod jest czystszy, krótszy i łatwiejszy w utrzymaniu.
Czego się nauczysz?
- Jak tworzyć i stosować wyrażenia regularne w C# do parsowania złożonych formatów tekstowych, takich jak logi serwera.
- Jak definiować nazwy grup w wyrażeniach regularnych, aby wygodnie wyodrębniać interesujące dane.
Opis zadania #
Napisz program, który dla podanego pliku tekstowego logs.txt, zawierającego logi w formacie:
[YYYY-MM-DD HH:mm:ss] LEVEL: IP - METHOD /api/RESOURCE/ID - HTTP_CODE HTTP_STATUS[: opcjonalny komunikat]Użyje wyrażenia regularnego z nazwanymi grupami, aby wyodrębnić następujące pola z każdego wpisu logu:
LEVEL: poziom wpisu,RESOURCE: nazwę zasobu,ID: identyfikator zasobu,HTTP_STATUS: kod odpowiedzi,HTTP_CODE: status odpowiedzi.
Uzyskane informacje powinny zostać zmapowane do kolekcji rekordów LogEntry, a następnie wypisane w konsoli w przykładowym formacie:
LEVEL: RESOURCE/ID => HTTP_CODE HTTP_STATUSDefinicje typów pomocniczych:
public record LogEntry(
string Level,
string Resource,
string Id,
int HttpCode,
string HttpStatus
);Uwagi
- Zadanie to należy traktować jako dodatkowe (z uwagi na podwyższony poziom trudności i złożoność zagadnienia).
Materiały pomocnicze:
Przykładowe rozwiązanie #
Przykładowe rozwiązanie można znaleźć w pliku Task04.cs.