Gyakorlati alapok III.

Halmaz (Set)


Ismételjük meg az előző fejezet legfontosabb megállapításait:

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

a Set fő jellegzetessége, hogy nem tartalmazhat duplikált-többszörös elemeket. 2 db Set akkor egyenlő, ha ugyanazon elemeket tartalmazzák.

 

Valós életből vett példák: bármilyen általánosabb megközelítésű katalógus, például állat-, és növényfajok, autómárkák, országnevek, megyenevek, fegyvernemek, stb. adatbázisa.

Eleddig kellő mennyiségű kód került bemutatásra ahhoz, hogy végre konkrét kollekció-implementálással próbálkozzunk, így rögtön csapjunk is bele a kódlecsóba (micsoda képzavar)!

A legelső, egyúttal talán legizgalmasabb a Set azon tulajdonságának vizsgálata, miszerint nem tartalmazhat duplikált-többszörös elemeket, először tehát ezt ellenőrizzük le. Egy egyszerű, a Set interfészből továbbspecializált HashSet típusú halmazt hozunk létre és megpróbáljuk kétszer beletenni a 4 sztringet:
 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<String> halmaz = new HashSet<String>();
    halmaz.add("2");
    halmaz.add("1");
    halmaz.add("5");
    halmaz.add("4");
    halmaz.add("4");
    halmaz.add("3");
    System.out.println(halmaz);
    }
}
 

Végeredmény:
[1, 2, 3, 4, 5]


Próbálkozásunk nem sikerült, a halmaz az elemeket garantáltan csakis egyszeresen fogja tartalmazni. Egyúttal vegyük észre, milyen egyszerűen tudtuk kinyomtatni a konzolra a halmaz tartalmát, valamint a Java-rendszer valamilyen rendezőelv szerint rögtön rendezte is. Azt gondolnánk, hogy ez szám szerinti növekvő sor, ami csak látszólag igaz, mert bár itt a belső, String-rendezési elv és a szám szerinti növekvés speciel megegyezik, de valójában nem azonosak. Ennek bizonyítására kissé át kell alakítanunk a kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<String> halmaz = new HashSet<String>();
    halmaz.add("23");
    halmaz.add("110");
    halmaz.add("555");
    halmaz.add("40");
    halmaz.add("40");
    halmaz.add("3456");
    System.out.println(halmaz);
    }
}
 

Végeredmény:
[110, 23, 555, 3456, 40]


Itt már láthatóan nincs növekvő számsor. Ebből és más tapasztalatokból levonhatunk általánosabb következtetéseket is:

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!


a HashSet az elemeket alapértelmezésben rendezetlenül, egy hash-táblában tárolja. Ez a leggyorsabb elemhozzáférést biztosítja, ha a kollekció bejárása nem fontos.

A Set következő specializációja, a TreeSet az elemeket egy bináris fában (nevezik piros-fekete fának is)...

 

www.informatika-programozas.hu

 

...tárolja és ez bár az előzőnél rendezettebb szerkezetet jelent, mégsem oldja fel a fenti rendezési problémát:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<String> halmaz = new TreeSet<String>();
    halmaz.add("23");
    halmaz.add("110");
    halmaz.add("555");
    halmaz.add("40");
    halmaz.add("40");
    halmaz.add("3456");
    System.out.println(halmaz);
    }
}
 

Végeredmény:
[110, 23, 3456, 40, 555]

Természetesen hiába erőltetünk String típusú elemeken számokhoz köthető rendezési szempontokat, ehhez nyilvánvalóan számokká kell alakítanunk az elemtípust (itt Integer, amely típus ráadásul objektumtípus is). Egyúttal a 40 esetében megint csak megpróbálkozunk a duplikált elemtartalommal:


www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 


import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz = new TreeSet<Integer>();
    halmaz.add(23);
    halmaz.add(110);
    halmaz.add(555);
    halmaz.add(40);
    halmaz.add(40);
    halmaz.add(3456);
    System.out.println(halmaz);
    }
}
 

Végeredmény:
[23, 40, 110, 555, 3456]

Az elemduplikálás most sem volt sikeres, ám az elemrendezést a TreeSet alapértelmezésben biztosította. A művelet természetesen működik for-each ciklussal is:
 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 


 

import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz = new TreeSet<Integer>();
    halmaz.add(23);
    halmaz.add(110);
    halmaz.add(555);
    halmaz.add(40);
    halmaz.add(40);
    halmaz.add(3456);
    for(int i : halmaz) {
        System.out.print(i + " ");
        }
    }
}
 

Végeredmény:
23 40 110 555 3456

Most nézzünk meg néhány további metódust. Például lekérdezzük a Set méretét (size()), ellenőrizzük, hogy üres-e (isEmpty()), eltávolítjuk az utolsóként betett elemet (remove(6)), végül töröljük a teljes tartalmát (clear()):
 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 


 

import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz = new TreeSet<Integer>();
    halmaz.add(2);
    halmaz.add(1);
    halmaz.add(5);
    halmaz.add(3);
    halmaz.add(4);
    halmaz.add(6);
    System.out.println("A Set elemei: " + halmaz);
    System.out.println("A Set mérete: " + halmaz.size() + " elem");
    System.out.println("A Set üres? " + halmaz.isEmpty());
    halmaz.remove(5);
    System.out.println("A Set elemei az 5. elem eltávolítása után: " + halmaz);
    halmaz.clear();
    System.out.println("A Set elemeinek törlése után: " + halmaz);
    }
}
 

Végeredmény:
A Set elemei: [1, 2, 3, 4, 5]
A Set mérete: 5 elem
A Set üres? false
A Set elemei az 5. elem eltávolítása után: [1, 2, 3, 4]
A Set elemeinek törlése után: []


Most fogalmazzunk meg egy olyan kódot, amelyik egy bemeneti tömbből leválogatja az egyszeres és duplikált elemeket (4 és 5). Valósítsuk ezt meg Set-ek segítségével:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 



import java.util.*;

public class Main {
public static void main(String args[]) {
    String [] bemenetiTomb = new String [] {"1", "2", "3", "4", "4", "5", "5", "6"};
    Set<String> halmazEgyediElemeknek = new HashSet<String>();
    Set<String> halmazDuplikaltElemeknek = new HashSet<String>();
    for(String elem : bemenetiTomb)
        if(!halmazEgyediElemeknek.add(elem))
            halmazDuplikaltElemeknek.add(elem);
            halmazEgyediElemeknek.removeAll(halmazDuplikaltElemeknek);
            System.out.println("Egyedi elemek: " + halmazEgyediElemeknek);
            System.out.println("Duplikált elemek: " + halmazDuplikaltElemeknek);
    }
}
 

Végeredmény:
Egyedi elemek: [1, 2, 3, 6]
Duplikált elemek: [4, 5]


Ismétlésképpen gyorsan emlékezzünk vissza a tömeges műveletek definícióira:

A Set alapszintű vizsgálatai után érdemes néhány jellemző tömeges műveletet is implementálnunk (bulk operations). Ehhez 2 db Set-re lesz szükségünk, amelyek elemei különbözni fognak, kivéve az utolsó 2 elem esetében (10 és 11). Talán a legtipikusabb példát vesszük erre: a páros-páratlan számok vizsgálatát.

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 


import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmazParatlan = new HashSet<Integer>();
    halmazParatlan.addAll(Arrays.asList(new Integer[] {1, 3, 5, 7, 9, 10, 11}));
    Set<Integer> halmazParos = new HashSet<Integer>();
    halmazParos.addAll(Arrays.asList(new Integer[] {2, 4, 6, 8, 10, 11}));

    System.out.println("A halmazok külön:" + "\n" + halmazParos + "\n" + halmazParatlan);

    System.out.println();

    Set<Integer> halmazUnio = new HashSet<Integer>(halmazParatlan);
    halmazUnio.addAll(halmazParos);
    System.out.print("A 2 halmaz úniója: " + halmazUnio);

    System.out.println();

    Set<Integer> halmazKozosResz = new HashSet<Integer>(halmazParatlan);
    halmazKozosResz.retainAll(halmazParos);
    System.out.print("A 2 halmaz közös része: " + halmazKozosResz);

    System.out.println();

    Set<Integer> halmazKulonbseg = new HashSet<Integer>(halmazParatlan);
    halmazKulonbseg.removeAll(halmazParos);
    System.out.print("A 2 halmaz különbsége: " + halmazKulonbseg);
    }
}

 

Végeredmény:
A halmazok külön:
[2, 4, 6, 8, 10, 11]
[1, 3, 5, 7, 9, 10, 11]

A 2 halmaz úniója: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
A 2 halmaz közös része: [10, 11]
A 2 halmaz különbsége: [1, 3, 5, 7, 9]


A példák bemutatása során érdekes dolgot vehettünk észre. Példányosításkor...

 

Set<Integer> halmazKozosResz = new HashSet<Integer>(halmazParatlan);

 

...a bal oldali, úgynevezett referenciatípus interfész típusú (Set), míg a jobb oldali, az objektumtípus már konkrét, példányosított osztálytípus (HashSet).

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Ezt az eljárást sok helyen alkalmazzák, mert nagyon praktikus, ha a Set konkrét, implementációs típusát kell megváltoztatni, hiszen ilyenkor csupán át kell írni a típust ahhoz, hogy az új, kívánt tulajdonságok elérhetővé váljanak.

Említsük még meg a Set egy további specializációját, neve LinkedHashSet. Ebben a halmaztípusban az elemek között (rejtett) mutatók tartják fent a kapcsolatot. Ennek eredménye például, hogy a halmazelemek listázása az elembevitel sorrendjében történik. Ezt láthatjuk az alábbi futtatható Java-kódban. Illetve újfent megpróbálkozunk duplikált elembevitellel (halmaz.add(4)), amely természetesen a Set jellegéből adódóan nem fog sikerülni:


www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 


 

 

 

import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz = new LinkedHashSet<Integer>();
    halmaz.add(5);
    halmaz.add(1);
    halmaz.add(3);
    halmaz.add(2);
    halmaz.add(4);
    halmaz.add(4);
    System.out.println(halmaz);
    }
}
 

Végeredmény:
[5, 1, 3, 2, 4]

Utolsó tulajdonságvizsgálatként pedig ellenőrizzük le a fent említett egyenlőségállításunkat:

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

2 db Set akkor egyenlő, ha ugyanazon elemeket tartalmazzák.

 

Ehhez természetesen 2 db halmazt kell használnunk, amelyeket kissé fapados módszerrel, különböző sorrendben és elemredundanciával (4-4 és 5-5), de lényegében azonosan fogunk feltölteni. Ezért az equals() függvény TRUE értéket fog visszaadni:


www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 


 

 

 

import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz1 = new HashSet<Integer>();
    halmaz1.add(5);
    halmaz1.add(1);
    halmaz1.add(3);
    halmaz1.add(2);
    halmaz1.add(4);
    halmaz1.add(4);
    System.out.println("1. halmaz: " + halmaz1);
    Set<Integer> halmaz2 = new HashSet<Integer>();
    halmaz2.add(1);
    halmaz2.add(5);
    halmaz2.add(5);
    halmaz2.add(3);
    halmaz2.add(2);
    halmaz2.add(4);
    System.out.println("2. halmaz: " + halmaz2);
    System.out.println("A halmazok egyenlők?");
    System.out.println(halmaz1.equals(halmaz2));
    }
}

 

Végeredmény:
1. halmaz: [1, 2, 3, 4, 5]
2. halmaz: [1, 2, 3, 4, 5]
A halmazok egyenlők?
true

 

Nyilvánvalóan az egyenlőség megbontása esetén a végeredmény már false lesz:


www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 


 

 

 

import java.util.*;

public class Main {
public static void main(String[] args) {
    Set<Integer> halmaz1 = new HashSet<Integer>();
    halmaz1.add(5);
    halmaz1.add(1);
    halmaz1.add(3);
    halmaz1.add(2);
    halmaz1.add(4);
    halmaz1.add(4);
    System.out.println("1. halmaz: " + halmaz1);
    Set<Integer> halmaz2 = new HashSet<Integer>();
    halmaz2.add(1);
    halmaz2.add(6);
    halmaz2.add(5);
    halmaz2.add(3);
    halmaz2.add(2);
    halmaz2.add(4);
    System.out.println("2. halmaz: " + halmaz2);
    System.out.println("A halmazok egyenlők?");
    System.out.println(halmaz1.equals(halmaz2));
    }
}

 

Végeredmény:
1. halmaz: [1, 2, 3, 4, 5]
2. halmaz: [1, 2, 3, 4, 5, 6]
A halmazok egyenlők?
false

 

Most pedig nézzünk meg egy rövid hasznossági összefoglalót a különböző halmaztípusokról!

Használjunk HashSet-et, ha:

Használjunk TreeSet-et, ha:

Használjunk LinkedHashSet-et, ha: