Kleinsten und größten Wert einer ArrayList ermitteln

Manche Zahlen und Objekte speichere ich in einer ArrayList. Hier kann man relativ leicht Objekte hinzufügen (.add()) oder entfernen (.remove()). In diesem Artikel beschreibe ich, wie man den größten und den kleinsten Wert (Minumum und Maximum) einer ArrayList bestimmt.

Für eine ArrayList<Integer>

Am einfachsten ist die Bestimmung des Minimums und des Maximums bei einer Liste von Objekten mit einer natürlichen Reihenfolge, wie z.B. Integer-Zahlen. In dem Falle kann man die statischen Methoden max() und min() der Collections-Klasse verwenden. Je nachdem wie die Liste aufgebaut ist, kann es allerdings auch effizientere Wege geben (siehe Stackoverflow und Zparacha-Blog).

ArrayList<Integer> liste = new ArrayList<Integer>();
liste.add(100);
liste.add(200);
liste.add(110);
liste.add(99);
 
System.out.println("Maximum: " + Collections.max(liste));
System.out.println("Minimum: " + Collections.min(liste));

Vollständiges Beispiel:

package de.fenon.test;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
 
/**
 * Beispiel: Kleinsten und größten Wert in einer ArrayList finden.
 * @author fenon.de
 */
public final class HauptProgramm {
  /** Zahl der Messungen. */
  static final Integer STICHPROBE = 10;
  /** Maximalwert. */
  static final Integer MAXIMUM = 10;
  /** Seed des Zufallszahlen-Generators. */
  static final Integer SEED = 1234567891;
 
  /**
   * Privater Konstruktor.
   * Dadurch kann kein Objekt der Klasse "Hauptprogramm" erzeugt werden.
   */
  private HauptProgramm() {}
 
  /**
   * main()-Methode.
   * @param args - Parameter der Kommandozeile.
   */
  public static void main(final String[] args) {
    ArrayList<Integer> liste = new ArrayList<Integer>();
    // Erzeuge zufällige Messwerte
    Random generator = new Random(SEED);
    for (int i = 0; i < STICHPROBE; i++) {
      liste.add(generator.nextInt(MAXIMUM));
    }
    // Gib die Messwerte aus
    for (Integer element:liste) {
      System.out.println(element);
    }
    // Gib Kleinsten und Größten Wert aus
    System.out.println("Maximum: " + Collections.max(liste));
    System.out.println("Minimum: " + Collections.min(liste));
 
  } // ENDE: main()
 
} // ENDE: HauptProgramm

Für eine ArrayList mit komplexeren Objekten

Man kann allerdings auch den minimalen und maximalen Wert eigener/komplexerer Objekte bestimmen. Im folgenden Beispiel habe ich eine Liste von Objekten des eigenen Typs Website. Mit Hilfe eines Comparators (hier: VergleicheHits) kann festgelegt werden, wie die Objekte verglichen werden sollen. Im Beispiel werden die Website-Objekte dann anhand ihrer Zugriffzahlen verglichen.

Klasse: Website

package de.fenon.Website;
 
/**
 * Informationen über Webseite.
 * @author fenon.de
 */
public class Website {
  /** Zahl der Besucher der Website. */
  private Integer hits;
 
  /**
   * Konstruktor.
   * @param besuche - Zahl der Besucher.
   */
  public Website(final Integer besuche) {
    this.setHits(besuche);
  } // ENDE: Konstruktor
 
  /**
   * Gibt die Besucherzahl der Webseite aus.
   * @return Zahl der Besucher der Website.
   */
  public final Integer getHits() {
    return hits;
  } // ENDE: getHits()
 
  /**
   * Setze die Besucherzahl der Webseite.
   * @param besuche - Zahl der Besucher.
   */
  public final void setHits(final Integer besuche) {
    this.hits = besuche;
  } // ENDE: setHits()
 
} // ENDE: Website

Comparator: VergleicheHits

package de.fenon.Website;
 
import java.util.Comparator;
 
/**
 * Vergleiche Website-Objekte anhand ihrer Hits.
 * @author fenon.de
 */
public class VergleicheHits implements Comparator<Website> {
  /** Konstruktor. */
  public VergleicheHits() {}
 
  @Override
  public final int compare(final Website a, final Website b) {
    if (a.getHits() < b.getHits()) {
      return -1;
    } else if (a.getHits().equals(b.getHits())) {
      return 0;
    } else {
      return 1;
    }
  } // ENDE: compare()
 
} // ENDE: VergleicheHits

Das Beispiel

package de.fenon.test;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;
 
import de.fenon.Website.VergleicheHits;
import de.fenon.Website.Website;
 
/**
 * Beispiel: Kleinsten und größten Wert in einer Liste finden.
 * @author fenon.de
 */
public final class HauptProgramm {
  /** Zahl der Messungen. */
  static final Integer STICHPROBE = 10;
  /** Maximalwert. */
  static final Integer MAXIMUM = 10;
  /** Seed des Zufallszahlen-Generators. */
  static final Integer SEED = 1234567891;
 
  /**
   * Privater Konstruktor.
   * Dadurch kann kein Objekt der Klasse "Hauptprogramm" erzeugt werden.
   */
  private HauptProgramm() {}
 
  /**
   * main()-Methode.
   * @param args - Parameter der Kommandozeile.
   */
  public static void main(final String[] args) {
    ArrayList<Website> seiten = new ArrayList<Website>();
    // Erzeuge zufällige Messwerte
    Random generator = new Random(SEED);
    for (int i = 0; i < STICHPROBE; i++) {
      seiten.add(new Website(generator.nextInt(MAXIMUM)));
    }
    // Gib die Messwerte aus
    for (Website element:seiten) {
      System.out.println("Hits: " + element.getHits());
    }
    // Gib Kleinsten und Größten Wert aus.
    // Mit einem anderen Getter könnte man z.B. den Namen der Website ausgeben.
    System.out.println(
      "Maximum: "
      + Collections.max(seiten, new VergleicheHits()).getHits()
      );
    System.out.println(
      "Minimum: "
      + Collections.min(seiten, new VergleicheHits()).getHits()
      );
  } // ENDE: main()
 
} // ENDE: HauptProgramm

Eine Liste zu einem data.frame konvertieren in R

Manchmal liegen in R Daten in Form einer Liste vor (z.B. die Ausgabe der Funktion hist()). In einer Liste können Daten enthalten sein, die eine unterschiedliche Länge haben. Im Falle von hist() sind z.B. Arrays unterschiedlicher Länge gemeinsam mit einfachen Variablen in einer Liste kombiniert.

Für weitere Analysen kann es jedeoch hilfreich sein, die gleich langen Teile in einen separaten data.frame auszulagern. Dann kann z.B. mit Hilfe der Funktion subset() relativ einfach Bereiche aus den Daten herausfiltern.

In R gibt es keine vorgefertigte Funktion, die direkt Daten vom Typ list in Daten vom Typ data.frame konvertieren kann. Das liegt daran, dass in einem data.frame alle enthaltenen Variablen (in der Regel Arrays) die gleiche Länge haben müssen. Unsere Entscheidung, welche Variablen aus der Liste in den data.frame übernommen werden sollen, müssen wir R mitteilen.

Das folgende Beispiel zeigt, wie man das sehr einfach bewerkstelligen kann:

1
2
3
4
5
6
7
8
# Liste mit Arrays ("x", "y", "sd") und einer Variablen ("name")
liste = list(x=c(1:5), y=c(11:15), sd=c(0,1,2,0,0), name="Meine Liste")
 
# data.frame ("df") erzeugen mit den Variablen "x", "y" und "sd"
df = with(data=liste, expr=data.frame(x, y))
 
# Struktur des data.frames ausgeben
str(df)
'data.frame':	5 obs. of  3 variables:
 $ x : int  1 2 3 4 5
 $ y : int  11 12 13 14 15
 $ sd: num  0 1 2 0 0

Im Anschluss kann man sehr einfach nur bestimmte Teile des data.frame ausgeben lassen.
Das kann man z.B. direkt über den Index (df[zeile, spalte]) oder über die Funktion subset() machen.

1
2
3
4
5
6
7
8
9
# Nur das zweite und vierte Element (Zeile) anzeigen
df[c(2,4),]
 
# Nur die Elemente (Zeilen) anzeigen, bei denen x>2 ist
df[(df$x>2),]
 
# Nur die Elemente (Zeilen) der Variablen (Spalten) x und y ausgeben,
# bei denen sd<1 und x>2 ist
subset(df, subset=(x>2 & sd<1), select=c(x,y))

Klassifizierung von Messwerten in R

Stell Dir vor, Du hast die Länge von 1000 Fischen gemessen. Im Anschluss möchtest Du die eine Häufigkeitsverteilung (Histogramm) der Größen erstellen. Je nachdem wie genau du gemessen hast, wirst du keine zwei Fische mit der gleichen Länge finden. Daher bist Du gut beraten, die Daten zunächst in bestimmte Längenklassen einzuteilen (z.B. „Anzahl von Fischen zwischen 23cm und 24cm“). Für diese Klassifizierung (binning) steht Dir in R die Funktion hist() zur Verfügung.

Nehmen wir mal an, die Längen der Fische folgen einer Normalverteilung. Im Durschnitt haben die Fische eine Länge von 25cm (± 5cm)

1
2
3
# Ziehe Eintausend Zufallszahlen aus einer Normalverteilung
# (Mittelwert: 25; Standardabweichung: 5)
laengen = rnorm(n=1e3, mean=25, sd=5)

Mit der Funktion hist() kannst Du die Daten nun in Klassen einteilen und plotten lassen.

1
2
3
# Klassifiziere die Daten
# (=Erstelle eine Histogramm und stelle es dar)
gebinnt = hist(laengen, plot=TRUE)
Automatisch erstelltes Histogramm der Beispieldaten. Die Klassengrenzen wurden von R bestimmt.
Automatisch erstelltes Histogramm der Beispieldaten. Die Klassengrenzen wurden von R bestimmt.

hist() erstellt nun eine list, in der die Klassengrenzen (breaks), die Häufigkeiten (counts), Dichten (densitiy) und Klassenmitten (mids), sowie der Name der ursprünglichen Variable (xname) und die Information, ob die Klassen alle gleich groß sind (equidist), gespeichert werden:

1
str(gebinnt)
List of 7
 $ breaks     : num [1:9] 5 10 15 20 25 30 35 40 45
 $ counts     : int [1:8] 3 29 140 370 312 129 15 2
 $ intensities: num [1:8] 0.0006 0.0058 0.028 0.074 0.0624 0.0258 0.003 0.0004
 $ density    : num [1:8] 0.0006 0.0058 0.028 0.074 0.0624 0.0258 0.003 0.0004
 $ mids       : num [1:8] 7.5 12.5 17.5 22.5 27.5 32.5 37.5 42.5
 $ xname      : chr "laengen"
 $ equidist   : logi TRUE
 - attr(*, "class")= chr "histogram"

Für weitere Analysen kannst Du selbst noch die Breite der Klassen (breite) und den Anteil der Klassen an der Stichprobe (anteil) zur Liste hinzufügen:

1
2
gebinnt$breite = diff(gebinnt$breaks)
gebinnt$anteil = gebinnt$counts / sum(gebinnt$counts)

Eigene Klassenbreiten festlegen

Du hast natürlich auch die Möglichkeit, selbst Klassengrenzen (breaks) anzugeben. Die Klassen müssen auch nicht alle gleich Groß sein. Bei einigen Daten können z.B. logarithmische Bin-Größen sinnvoll sein.

1
2
3
4
# Klassen mit Klassenbreite 1 (cm) erstellen
gebinnt = hist(
  laengen, plot=T, breaks=c(0:50)
  , xlab="Länge (cm)", ylab="Häufigkeit", main="Häufigkeitsverteilung")
Histogramm mit selbst gewählten Klassengrenzen (hier: Klassenbreite=1cm)
Histogramm mit selbst gewählten Klassengrenzen (hier: Klassenbreite=1cm)

Hierbei muss man allerdings darauf achten, dass keiner der Werte ausgeschlossen wurde, weil er ausserhalb der gewählten Klassen lag bzw. auf die untere (oder obere) Klassengrenze gefallen ist.

Fehler in hist.default(laengen, plot = T, breaks = c(15:50), xlab = "Länge (cm)",  : 
  einige 'x' nicht gezählt: evtl. überdecken die 'breaks' nicht den gesamten Bereich von 'x'

Daher sollte man immer prüfen, ob die Summe der Werte in den Klassen auch tatsächlich der Stichprobengröße entspricht.

# Sind alle Messwerte der Stichpobe im Histogramm berücksichtigt?
sum(gebinnt$counts)

Eventuell muss man dann weitere Klasse hinzunehmen, bzw. die untere (oder obere) Klassengrenze zu einer Klasse hinzu zählen (Option include.lowest=T).

Alternativen

Alternativ zur Funktion hist() könnte man ein Histogramm auch durch die Anwendung der Funktion table() auf gerundete Daten (round()) erstellen: table(round(daten)).

Die Funktion table() ist dazu gedacht die „Anzahl gleicher Werte zu ermitteln“. Das entspricht aber nicht ganz dem Gedanken der Klassifizierung.