→ Slide 1

Kolekcje i algorytmy. Strumienie. Operacje IO

→ Slide 2

Wyrażenia lambda

// Bez parametrów
Runnable r = () -> System.out.println("Hello!");
 
// Jeden parametr (nawiasy opcjonalne)
Consumer<String> print = s -> System.out.println(s);
 
// Wiele parametrów
Comparator<Integer> cmp = (a, b) -> a - b;
 
// Blok instrukcji
BinaryOperator<Integer> add = (a, b) -> {
    int result = a + b;
    return result;
};
→ Slide 3

Interfejsy funkcyjne

@FunctionalInterface
interface Transformer<T> {
    T transform(T input);
    // można dodawać metody default i static
    default T transformTwice(T input) {
        return transform(transform(input));
    }
}
 
Transformer<String> upper = s -> s.toUpperCase();
System.out.println(upper.transform("hello")); // HELLO
→ Slide 4

Klasy anonimowe i lambda

Przykład klasy anonimowej vs lambda:

// Klasa anonimowa
Comparator<String> comp1 = new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.length() - b.length();
    }
};
 
// Równoważne wyrażenie lambda
Comparator<String> comp2 = (a, b) -> a.length() - b.length();
 
List<String> names = Arrays.asList("Ania", "Bartosz", "Ola");
names.sort(comp2);
System.out.println(names); // [Ola, Ania, Bartosz]

Uwaga: zmienne używane w klasach anonimowych i lambdach muszą być final lub efektywnie finalne (Java 8+).

→ Slide 5

Referencje do metod

Rodzaj Składnia Odpowiednik lambdy
Metoda statyczna Klasa::metoda (x) → Klasa.metoda(x)
Metoda instancji (dowolny obiekt) Klasa::metoda (x) → x.metoda()
Metoda instancji (konkretny obiekt) obj::metoda (x) → obj.metoda(x)
Konstruktor Klasa::new (x) → new Klasa(x)
→ Slide 6

Przykład

List<String> names = Arrays.asList("Ania", "Bartosz", "Ola");
 
// Referencja do metody statycznej
names.forEach(System.out::println);
 
// Referencja do metody instancji
names.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println);
 
// Referencja do konstruktora
List<Integer> sizes = names.stream()
    .map(String::length)
    .collect(Collectors.toList());
→ Slide 7

Predefiniowane interfejsy funkcyjne

Pakiet java.util.function - gotowe interfejsy funkcyjne:

Interfejs Metoda Opis
Function<T,R> R apply(T t) Przekształca T w R
Predicate<T> boolean test(T t) Sprawdza warunek
Consumer<T> void accept(T t) Konsumuje wartość
Supplier<T> T get() Dostarcza wartość
BiFunction<T,U,R> R apply(T t, U u) Dwa argumenty
UnaryOperator<T> T apply(T t) Funkcja tożsama
BinaryOperator<T> T apply(T t1, T t2) Dwa arg. tego samego typu
→ Slide 8

Przykład

Function<String, Integer> len = String::length;
Predicate<Integer> isEven = n -> n % 2 == 0;
Consumer<String> log = s -> System.out.println("[LOG] " + s);
Supplier<List<String>> listFactory = ArrayList::new;
 
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isPositiveEven = isEven.and(isPositive);
System.out.println(isPositiveEven.test(4));  // true
System.out.println(isPositiveEven.test(-2)); // false
→ Slide 9

Wprowadzenie do kolekcji

// Tablica - stały rozmiar
String[] arr = new String[3];
arr[0] = "Ania";
 
// Kolekcja - dynamiczny rozmiar
List<String> list = new ArrayList<>();
list.add("Ania");
list.add("Bartosz");
list.remove("Ania");
System.out.println(list.size()); // 1
→ Slide 10

Hierarchia kolekcji w Javie

Główne interfejsy (java.util):

