Angewandte	
  Informa/k	
  für	
  
Ingenieure/-­‐innen	
  
Michael	
  Zilske	
  
Angenommen,	
  ich	
  möchte	
  	
  
•  eine	
  Textdatei	
  einlesen	
  (zum	
  Beispiel	
  einen	
  Roman),	
  	
  
•  d...
public static void main(String[] args) throws IOException {	
	new CorpusCount().run();	
}	
	
void run() throws IOException...
void readFile(String filename) throws IOException {	
	FileReader fr = new FileReader(filename);	
	BufferedReader br = new ...
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(wo...
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(wo...
private class Entry {	
	String word;	
	int count;	
}	
	
private void countWord(String word) {	
	Entry entry = findEntry(wo...
private class Entry {	
	String word;	
	int count;	
}	
	
List<Entry> entries = new ArrayList<Entry>();	
	
private void coun...
List<Entry> entries = new ArrayList<Entry>();	
	
private Entry findEntry(String word) {	
	for (Entry entry : entries) {	
	...
public static void main(String[] args) {	
	String a = new String(new char[]{'h','e','l','l','o'});	
	String b = new String...
public static void main(String[] args) {	
	String c = "Hello";	
	String d = "Hello";	
	System.out.println(c == d);	
}	
	
t...
public static void main(String[] args) throws IOException {	
	new CorpusCount().run();	
}	
	
void run() throws IOException...
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<En...
java.util	
	
Interface Comparator<T>	
	
int compare(T o1, T o2)	
	
Compares its two arguments for order. Returns a negativ...
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<En...
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<En...
List<Entry> entries = new ArrayList<Entry>();	
	
private class EntryComparator implements
	 	 	 	 	 	 	 	 	 	Comparator<En...
private void write() throws IOException {	
	FileWriter fw = new FileWriter("files/words.txt");	
	BufferedWriter bw = new B...
Wie	
  lange	
  dauert	
  das?	
  
void
	
	
	
	
	
	
	
	
	
}	
  

run() throws IOException {	
	long startTime = System.curr...
Wie	
  lange	
  dauert	
  das?	
  
void run() throws IOException {	
	 	long startTime = System.currentTimeMillis(); 	
	 	r...
Das	
  ist	
  eigentlich	
  schon	
  ziemlich	
  schnell.	
  Ich	
  bin	
  überrascht.	
  
Anscheinend	
  sind	
  Laptops	...
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
	nWords++;	
}	
  

CountWord	
  b...
private Entry findEntry(String word) {	
	for (Entry entry : entries) {	
	 	if (word.equals(entry.word)) {	
	 	 	return ent...
Hypothese:	
  Das	
  Programm	
  wird	
  allmählich	
  langsamer,	
  je	
  mehr	
  
Daten	
  wir	
  schon	
  verarbeitet	
...
private void countWord(String word) {	
	Entry entry = findEntry(word);	
	entry.count++;	
	nWords++;	
	if (nWords % 10000 =...
Die	
  Lösung	
  
Eine	
  Liste,	
  in	
  der	
  linear	
  nach	
  Einträgen	
  gesucht	
  wird,	
  ist	
  keine	
  
adäqu...
Map<String, Entry> entriesMap = new HashMap<String, Entry>();	
	
private Entry findEntry(String word) {	
	Entry entry = en...
private void sort() {	
	entries = new ArrayList<Entry>(entriesMap.values());	
	Collections.sort(entries, new EntryComparat...
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Intermediate execution time:
Interm...
Sta_	
  
Collections.sort(entries, new EntryComparator());	

kann	
  man	
  auch	
  schreiben:	
  
Collections.sort(entrie...
Bleibt	
  zu	
  erklären:	
  Wie	
  macht	
  die	
  HashMap	
  das?	
  

Wikipedia:	
  
Hash	
  Table	
  

Die	
  Einträge...
Upcoming SlideShare
Loading in...5
×

Vorlesung 7: Laufzeit, Maps

315

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
315
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
2
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Vorlesung 7: Laufzeit, Maps

  1. 1. Angewandte  Informa/k  für   Ingenieure/-­‐innen   Michael  Zilske  
  2. 2. Angenommen,  ich  möchte     •  eine  Textdatei  einlesen  (zum  Beispiel  einen  Roman),     •  die  Häufigkeiten  aller  unterschiedlichen  vorkommenden   Wörter  zählen,   •  diese  Wörter  mit  ihren  Häufigkeiten  als  Rangliste  ausgeben.   the to of and a was in I her not be 12231 11979 10565 10285 6627 5341 5334 5206 5015 4115 4078
  3. 3. public static void main(String[] args) throws IOException { new CorpusCount().run(); } void run() throws IOException { readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); sort(); write(); }   Ich  habe  mir  also  drei  Jane-­‐Austen-­‐Romane  aus  dem  Internet   besorgt.  Weil  ich  die  nacheinander  bearbeiten  will,  schreibe  ich   mir  eine  separate  Methode  readFile().  Danach  will  ich  die   gesammelten  Daten  sor/eren  und  dann  herausschreiben.     Da  diese  Methoden  Dateiopera/onen  ausführen,  deklarieren  sie   IOExcep/ons.  Ich  deklariere  sie  weiter.  
  4. 4. void readFile(String filename) throws IOException { FileReader fr = new FileReader(filename); BufferedReader br = new BufferedReader(fr); String line; while ((line = br.readLine()) != null) { String[] words = line.split(" "); for (String word : words) { countWord(word); } } br.close(); }   Diese  Methode  haben  wir  im  Prinzip  letzte  Woche  schon  gesehen.   Man  muss  Reader  und  Writer  übrigens,  wenn  man  sie  nicht  mehr   braucht,  mit  close()  schließen,  denn  Dateizugriffe  sind  vom   Betriebssystem  verwaltete,  beschränkte  Ressourcen.  
  5. 5. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Was  ich  berechnen  möchte,  ist  eine  Tabelle,  deren  Einträge   Wörter  mit  ihren  (bisherigen)  Häufigkeiten  sind.  Für  jedes  Wort,   das  ich  einlese,  suche  ich  den  entsprechenden  Eintrag  heraus  und   erhöhe  den  Zähler.     Für  die  Tabellenzeile  eignet  sich  eine  eigene  Klasse  Entry.  Ich  kann   eine  Klasse  innerhalb  einer  anderen  Klasse  definieren  –  wenn  ich   sie  private  deklariere,  ist  sie  nur  innerhalb  der  umschließenden   Klasse  sichtbar.  Prima  für  einmalige  Dinge  wie  Tabellenzeilen!  
  6. 6. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Vorteil  einer  solchen  Konstruk/on  (private  Klasse):  Ich  brauche   nicht  darüber  nachzudenken,  wie  sie  verwendet  werden  könnte.   Im  Uhrenladenbeispiel  habe  ich  gesagt,  es  ist  gut,  Felder  immer   als  private  zu  deklarieren,  um  nur  kontrollierten  Zugriff  darauf  zu   erlauben  (wie  bei  der  Uhr,  die  man  nur  zerstören,  aber  nicht   reparieren  kann).   Bei  einer  privaten  Klasse  ist  das  unnö/g,  denn  alle  sta]indenden   Zugriffe  stehen  in  derselben  Datei,  also  gibt  es  nichts  zu   kontrollieren.  Ich  kann  munter  direkt  die  Felder  verwenden.  
  7. 7. private class Entry { String word; int count; } private void countWord(String word) { Entry entry = findEntry(word); entry.count++; }   Wie  finde  ich  nun  meinen  Eintrag,  bzw.  wie  verwalte  ich  meine   Einträge  überhaupt?  
  8. 8. private class Entry { String word; int count; } List<Entry> entries = new ArrayList<Entry>(); private void countWord(String word) { Entry entry = findEntry(word); entry.count++; } private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }  
  9. 9. List<Entry> entries = new ArrayList<Entry>(); private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }   Das  ist  eine  völlig  legi/me  Art,  mit  den  bisher  gelernten  Mi_eln  das   Problem  zu  lösen.   Es  wird  für  ein  gegebenes  Wort  ein  bisher  vorhandener  Eintrag   gesucht.  Wenn  keiner  vorhanden  ist,  wird  einer  angelegt.   Warum  braucht  man  “equals”,  warum  reicht  nich  “==“?  
  10. 10. public static void main(String[] args) { String a = new String(new char[]{'h','e','l','l','o'}); String b = new String(new char[]{'h','e','l','l','o'}); System.out.println(a == b); System.out.println(a.equals(b)); Diese  Schreibweise  ini/alisiert   b = a; ein  Array  mit  den   System.out.println(a == b); angegebenen  Werten.   } false true true   String  ist  ein  Objek_yp.  Das  heißt,  ==  vergleicht,  ob  die  Variablen  a   und  b  dasselbe  Objekt  im  Speicher  bezeichnen.   Zwei  unterschiedliche  String-­‐Objekte  im  Speicher  können  aber   durchaus  für  die  gleiche  Zeichenke_e  stehen.  Und  das  ist  die  Art   Gleichheit,  die  uns  hier  interessiert.  
  11. 11. public static void main(String[] args) { String c = "Hello"; String d = "Hello"; System.out.println(c == d); } true   Das  ist  eine  Anomalie:  Strings,  die  ich  als  Wert  in  meinen  Quellcode   schreibe  (sog.  Literale,  wie  5,  0.45,  ‘a’  oder  eben  “abc”)  werden   vom  Java-­‐Compiler  als  iden/sch  erkannt  und  tatsächlich  nur  einmal   als  Objekt  angelegt.  Dieser  Code  wird  also  intern  umgeformt  zu  so   etwas  wie:   public static void main(String[] args) { String HELLO = "Hello"; String c = HELLO; String d = HELLO; System.out.println(c == d); }   ...  und  damit  ist  das  Verhalten  wieder  erklärt.  Das  nur  am   Rande.  
  12. 12. public static void main(String[] args) throws IOException { new CorpusCount().run(); } void run() throws IOException { readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); sort(); write(); }   readFile  haben  wir  jetzt  also  im  Griff.  Jetzt  schauen  wir  uns  sort   an.  
  13. 13. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { return arg0.word.compareTo(arg1.word); } } private void sort() throws IOException { Collections.sort(entries, new EntryComparator()); }   Collec/ons.sort  ist  eine  sta/sche  Methode,  die  eine  Liste  sor/ert.   Neben  dieser  Liste  benö/gt  sie  als  Argument  einen  Vergleicher,   das  heißt,  ein  Objekt,  das  das  Interface  Comparator<T>   implemen/ert,  wobei  T  der  Typ  der  Listenelemente  ist.  Das   Interface  Comparator<T>  wiederum  verlangt  eine  Methode   compare(T  a,  T  b).  
  14. 14. java.util Interface Comparator<T> int compare(T o1, T o2) Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the second. [...] Mit  einem  solchen  übergebenen  Comparator  wird  bes/mmt,   wonach  die  Liste  sor/ert  werden  soll.     In  der  Dokumenta/on  des  Interface  Comparator  wird   bes/mmt,  dass  Klassen,  die  es  implemen/eren,  darauf  achten   müssen,  dass  compare  eine  totale  Ordnung  beschreibt.  
  15. 15. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { return arg0.word.compareTo(arg1.word); } } private void sort() throws IOException { Collections.sort(entries, new EntryComparator()); }   In  diesem  Beispiel  wird  zunächst  alphabe/sch  sor/ert.  String  hat   prak/scherweise  eine  compareTo-­‐Methode,  an  die  wir  den   Vergleich  delegieren  können,  wenn  wir  nach  dem  String-­‐Feld   word  sor/eren  wollen.  
  16. 16. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return -1; else if (arg0.count > arg1.count) return 1; else return 0; }   } Wenn  wir  nach  dem  int-­‐Feld  count  sor/eren  wollen,  müssen  wir   leider  die  Bedingung  ausschreiben.  int  ist  kein  Objek_yp  und  hat   daher  keine  compareTo-­‐Methode  (oder  sonst  irgendwelche   Methoden).     Eine  Methode  Integer.compare(a,  b)  gibt  es  leider  erst  in  Java  7.  
  17. 17. List<Entry> entries = new ArrayList<Entry>(); private class EntryComparator implements Comparator<Entry> { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return 1; else if (arg0.count > arg1.count) return -1; else return 0; }   } Eigentlich  wollen  wir  aber  absteigend  sor/eren,  also  müssen  wie   das  Vorzeichen  umdrehen,  so  dass  der  Eintrag  mit  der  größeren   Zahl  als  kleiner  gilt.  
  18. 18. private void write() throws IOException { FileWriter fw = new FileWriter("files/words.txt"); BufferedWriter bw = new BufferedWriter(fw); for (Entry entry : entries) { bw.write(entry.word + "t" + entry.count); bw.newLine(); } bw.close(); }   Fehlt  nur  noch  die  write-­‐Methode.  Hier  gehen  wir  die  mi_lerweile   sor/erte  Liste  durch  und  geben  sie  aus.    
  19. 19. Wie  lange  dauert  das?   void }   run() throws IOException { long startTime = System.currentTimeMillis(); readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); long endTime = System.currentTimeMillis(); sort(); write(); System.out.println("Total execution time: " + (endTime - startTime) );
  20. 20. Wie  lange  dauert  das?   void run() throws IOException { long startTime = System.currentTimeMillis(); readFile("files/emma.txt"); readFile("files/sense.txt"); readFile("files/persuasion.txt"); long endTime = System.currentTimeMillis(); sort(); write(); System.out.println("Total execution time: " + (endTime - startTime) ); } Total execution time: 5744   Auf  meinem  Rechner:  5,7  Sekunden.    
  21. 21. Das  ist  eigentlich  schon  ziemlich  schnell.  Ich  bin  überrascht.   Anscheinend  sind  Laptops  so  schnell,  dass  dicke  Bücher  als   Beispiel  für  “große  Datenmengen”  nicht  taugen.     Trotzdem:  Bekommen  wir  es  schneller?   Überlegen  wir  uns,  was  da  passiert:   String line; while ((line = br.readLine()) != null) { String[] words = line.split(" "); for (String word : words) { countWord(word); } } br.close();   Jede  Zeile,  und  jedes  Wort  wird  einzeln  verarbeitet.  Daran   kommen  wir  nicht  vorbei,  wenn  wir  Wörter  zählen  wollen.   Aber  bekommen  wir  die  Methode  countWord()  schneller?  
  22. 22. private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; }   CountWord  bleibt  auch  nicht  viel  anderes  übrig,  als  den   Eintrag  herauszusuchen  und  zu  erhöhen.     Aber  bekommen  wir  findEntry  schneller?  
  23. 23. private Entry findEntry(String word) { for (Entry entry : entries) { if (word.equals(entry.word)) { return entry; } } Entry entry = new Entry(); entry.word = word; entries.add(entry); return entry; }   Wir  suchen  einen  Eintrag  in  der  Wortliste,  indem  wir  sie  von   vorne  nach  hinten  dursuchen.  Das  ist  nicht  effizient,   insbesondere  dauert  diese  Opera/on  länger,  je  mehr  Wörter   schon  im  Katalog  stehen.   Im  schlimmsten  Fall,  also  wenn  das  Wort  noch  gar  nicht   vorhanden  ist,  wird  die  Liste  einmal  ganz  durchgegangen.  
  24. 24. Hypothese:  Das  Programm  wird  allmählich  langsamer,  je  mehr   Daten  wir  schon  verarbeitet  haben,  weil  die  Wortliste  sich  füllt   und  somit  jedes  Wort  mit  der  Zeit  im  Schni_  länger  gesucht   werden  muss.   private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; if (nWords % 10000 == 0) { final long currentTime = System.currentTimeMillis(); System.out.println("Intermediate execution time: ” + (currentTime - checkpointTime) ); checkpointTime = currentTime; } }  
  25. 25. private void countWord(String word) { Entry entry = findEntry(word); entry.count++; nWords++; if (nWords % 10000 == 0) { final long currentTime = System.currentTimeMillis(); System.out.println("Intermediate execution time: ” + (currentTime - checkpointTime) ); checkpointTime = currentTime; } } In  der  Tat.  Zumindest  tendenziell  und  am  Anfang.   In  eine  genaue  Analyse  würde  natürlich  mehr   Intermediate execution time: 83 Intermediate execution time: 79 hineinspielen:  Irgendwann  wurden  alle  gängigen   Intermediate execution time: 89 englischen  Wörter  gesehen,  so  dass  sich  nicht   Intermediate execution time: 97 Intermediate execution time: 110 mehr  viel  ändert,  insbesondere  wurden  die   Intermediate execution time: 122 gängigsten  Wörter  schon  ziemlich  früh  gesehen   Intermediate execution time: 124 Intermediate execution time: 140 und  damit  auch  schneller  gefunden,  usw.   Intermediate execution time: 152   Intermediate execution time: 152 Intermediate execution time: 102 Man  begnügt  sich  aber  normalerweise  mit  einer   Intermediate execution time: 115 worst-­‐case  Analyse:  Bei  einem  künstlichen  Text,   Intermediate execution time: 112 Intermediate execution time: 115 der  nur  unterschiedliche  Wörter  enthält,  würde   Intermediate execution time: 123 dieses  Programm  pro  Wort  immer  langsamer   Intermediate execution time: 122 Intermediate execution time: 198 werden.     ...
  26. 26. Die  Lösung   Eine  Liste,  in  der  linear  nach  Einträgen  gesucht  wird,  ist  keine   adäquate  Datenstruktur  für  dieses  Problem.   Was  wir  brauchen,  ist  keine  Liste,  sondern  eine  Indexstruktur,  in   der  auf  Einträge  unter  einem  besAmmten  Suchschlüssel  effizient   zugegriffen  werden  kann.     Diese  Strukturen  sind  in  Java  die  Maps.     Map<String, Entry> map = new HashMap<String,Entry>();  
  27. 27. Map<String, Entry> entriesMap = new HashMap<String, Entry>(); private Entry findEntry(String word) { Entry entry = entriesMap.get(word); if (entry == null) { entry = new Entry(); entry.word = word; entriesMap.put(word, entry); } return entry; }   Abgesehen  vom  Effizienzgewinn  wird  auch  der  Code  einfacher.   Die  for-­‐Schleife  ist  weg.  Mit  Map.get  wird  ein  Eintrag  (Wert)   unter  einem  Schlüssel  nachgeschlagen.  Mit  Map.put  wird  er   hinzugefügt.     Die  beiden  Typparameter  (genau  behandeln  wir  die  später)   geben  den  Schlüsseltyp  und  den  Wer_yp  an.  Dies  hier  ist  eine   Map  von  String  nach  Entry.  
  28. 28. private void sort() { entries = new ArrayList<Entry>(entriesMap.values()); Collections.sort(entries, new EntryComparator()); }   Zum  Sor/eren  erzeuge  ich  weiterhin  meine  Liste  (dann  muss  ich   auch  den  Code  für  das  Herausschreiben  nicht  ändern).     Dieser  Konstruktor  von  ArrayList  erzeugt  eine  Liste  basierend  auf   einer  bestehenden  Liste,  und  Map.values()  liefert  eine  Liste,  die   aus  allen  Werten  der  Map  besteht.  
  29. 29. Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Intermediate execution time: Total execution time: 238   46 25 20 17 9 13 5 5 4 9 10 10 2 2 1 2 10 3 2 2 2 2 1 2 7 2 2 2 2 2 1 2 5 Und  wie  es  sich  lohnt:  Es  dauert  jetzt  nur   noch  0,2  Sekunden  sta_  5.     Die  Entwicklung  über  die  Laufzeit  ist   auch  völlig  anders:  Das  Programm  wird   eher  schneller.  Vermutlich,  weil  die   Laufzeit  nun  nicht  mehr  durch  das   Suchen  dominiert  wird,  sondern  durch   das  Hinzufügen  neuer  Wörter  (und   davon  kommen  später  nicht  mehr  so   viele).  
  30. 30. Sta_   Collections.sort(entries, new EntryComparator()); kann  man  auch  schreiben:   Collections.sort(entries, new Comparator<Entry>() { public int compare(Entry arg0, Entry arg1) { if (arg0.count < arg1.count) return 1; else if (arg0.count > arg1.count) return -1; else return 0; } });   Hier  wird  eine  sogenannte  anonyme  Klasse  übergeben  (weil   sie  den  Namen  (EntryComparator)  hier  gar  nicht  bekommt).   Das  ist  gut  geeignet  für  Klassen  wie  diese,  von  denen  man   nur  ein  einziges  Objekt  erzeugt,  um  es  an  eine  Methode  zu   übergeben,  und  die  man  danach  nie  wieder  benutzt.  
  31. 31. Bleibt  zu  erklären:  Wie  macht  die  HashMap  das?   Wikipedia:   Hash  Table   Die  Einträge  werden  zu  sogenannten  Hashwerten  verschlüsselt.  Die   HashMap  ist  im  Prinzip  ein  Array  aus  Listen,  und  in  der  Liste  an  einer   Arrayposi/on  n  sind  alle  Werte,  deren  Hashwert  n  ist.   Die  genauen  Eigenschauen  dieser  Struktur  hängen  von  der  Wahl  der   Hashfunk/on  ab.  Die  Analyse  sprengt  hier  den  Rahmen,  aber  diese  Struktur   ist  immer  dann  Mi_el  der  Wahl,  wenn  Dinge  nachgeschlagen  werden  sollen.  
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×