import java.util.ArrayList;

/**
 * Set-Variante A
 * 
 * @author Rainer Helfrich
 * @version 27.9.2020
 */
public class SetVarianteA extends Set
{
    private ArrayList<Integer>[] daten;
    private int anzahl;

    /**
     * Erzeugt ein neues leeres Set
     */
    public SetVarianteA()
    {
        this(10);
    }

    private SetVarianteA(int capacity)
    {
        if (capacity < 10)
        {
            capacity = 10;
        }
        daten = new ArrayList[capacity*2];
        anzahl = 0;
    }

    protected int[] alsArray()
    {
        int[] ergebnis = new int[anzahl];
        int index = 0;
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int x : l)
                {
                    ergebnis[index] = x;
                    index++;
                }
            }
        }
        return ergebnis;
    }

    /**
     * Gibt zurück, ob die Menge kein Element enthält
     * @return true, wenn die Menge leer ist; false sonst
     */
    public boolean istLeer()
    {
        for (ArrayList<Integer> l : daten)
        {
            if (l != null && !l.isEmpty())
            {
                return false;
            }
        }
        return true;
    }

    /**
     * Gibt die Mächtigkeit der Menge zurück
     * @return Die Anzahl der Elemente in der Menge
     */
    public int anzahl()
    {
        return anzahl;
    }

    /**
     * Bestimmt die Differenzmenge der Menge selbst und der Menge s.
     * @param s Die Menge, die von dieser Menge abgezogen werden soll.
     * @return Ein neues Set, das alle Elemente dieser Menge enthält, die nicht in s enthalten sind.
     */
    public Set differenz(Set s)
    {
        Set ergebnis = new SetVarianteA(anzahl);
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int n : l)
                {
                    if (!s.enthaelt(n))
                    {
                        ergebnis.einfuegen(n);
                    }
                }
            }
        }
        return ergebnis;
    }

    /**
     * Gibt zurück, ob diese Menge eine Untermenge der Menge s ist.
     * @param s Die Menge, die geprüft werden soll.
     * @return true, wenn jedes Element dieser Menge in s enthalten ist; false sonst
     */
    public boolean untermenge(Set s)
    {
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int n : l)
                {
                    if (!s.enthaelt(n))
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Bestimmt die Vereinigungsmenge der Menge selbst und der Menge s.
     * @param s Die Menge, mit der die Vereinigungsmenge bestimmt werden soll
     * @return Ein neues Set, das die Vereinigungsmenge der beiden Mengen repräsentiert.
     */
    public Set vereinigungsmenge(Set s)
    {
        SetVarianteA ergebnis = new SetVarianteA(s.anzahl() + anzahl());
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int n : l)
                {
                    ergebnis.einfuegen(n);
                }
            }
        }
        for (int n : s.alsArray())
        {
            ergebnis.einfuegen(n);
        }
        return ergebnis;
    }

    /**
     * Fügt einen neuen Wert in die Menge ein, falls er nicht bereits enthalten ist.
     * @param wert Der einzufügende Wert
     */
    public void einfuegen(int n)
    {
        if ((double)anzahl / daten.length > 0.5)
        {
            rehash();
        }
        int hash = n % daten.length;
        if (daten[hash] == null)
        {
            daten[hash] = new ArrayList<Integer>(2);
        }
        if (!daten[hash].contains(n))
        {
            anzahl++;
            daten[hash].add(n);
        }
    }

    private void rehash()
    {
        ArrayList<Integer>[] tmp = new ArrayList[2*daten.length+1];
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int n : l)
                {
                    int hash = n % tmp.length;
                    if (tmp[hash] == null)
                    {
                        tmp[hash] = new ArrayList<Integer>(2);
                    }
                    tmp[hash].add(n);
                }
            }
        }
        daten = tmp;
    }

    /**
     * Bestimmt die Schnittmenge der Menge selbst und der Menge s.
     * @param s Die Menge, mit der die Schnittmenge bestimmt werden soll
     * @return Ein neues Set, das die Schnittmenge der beiden Mengen repräsentiert.
     */
    public Set schnittmenge(Set s)
    {
        SetVarianteA ergebnis = new SetVarianteA(anzahl);
        for (ArrayList<Integer> l : daten)
        {
            if (l != null)
            {
                for (int n : l)
                {
                    if (s.enthaelt(n))
                    {
                        ergebnis.einfuegen(n);
                    }
                }
            }
        }
        return ergebnis;
    }

    /**
     * Gibt zurück, ob ein bestimmter Wert in der Menge enthalten ist.
     * @param wert Der zu suchende Wert
     */
    public boolean enthaelt(int n)
    {
        int hash = n % daten.length;
        return daten[hash] != null && daten[hash].contains(n);
    }

    /**
     * Entfernt einen Wert aus der Menge, falls er enthalten ist.
     * @param wert Der zu entfernende Wert
     */
    public void entfernen(int n)
    {
        int hash = n % daten.length;
        if (daten[hash] != null)
        {
            if (daten[hash].contains(n))
            {
                anzahl--;
                daten[hash].remove(new Integer(n));
            }
        }
    }
}