Iterable
  └── Collection
        ├── List       (uporządkowane, duplikaty OK)
        │     ├── ArrayList
        │     └── LinkedList
        ├── Set        (bez duplikatów)
        │     ├── HashSet
        │     ├── LinkedHashSet
        │     └── SortedSet → TreeSet
        └── Queue      (kolejka FIFO/LIFO)
              ├── LinkedList
              └── PriorityQueue
Map (osobna hierarchia, nie extends Collection)
  ├── HashMap
  ├── LinkedHashMap
  └── SortedMap → TreeMap
→ Slide 11

Interfejsy kolekcji

Interfejs Charakterystyka
List<E> Elementy w kolejności, dostęp przez indeks, duplikaty OK
Set<E> Brak duplikatów, kolejność zależy od implementacji
SortedSet<E> Set posortowany naturalnie lub przez Comparator
Queue<E> Operacje FIFO: offer, poll, peek
Deque<E> Kolejka dwustronna: stos + kolejka
Map<K,V> Pary klucz-wartość, klucze unikalne
SortedMap<K,V> Map z posortowanymi kluczami
List<String> list = new ArrayList<>();     // indeks, duplikaty
Set<String> set = new HashSet<>();         // bez duplikatów
Queue<String> queue = new LinkedList<>();  // FIFO
Deque<String> stack = new ArrayDeque<>();  // stos (LIFO)
Map<String, Integer> map = new HashMap<>();// klucz -> wartość
→ Slide 12

Iterable i pętla for-each

List<String> names = List.of("Ania", "Ola", "Bartosz");
 
for (String name : names) {
    System.out.println(name);
}
 
Map<String, Integer> scores = Map.of("Ania", 90, "Ola", 88);
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}
→ Slide 13

Iterator i Spliterator

List<Integer> nums = new ArrayList<>(List.of(1, 2, 3, 4, 5));
 
Iterator<Integer> it = nums.iterator();
while (it.hasNext()) {
    Integer n = it.next();
    if (n % 2 == 0) it.remove();
}
System.out.println(nums); // [1, 3, 5]
 
Spliterator<Integer> sp = nums.spliterator();
Spliterator<Integer> sp2 = sp.trySplit();
if (sp2 != null) sp2.forEachRemaining(System.out::println);
sp.forEachRemaining(System.out::println);
→ Slide 14

Implementacje kolekcji

Klasa Bazuje na Właściwości
ArrayList<E> tablica dynamiczna szybki dostęp O(1), wolny insert w środku
LinkedList<E> lista dwukierunkowa szybki insert O(1), wolny dostęp O(n)
HashSet<E> tablica haszująca O(1) add/contains, brak porządku
LinkedHashSet<E> hash + lista O(1) operacje, zachowuje kolejność wstawiania
TreeSet<E> drzewo czerwono-czarne O(log n), posortowane
HashMap<K,V> tablica haszująca O(1) get/put, brak porządku kluczy
TreeMap<K,V> drzewo czerwono-czarne O(log n), posortowane klucze
ArrayDeque<E> tablica cykliczna szybszy stos/kolejka niż Stack/LinkedList
→ Slide 15

Operacje na kolekcjach

Podstawowe operacje interfejsu Collection:

List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
 
list.add("D");              // dodaj na koniec
list.add(1, "X");           // wstaw na pozycję 1
list.remove("B");           // usuń element
list.remove(0);             // usuń po indeksie
boolean has = list.contains("C");  // sprawdź obecność
int size = list.size();
 
// Iteracja
for (String s : list) System.out.println(s);
 
// Iterator (z możliwością usuwania)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("X")) it.remove();
}
 
// Operacje grupowe
list.addAll(Arrays.asList("E", "F"));
list.removeIf(s -> s.equals("A"));  // Java 8+
→ Slide 16

Operacje na mapach

Map<String, Integer> scores = new HashMap<>();
 
scores.put("Ania", 90);
scores.put("Bartosz", 75);
scores.put("Ola", 88);
 
int score = scores.get("Ania");          // 90
scores.getOrDefault("Kasia", 0);         // 0 (brak klucza)
scores.putIfAbsent("Kasia", 60);         // dodaj jeśli brak
scores.replace("Bartosz", 80);           // zastąp wartość
 
// Iteracja
for (Map.Entry<String, Integer> e : scores.entrySet()) {
    System.out.println(e.getKey() + " -> " + e.getValue());
}
 
// Java 8+
scores.forEach((k, v) -> System.out.println(k + ": " + v));
scores.compute("Ania", (k, v) -> v + 10); // Ania = 100
→ Slide 17

Klasa Collections - algorytmy

Narzędziowa klasa java.util.Collections - metody statyczne:

List<Integer> nums = new ArrayList<>(Arrays.asList(3, 1, 4, 1, 5, 9));
 
Collections.sort(nums);                    // sortowanie rosnące
Collections.sort(nums, Comparator.reverseOrder()); // malejące
Collections.shuffle(nums);                 // losowe tasowanie
Collections.reverse(nums);                 // odwrócenie
Collections.swap(nums, 0, 1);             // zamiana elementów
 
int max = Collections.max(nums);           // maksimum
int min = Collections.min(nums);           // minimum
int freq = Collections.frequency(nums, 1); // liczba wystąpień
 
// Synchronizacja (thread-safe wrapper)
List<Integer> syncList = Collections.synchronizedList(nums);
// Niemodyfikowalna kopia
List<Integer> readOnly = Collections.unmodifiableList(nums);
→ Slide 18

Strumienie - wprowadzenie

List<String> names = Arrays.asList("Ania", "Bartosz", "Ola", "Adam");
 
long count = names.stream()               // tworzenie strumienia
    .filter(n -> n.startsWith("A"))       // pośrednia: filtrowanie
    .map(String::toUpperCase)             // pośrednia: mapowanie
    .count();                             // końcowa: liczenie
 
System.out.println(count); // 2
→ Slide 19

Tworzenie strumieni

// Ze kolekcji
List<String> list = List.of("a", "b", "c");
Stream<String> s1 = list.stream();
Stream<String> s2 = list.parallelStream();  // równoległy
 
// Z tablicy
Stream<String> s3 = Arrays.stream(new String[]{"x", "y"});
 
// Stream.of
Stream<Integer> s4 = Stream.of(1, 2, 3, 4, 5);
 
// Strumienie liczbowe (bez autoboxing)
IntStream ints = IntStream.range(1, 11);       // 1..10
LongStream longs = LongStream.rangeClosed(1, 5); // 1..5 włącznie
DoubleStream doubles = DoubleStream.of(1.5, 2.5);
 
// Nieskończone strumienie
Stream<Integer> evens = Stream.iterate(0, n -> n + 2);
Stream<Double> randoms = Stream.generate(Math::random);
→ Slide 20

Operacje pośrednie na strumieniach

List<String> words = Arrays.asList("hello", "world", "java", "stream");
 
List<String> result = words.stream()
    .filter(w -> w.length() > 4)          // "hello", "world", "stream"
    .map(String::toUpperCase)             // "HELLO", "WORLD", "STREAM"
    .sorted()                             // "HELLO", "STREAM", "WORLD"
    .collect(Collectors.toList());
 
// flatMap - spłaszczanie
List<List<Integer>> nested = List.of(List.of(1, 2), List.of(3, 4));
List<Integer> flat = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList()); // [1, 2, 3, 4]
→ Slide 21

Operacje końcowe na strumieniach

List<Integer> nums = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// Zbieranie do kolekcji
List<Integer> evens = nums.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());       // [2, 4, 6, 8, 10]
 
// Redukcja
int sum = nums.stream().reduce(0, Integer::sum); // 55
Optional<Integer> max = nums.stream().max(Integer::compareTo); // 10
 
// Statystyki
IntSummaryStatistics stats = nums.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();
System.out.println(stats.getAverage()); // 5.5
 
// Sprawdzanie warunków
boolean anyEven = nums.stream().anyMatch(n -> n % 2 == 0); // true
boolean allPos  = nums.stream().allMatch(n -> n > 0);       // true
boolean noneNeg = nums.stream().noneMatch(n -> n < 0);      // true
 
// Znalezienie elementu
Optional<Integer> first = nums.stream().filter(n -> n > 5).findFirst();
→ Slide 22

Operacje IO - wprowadzenie

// Ścieżki (java.nio.file.Path)
Path path = Path.of("data", "plik.txt");   // Java 11+
Path abs  = path.toAbsolutePath();
Path parent = path.getParent();            // "data"
String name = path.getFileName().toString(); // "plik.txt"
→ Slide 23

Odczyt i zapis plików

// Zapis do pliku (java.nio - zalecane)
Path path = Path.of("wyniki.txt");
List<String> lines = List.of("Linia 1", "Linia 2", "Linia 3");
Files.write(path, lines, StandardCharsets.UTF_8);
 
// Odczyt całego pliku
String content = Files.readString(path, StandardCharsets.UTF_8); // Java 11+
List<String> readLines = Files.readAllLines(path, StandardCharsets.UTF_8);
 
// Odczyt wiersz po wierszu (duże pliki)
try (BufferedReader reader = Files.newBufferedReader(path)) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}
 
// Zapis za pomocą BufferedWriter
try (BufferedWriter writer = Files.newBufferedWriter(path,
        StandardOpenOption.APPEND)) {
    writer.write("Dodatkowa linia");
    writer.newLine();
}
→ Slide 24

Try-with-resources

// Stary sposób (przed Java 7)
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("plik.txt"));
    System.out.println(reader.readLine());
} finally {
    if (reader != null) reader.close();
}
 
// Try-with-resources (Java 7+) - zalecane
try (BufferedReader reader = new BufferedReader(new FileReader("plik.txt"))) {
    System.out.println(reader.readLine());
} // reader.close() wywołane automatycznie
 
// Wiele zasobów
try (InputStream in  = new FileInputStream("wejście.bin");
     OutputStream out = new FileOutputStream("wyjście.bin")) {
    in.transferTo(out); // Java 9+
}
→ Slide 25

Ćwiczenia praktyczne

Ćwiczenie 1: Monte Carlo - przybliżanie liczby $\pi$

Zaimplementuj poniższe zadania, korzystając z kolekcji, strumieni i operacji IO.

Wyznacz wartość przybliżoną liczby $\pi$ metodą Monte Carlo: losuj punkty w kwadracie o boku 1, sprawdź ile z nich trafia do ćwiartki koła wpisanej w ten kwadrat, czyli takich, które spełniają warunek $$x^2 + y^2 \leq 1$$ Przybliżona liczba $\pi \approx 4\frac{k}{n} $, gdzie $k$ to liczba punktów trafiających do ćwiartki koła, a $n$ to całkowita liczba losowanych punktów.

Wykonaj symulację dla 1 miliona losowych punktów i wypisz wynik.

W rozwiązaniu wykorzystaj kolekcje i strumienie. Porównaj czas wykonania algorytmu dla różnych implementacji:

Do pomiaru czasu wykonania użyj System.nanoTime() lub System.currentTimeMillis().

Ćwiczenie 2: Odczyt danych z pliku i przetwarzanie

Wczytaj plik CSV tips.csv i wyznacz średnią wartość rachunku (kolumna total_bill) dla każdej płci (kolumna sex). Użyj strumieni i kolekcji do grupowania danych.

Format pliku tips.csv:

"total_bill","tip","sex","smoker","day","time","size"
16.99,1.01,"Female","No","Sun","Dinner",2
10.34,1.66,"Male","No","Sun","Dinner",3
...
→ Slide 26

Zadanie 4. Słownik wyrazów

Napisz program generujący słownik wyrazów zawartych w pliku tekstowym. Program wczytuje nazwę pliku ze standardowego wejścia, a następnie wyświetla posortowaną alfabetycznie listę wyrazów wraz z liczbą ich wystąpień.

Przykładowe wyjście:

Podaj nazwę pliku: tekst.txt
ala      5
kota     2
ma       1

Wymagania: