CodeGym /Kurslar /JAVA 25 SELF /Zipləmə (zip), axınların yaradılması (iterate, generate)

Zipləmə (zip), axınların yaradılması (iterate, generate)

JAVA 25 SELF
Səviyyə , Dərs
Mövcuddur

1. zip ilə tanışlıq

Proqramlaşdırmada zip termini (və ya “zipləmə”, “birləşdirmə”) iki (və ya daha çox) siyahını götürüb onları bir cütlər axınına birləşdirən əməliyyatı ifadə edir: hər siyahıdan bir element olmaqla. Əgər Python ilə tanışsınızsa, orada məhz bunu edən zip funksiyası var.

Nümunə:

  • Adlar siyahısı: ["Anya", "Boris", "Vika"]
  • Yaşlar siyahısı: [20, 25, 19]
  • “Zipləmə”dən sonra alınacaq: [("Anya", 20), ("Boris", 25), ("Vika", 19)]

Bu, iki kolleksiyanı sinxron emal etmək lazım olanda rahatdır — məsələn, ad və yaşın birlikdə getdiyi obyektlər yaratmaq üçün.

Niyə Stream API-də zip yoxdur?
Standart Stream API-də (Java 22-yə qədər) zip metodu yoxdur. Səbəb odur ki, axınlar sonsuz ola bilər, kolleksiyaların uzunluqları fərqli ola bilər və bir kolleksiya digərindən uzun olduqda necə davranmalı olduğun həmişə aydın deyil. Amma praktikada zip tez-tez lazım olur.

2. Java-da zip-in reallaşdırılması: daxili metod olmadan necə yaşamaq

Ən sadə üsul — indekslər vasitəsilə

Əgər sizdə iki siyahı varsa və bunun “adi” kolleksiyalar olduğuna əminsinizsə (məsələn, List<A>, List<B>), indekslərdən istifadə etmək olar:

import java.util.*;
import java.util.stream.*;

public class ZipExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Anya", "Boris", "Vika");
        List<Integer> ages = Arrays.asList(20, 25, 19);

        int size = Math.min(names.size(), ages.size());

        List<Person> people = IntStream.range(0, size)
            .mapToObj(i -> new Person(names.get(i), ages.get(i)))
            .collect(Collectors.toList());

        people.forEach(System.out::println);
    }

    static class Person {
        String name;
        int age;
        Person(String name, int age) { this.name = name; this.age = age; }
        public String toString() { return name + " (" + age + ")"; }
    }
}

Çıxış:

Anya (20)
Boris (25)
Vika (19)

Nə baş verir?

  • İki siyahının minimal ölçüsünü götürürük — sərhəddən kənara çıxmamaq üçün.
  • IntStream.range(0, size) istifadə edirik — indekslər axını yaradırıq.
  • Hər indeks üçün hər iki siyahıdan bir element götürüb onları “birləşdiririk”.
  • Nəticəni Collectors.toList() ilə siyahıya toplayırıq.

Stream<T> üçün zip etmək olar?

Texniki cəhətdən — bəli, amma bu, yalnız hər iki axın sonlu və arxasında indekslə sürətli giriş imkanı olan struktur (yəni, əslində elə List) olduqda rahatdır. “Həqiqi” axınlar (məsələn, sonsuz) üçün korrekt zip reallaşdırması daha çətindir və əlavə məntiq tələb edir.

Alternativ variantlar: üçüncü tərəf kitabxanaları

“Hazır” zip istəyirsinizsə, aşağıdakı kitabxanalardan istifadə edə bilərsiniz:

  • org.apache.commons.lang3.Streams.zip (Apache Commons Lang 3.10+)
  • io.vavr.collection.Stream.zip (Vavr)
  • com.codepoetics.protonpack.StreamUtils.zip (ProtonPack)

Bu kursda standart kitabxanadan istifadə edirik, buna görə də “əl ilə” üsulları nəzərdən keçiririk.

3. zip istifadəsinin praktiki nümunələri

Nümunə 1. İki kolleksiyanın sinxron keçidi (elementlərin cəmlənməsi)

List<Integer> a = Arrays.asList(1, 2, 3, 4);
List<Integer> b = Arrays.asList(10, 20, 30, 40);

List<Integer> sums = IntStream.range(0, Math.min(a.size(), b.size()))
    .mapToObj(i -> a.get(i) + b.get(i))
    .collect(Collectors.toList());

System.out.println(sums); // [11, 22, 33, 44]

Nümunə 2. Sətirlərin və simvolların ziplənməsi

String[] words = {"cat", "dog", "fox"};
char[] marks = {'!', '?', '.'};

List<String> zipped = IntStream.range(0, Math.min(words.length, marks.length))
    .mapToObj(i -> words[i] + marks[i])
    .collect(Collectors.toList());

System.out.println(zipped); // [cat!, dog?, fox.]

Vizualizasiya (sxem)

names:   [Anya] [Boris] [Vika]
ages:    [20 ] [25   ] [19  ]
          |      |      |
zip ---> (Anya,20) (Boris,25) (Vika,19)

4. Stream.iterate və Stream.generate — yeni axınların yaradılması

Bəzən təkcə mövcud kolleksiyaları emal etmək deyil, həm də “yerindəcə” yeni ardıcıllıqlar yaratmaq lazımdır. Bunun üçün Stream API-də iki faydalı metod var:

  • Stream.iterate — qaydaya əsasən ardıcıllıq yaradır (məsələn, arifmetik proqressiya).
  • Stream.generate — hər elementin Supplier vasitəsilə hesablandığı axın yaradır (məsələn, təsadüfi ədəd, cari vaxt və s.).

Stream.iterate

Sintaksis:

Stream.iterate(seed, unaryOperator)
  • seed — başlanğıc dəyər;
  • unaryOperator — növbəti elementi hesablayan funksiya.

Nümunə 1: Arifmetik proqressiya

Stream<Integer> numbers = Stream.iterate(0, n -> n + 2); // 0, 2, 4, 6, ...
numbers.limit(5).forEach(System.out::println);
// Çap edəcək: 0 2 4 6 8

Nümunə 2: Tarixlərin generasiyası

import java.time.LocalDate;

Stream<LocalDate> days = Stream.iterate(LocalDate.now(), date -> date.plusDays(1));
days.limit(3).forEach(System.out::println);
// Məsələn: 2024-06-09, 2024-06-10, 2024-06-11

Nümunə 3: Sonsuz axın — limit etməyi unutmayın!

Stream<Integer> endless = Stream.iterate(1, n -> n * 2);
endless.limit(5).forEach(System.out::println); // 1 2 4 8 16

Java 9+ versiyasında şərt predikatı ilə aşırı yüklənmiş variant mövcuddur:

Stream.iterate(0, n -> n < 10, n -> n + 2)
    .forEach(System.out::println); // 0 2 4 6 8

Stream.generate

Sintaksis:

Stream.generate(Supplier<T>)

Hər element Supplier.get() çağırışı ilə hesablanır.

Nümunə 1: Təsadüfi ədədlər

import java.util.Random;

Random random = new Random();
Stream<Integer> randoms = Stream.generate(random::nextInt);
randoms.limit(5).forEach(System.out::println);

Nümunə 2: Eyni dəyərlərin generasiyası

Stream<String> stars = Stream.generate(() -> "*");
stars.limit(4).forEach(System.out::print); // ****

Nümunə 3: Unikal identifikatorlar

import java.util.UUID;

Stream<String> uuids = Stream.generate(() -> UUID.randomUUID().toString());
uuids.limit(3).forEach(System.out::println);

Vizualizasiya (sxem)

Stream.iterate:

[seed] -> op() -> op() -> op() -> ...
       n      n+1    n+2    n+3

Stream.generate:

Supplier() -> Supplier() -> Supplier() -> ...
    val1         val2          val3

5. İstifadə nümunələri: tətbiq üçün məlumatların generasiyası

Tutaq ki, Student sinfimiz var:

class Student {
    String name;
    int age;
    Student(String name, int age) { this.name = name; this.age = age; }
    public String toString() { return name + " (" + age + ")"; }
}

Nümunə 1. Test tələbələrinin generasiyası

List<String> names = Arrays.asList("Anya", "Boris", "Vika", "Gleb", "Dasha");
Stream<Student> students = IntStream.range(0, names.size())
    .mapToObj(i -> new Student(names.get(i), 18 + i));

students.forEach(System.out::println);
// Anya (18), Boris (19), Vika (20), Gleb (21), Dasha (22)

Nümunə 2. Təsadüfi tələbələrin generasiyası

Random random = new Random();
List<String> pool = Arrays.asList("Ira", "Oleg", "Maksim", "Tanya", "Sergey");

Stream<Student> randomStudents = Stream.generate(() ->
    new Student(
        pool.get(random.nextInt(pool.size())),
        18 + random.nextInt(5)
    )
);

randomStudents.limit(3).forEach(System.out::println);
// Məsələn: Tanya (19), Oleg (21), Ira (20)

Nümunə 3. Hesabat üçün tarix ardıcıllığının generasiyası

import java.time.LocalDate;

Stream<LocalDate> dates = Stream.iterate(LocalDate.of(2024, 6, 1), d -> d.plusDays(1));
dates.limit(5).forEach(System.out::println);
// 2024-06-01, 2024-06-02, ..., 2024-06-05

Müqayisə: zip, iterate, generate nə zaman istifadə olunmalıdır

  • zip — iki (və ya daha çox) siyahı/axını indekslər üzrə birləşdirib sinxron emal etmək lazım olanda.
  • iterate — qaydaya əsasən ardıcıllıq lazım olanda (rəqəmlər, tarixlər, addımlar).
  • generate — hər bir element müstəqil hesablandıqda (təsadüfi dəyərlər, unikal ID-lər).

7. zip və axınların generasiyası ilə işləyərkən tipik xətalar

Xəta № 1: limit olmadan qeyri-məhdud axın. Əgər Stream.iterate və ya Stream.generate-dən limit ilə məhdudlaşdırmadan istifadə etsəniz, proqram ilişə bilər və ya bütün yaddaşı “yeyə” bilər.

Stream.generate(() -> 1).forEach(System.out::println); // Heç vaxt bitməyəcək!

Xəta № 2: zip zamanı fərqli uzunluqların yanlış emalı. Əgər bir siyahı digərindən uzundursa, minimal uzunluğa qədər getmək lazımdır, əks halda IndexOutOfBoundsException alacaqsınız.

Xəta № 3: adi Stream<T> üzərində zip etməyə cəhd. Adi axınlarda indekslə giriş yoxdur. Praktik zip daha çox List üçün edilir.

Xəta № 4: Axın yaradılarkən kolleksiyanı dəyişdirmək. Əgər axın gedərkən kolleksiyanı dəyişsəniz, ConcurrentModificationException ala bilərsiniz. Yeni məlumatlar yaradın — köhnəni “yolda” dəyişməyin.

Xəta № 5: Sıralamanın itməsi. Sıra önəmlidirsə (məsələn, zip zamanı), List istifadə edin, Set yox — əks halda elementlərin sırası qeyri-müəyyən olacaq.

Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION