Računalništvo 1 (PRA)

lekcije / Seznami

Seznami

V prejšnji lekciji smo spoznali razred Vector kot eno od splošno uporabnih podatkovnih struktur, ki so namenjene shranjevanju in iskanju podatkov. Sklicevali smo se na podobnost s tabelami in zares je vektor implementiran kot tabela. Vector je le ena od tovrstnih podatkovnih struktur, ostale bomo spoznali pri predmetu Računalništvo 2.
Za radovedne povejmo, da lahko dober nabor tovrstnih struktur najdemo kot razrede, ki implemetirajo vmesnika Collection in Map. Radovedneže napotimo tudi na izvorno kodo razreda Vector, ki je na voljo (kot tudi velika večina ostalih razredov) v (veliki) datoteki src.zip, ki je običajno instalirana v vrhnji mapi J2SDK (to je na šolskih računalnikih običajno C:\programs\java\src.zip).
Tokrat si bomo ogledali nekaj splošnih lastnosti teh razredov in kako so narejeni. Ker je razred Vector narejen s tabelo in ga (načeloma) že znamo napisati sami (zagotovo bi se ob tem dolgočasili), bomo pokukali v nov svet rekurzivnih podatkovnih struktur in si ogledali najpreprostejšo med njimi: seznam (angl. list).

Seznam

Razmišljajmo splošno: kaj si želimo od podatkovne strukture, ki naj služi za shranjevanje in iskanje podatkov. Odgovor na to seveda ni preprost, saj se približno polovica zgodovine računalništva vrti okrog tega vprašanja (druga polovica zgodovine pa se ukvarja s tem, kako shranjene podatke obdelati). Pojavi se seveda tudi kopica zapletenih podpvrašanj: kaj je podatek, kaj je podatkovna struktura, kaj je shranjevanje, kaj je iskanje, ... Zato bomo v našem razmisleku skromni in si zamislili minimalistično rešitev:
  1. Podatek je poljuben objekt
  2. Seznam je bodisi prazen, bodisi vsebuje (1) prvi podatek in (2) seznam ostalih podatkov
  3. Struktura seznam naj ima naslednje metode:
    Osnovne metode za delo s seznami
    seznam = seznam.dodaj(Object obj)dodaj podatek
    seznam.prvi()vrni prvi podatek
    seznam.ostanek()vrni ostanek seznama (vse razen prvega podatka)
    seznam.prazen()Ali je seznam prazen?
Na sliki si poglejmo, kaj smo določili z gornjimi izhodišči: Naslednja slika prikazuje seznam s tremi elementi, nizi "Veni", "vidi", "vici": Napravimo najprej osnovne komponente in metode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Seznam {
  private boolean prazen; // == true, ce je seznam prazen
  private Object element; // prvi element
  private Seznam ostali;  // ostanek seznama

  public Seznam(Object ee, Seznam oo) {
    element = ee;  ostali = oo;  prazen = false;
  }

  public Seznam() {
    element = null; ostali = null; prazen = true;
  }

  public Seznam dodaj(Object obj) { return new Seznam(obj, this); }

  public Object prvi() { return element; }

  public Seznam ostanek() { return ostali; }

  public boolean prazen() { return prazen; }

} // class
Komponento prazen smo morali dodati zato, da je prazen seznam še vedno objekt. Če bi uporabljali le funkcije (statične metode), bi lahko imeli vrednost null za prazen seznam.
Naloga 1
Za objekte se spodobi, da definirajo metode equals in toString, zato jih dodajmo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean equals(Seznam s) {
    if (this.prazen() && s.prazen()) return true;
    if (this.prazen() != s.prazen()) return false;
    return element.equals(s.prvi()) && ostali.equals(s.ostali);
  }

  public String toString() {
    String s = "(";
    Seznam l = this;
    while (l != null && !l.prazen()) {
      if (l.element != null) s += l.element.toString();
      else s += "()";
      l = l.ostali;
      if (l != null && !l.prazen()) s += " ";
    }
    s += ")";
    return s;
  }
Metoda equals je rekurzivna metoda, kar je najbolj naraven način za obravnavo rekurzivnih podatkovnih struktur. Metoda toString pa je iterativna (ena redkih v tej lekciji).
Preden napišemo testni program, si napravimo še metodo, ki tabelo predela v seznam.
1
2
3
4
5
6
7
8
public static Seznam napolni(Object[] tbl, int index) {
    if (index >= tbl.length) return new Seznam();
    return new Seznam(tbl[index], napolni(tbl, index+1));
  }

  public static Seznam napolni(Object[] tbl) {
    return napolni(tbl, 0);
  }
Zdaj pa si lahko ogledamo delovanje:
SeznamTest1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class SeznamTest1 {

  public static void main(String[] aa) {
    String[] ss = {"Veni", "vidi", "vici"};
    String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"};

    Seznam ll = Seznam.napolni(ss);
    Seznam mm = Seznam.napolni(ss);
    Seznam nn = Seznam.napolni(tt);

    System.out.println(ll);
    System.out.println(mm);
    System.out.println(nn);

    System.out.println(ll.equals(mm));
    System.out.println(ll == mm);
    System.out.println(ll.equals(nn));

    mm = mm.dodaj(null);
    mm = mm.dodaj(new Seznam());
    System.out.println(mm);

    Seznam kk = new Seznam();
    kk = kk.dodaj("GHI");
    kk = kk.dodaj("DEF");
    kk = kk.dodaj("ABC");
    System.out.println(kk);

    ll = ll.dodaj(nn);
    System.out.println(ll);

    ll = Seznam.napolni(ss);
    ll = ll.dodaj(ll);
    System.out.println(ll);

  } // main

} // class
$ java SeznamTest1
(Veni, vidi, vici)
(Veni, vidi, vici)
(Ceterum, censeo, Carthaginem, esse, delendam)
true
false
false
((), (), Veni, vidi, vici)
(ABC, DEF, GHI)
((Ceterum, censeo, Carthaginem, esse, delendam), Veni, vidi, vici)
((Veni, vidi, vici), Veni, vidi, vici)
Kot vidimo, metoda dodaj() dodaja elemente na začetek seznama. Napišimo še metodo, ki dodaja elemente na konec seznama.
1
2
3
4
public void dodajNaKonec(Seznam l) {
    if (ostali == null || ostali.prazen()) ostali = l;
    else ostali.dodajNaKonec(l);
  }
in poglejmo kako deluje
SeznamTest2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class SeznamTest2 {

  public static void main(String[] aa) {
    String[] ss = {"Veni", "vidi", "vici"};
    String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"};

    Seznam ll = Seznam.napolni(ss);
    Seznam nn = Seznam.napolni(tt);

    System.out.println(ll);
    System.out.println(nn);

    Seznam mm = ll.dodaj(nn);
    System.out.println(mm);

    ll.dodajNaKonec(nn);
    System.out.println(ll);

    ll = new Seznam();
    ll.dodajNaKonec(nn);
    System.out.println(ll);

    ll = new Seznam("???",new Seznam());
    ll.dodajNaKonec(nn);
    System.out.println(ll);

    ll = Seznam.napolni(ss);
    ll = ll.dodaj(ll);
    ll = ll.dodaj("!!!");
    ll.dodajNaKonec(new Seznam("???",null));
    System.out.println(ll);

  } // main

} // class
$ java SeznamTest2
(Veni, vidi, vici)
(Ceterum, censeo, Carthaginem, esse, delendam)
((Ceterum, censeo, Carthaginem, esse, delendam), Veni, vidi, vici)
(Veni, vidi, vici, Ceterum, censeo, Carthaginem, esse, delendam)
(Ceterum, censeo, Carthaginem, esse, delendam)
(???, Ceterum, censeo, Carthaginem, esse, delendam)
(!!!, (Veni, vidi, vici, ???), Veni, vidi, vici, ???)
Naloga 2
Metoda dodajNaKonec()spremeni objekt, kjer jo uporabimo. Morda bi bila intuitivno bolj smiselna spodnja statična metoda zlepi(Seznam aa, Seznam bb). Dodali bomo še enostavno metodo za kopiranje seznamov in metodo za obračanje seznama.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Seznam kopija() {
    if (prazen()) return new Seznam();
    return new Seznam(element, ostali.kopija());
  }

  public static Seznam zlepi(Seznam a, Seznam b) {
    if (a == null || a.prazen()) return b;
    return new Seznam(a.prvi(), zlepi(a.ostanek(), b));
  }

  public Seznam obrni() {
    if (prazen()) return new Seznam();
    return zlepi(ostali.obrni(), new Seznam(element, new Seznam()));
  } // obrni
Testni program:
SeznamTest3.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SeznamTest3 {

  public static void main(String[] aa) {
    String[] ss = {"Veni", "vidi", "vici"};
    String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"};

    Seznam ll = Seznam.napolni(ss);
    Seznam mm = Seznam.napolni(tt);
    Seznam nn = ll.kopija();

    System.out.println(ll);
    System.out.println(mm);
    System.out.println(nn);

    System.out.println(nn == ll);
    System.out.println(nn.equals(ll));

    nn = Seznam.zlepi(ll,mm);
    System.out.println(nn);

    nn = ll.obrni();
    System.out.println(nn);

  } // main

} // class
$ java SeznamTest3
(Veni, vidi, vici)
(Ceterum, censeo, Carthaginem, esse, delendam)
(Veni, vidi, vici)
false
true
(Veni, vidi, vici, Ceterum, censeo, Carthaginem, esse, delendam)
(vici, vidi, Veni)
Napišimo zdaj metodo, ki vrne število elementov v seznamu, in metodo, ki vrne k-ti element.
1
2
3
4
5
6
7
8
9
10
11
public int steviloElementov() {
    if (prazen()) return 0;
    return 1 + ostanek().steviloElementov();
  }

  public Object elementNa(int k) {
    if   (k < 0)       { return null; }
    else if (prazen()) { return null; }
    else if (k == 0)   { return prvi(); }
    else               { return ostanek().elementNa(k-1); }
  }
Testni program:
SeznamTest4.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SeznamTest4 {

  public static void main(String[] aa) {
    String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"};

    Seznam ll = Seznam.napolni(tt);

    System.out.println(ll);

    int max = ll.steviloElementov();
    System.out.println(max);

    for (int ii = -1; ii <= max; ii++) {
      System.out.println(ii+" "+ll.elementNa(ii));
    }

  } // main

} // class
$ java SeznamTest4
(Ceterum, censeo, Carthaginem, esse, delendam)
5
-1 null
0 Ceterum
1 censeo
2 Carthaginem
3 esse
4 delendam
5 null
Skoraj vse dosedanje metode so rekurzivne. Napravili bi jih lahko tudi iterativno (z zankami), podobno kot smo napravili toString() (poskusi!).
Za zaključek ukvarjanja s seznami napravimo še metodi za vstavljanje elementa na poljubno mesto v seznamu in metodo za brisanje elementa iz seznama. Metoda vstavi(Objekt obj, int k) bo vstavila element obj na k-to mesto v seznamu. Metoda elementNa(k) vrne element na k-tem mestu. Metoda zbrisi(int k) zbriše element na k-tem mestu. Vrednost metode zbrisi na bo objekt, ki ga je pobrisala. Metoda vstavi pa na vrne kot vrednost tisti del seznama, ki se začne z novo vstavljenim elementom.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public Seznam vstavi(Object oo, int index) {
    if (index < 0) return null;                    // premajhen index
    if (index == 0) {                       // vstavljanje na začetku
      Seznam nn = prazen ? new Seznam() : new Seznam(element,ostali);
      element = oo;  ostali = nn;  prazen = false;
      return this;
    }
    Seznam ll = this;                     // trenutni element seznama
    Seznam pp = null;                     // prejšnji element seznama
    while (index > 0 && !ll.prazen()) {
      index--; pp = ll; ll = ll.ostanek();
    }
    if (index > 0) return null;      // smo na koncu, prevelik indeks
    Seznam nn = new Seznam(oo,ll);
    pp.ostali = nn;                           // vstavljanje na sredi
    return nn;
  } // vstavi

  public Object zbrisi(int index) {
    Object zbrisan;                    // zapomnimo si zbrisan objekt
    if (index < 0) return null;                    // premajhen index
    if (prazen) return null; // iz praznega seznama ne moremo brisati
    if (index == 0) {                          // brisanje na zaeetku
      zbrisan = element;               // zapomnimo si zbrisan objekt
      if (ostali.prazen()) {         // zbrisati moramo edini element
        element = null; ostali = null; prazen = true;
      }
      else {             // brišemo prvi element, naslednik ni prazen
        element = ostali.element;  ostali = ostali.ostali;
      }
      return zbrisan;
    }
    Seznam ll = this;                     // trenutni element seznama
    Seznam pp = null;                     // prejšnji element seznama
    while (index > 0 && !ll.prazen()) {
      index--; pp = ll; ll = ll.ostanek();
    }
    if (ll.prazen()) return null;    // smo na koncu, prevelik indeks
    zbrisan = ll.element;              // zapomnimo si zbrisan objekt
    pp.ostali = ll.ostali;                     // brisanje na sredini
    return zbrisan;
  } // zbrisi
Dogajanje pri vstavljanju se loči na dva do tri primere: vstavljanje na začetek seznama (ki je lahko prazen ali neprazen) in vstavljanje na sredini seznama. Dogajanje opisujejo slike:
Vstavljanje na sredi seznama:
Vstavljanje na začetek nepraznega seznama:
Vstavljanje na začetek praznega seznama:
Naloga 3
[rešitev]
Slike delovanja metode zbrisi so pri
Testni program:
SeznamTest5.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.io.*;

public class SeznamTest5 {

  public static void main(String[] aa) throws IOException {
    String vrsta;
    BufferedReader vhod;
    vhod = new BufferedReader(new InputStreamReader(System.in));

    Seznam ll = Seznam.napolni(aa);

    System.out.println("VSTAVLJANJE");
    int ii = 1;
    while ((vrsta = vhod.readLine()) != null) {
      if (vrsta.length() == 0) break;
      int index = Integer.parseInt(vrsta);
      System.out.println(ll.vstavi("NOV"+ii, index));
      System.out.println(ll);
      ii++;
    }
    System.out.println("BRISANJE");
    while ((vrsta = vhod.readLine()) != null) {
      if (vrsta.length() == 0) break;
      int index = Integer.parseInt(vrsta);
      System.out.println(ll.zbrisi(index));
      System.out.println(ll);
    }

  } // main

} // class
$ java SeznamTest5 veni vidi vici
2

VSTAVLJANJE
0
(NOV1, veni, vidi, vici)
(NOV1, veni, vidi, vici)
2
(NOV2, vidi, vici)
(NOV1, veni, NOV2, vidi, vici)
4
(NOV3, vici)
(NOV1, veni, NOV2, vidi, NOV3, vici)
6
(NOV4)
(NOV1, veni, NOV2, vidi, NOV3, vici, NOV4)
0
(NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4)
(NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4)
8
(NOV6)
(NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6)
-1
null
(NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6)
10
null
(NOV5, NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6)

BRISANJE
0
NOV5
(NOV1, veni, NOV2, vidi, NOV3, vici, NOV4, NOV6)
1
veni
(NOV1, NOV2, vidi, NOV3, vici, NOV4, NOV6)
2
vidi
(NOV1, NOV2, NOV3, vici, NOV4, NOV6)
3
vici
(NOV1, NOV2, NOV3, NOV4, NOV6)
4
NOV6
(NOV1, NOV2, NOV3, NOV4)
10
null
(NOV1, NOV2, NOV3, NOV4)
-1
null
(NOV1, NOV2, NOV3, NOV4)
Na koncu podajmo še celoten razred Seznam skupaj z nekaj več komentarji.
Seznam.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
public class Seznam {

  // komponente:

  // prazen označuje, da je to prazen seznam; vrednosti element in
  // ostali sta v tem primeru nepomembni (običajno sta null).
  // To potrebujemo zato, da je tudi prazen seznam objekt, da lahko
  // na njem kličemo objektne metode. Lahko bi napisali podoben
  // razred, ki bi za prazen seznam uporabljal vrednost null (kar bi
  // bilo običajno) in bi zato imel predvsem statične metode.
  // Če nimamo objekta, ki je prazen seznam, npr. ne moremo poklicati
  // metode dodaj na praznem seznamu.
  boolean prazen;

  // element vsebuje objekt, ki je (prvi) element seznama
  Object element;

  // ostali je seznam ostalih elementov
  Seznam ostali;

  // konstruktor praznega seznama
  public Seznam(Object ee, Seznam oo) {
    element = ee;  ostali = oo;  prazen = false;
  } // Seznam

  // konstruktor elementa seznama
  public Seznam() {
    element = null;  ostali = null;  prazen = true;
  } // Seznam

  // na začetek seznama this dodamo nov element ee in nov seznam
  // vrnemo kot vrednost funkcije
  public Seznam dodaj(Object ee) {
    return new Seznam(ee,this);
  } // dodaj

  // vrnemo prvi element seznama this
  public Object prvi() {
    return element;
  } // prvi

  // vrne ostanek seznama this (brez prvega elementa)
  public Seznam ostanek() {
    return ostali;
  } // ostanek

  // vrne true natanko tedaj, ko je seznam this prazen
  public boolean prazen() {
    return prazen;
  } // prazen

  // metoda, ki ugotavlja strukturno enakost dveh seznamov: this in oo
  // nadomešča metodo Object.equals
  public boolean equals(Object oo) {
    Seznam ss = (Seznam)oo;
    // prazna seznama sta enaka
    if (this.prazen() && ss.prazen()) return true;
    // če je eden prazen in drugi ni, nista enaka
    if (this.prazen() != ss.prazen()) return false;
    // enaka sta natanko tedaj, ko sta enaka prva elementa in ko sta
    // enaka ostanka
    return element.equals(ss.prvi()) && ostali.equals(ss.ostali);
  } // equals

  // metoda, ki seznam pretvori v String; nadomešča Object.toString
  // Elementi seznama so ločeni s presledki, cel seznam pa je v
  // oklepajih. Prazen seznam je "()".
  // Elementi seznama so lahko poljubni objekti, vključno s seznami
  // in tudi vrednostjo null.
  // V tej metodi dovolimo, da se seznam konča z vrednostjo null in
  // ne le s pravim praznim seznamom.
  public String toString() {
    String ss = "(";
    Seznam ll = this;
    while (ll != null && !ll.prazen()) {
      if (ll.element != null) ss += ll.element.toString();
      else ss += "()";
      ll = ll.ostali;
      if (ll != null && !ll.prazen()) ss += ", ";
    }
    ss += ")";
    return ss;
  } // toString

  // elemente iz tabele tbl od indeksa index naprej pretvorimo v
  // seznam, ki ga vrnemo kot vrednost metode.
  public static Seznam napolni(Object[] tbl, int index) {
    if (index >= tbl.length) return new Seznam();
    return new Seznam(tbl[index], napolni(tbl,index+1));
  } // napolni

  // Vse elemente tabele tbl pretvorimo v seznam, ki ga vrnemo kot
  // vrednost metode.
  public static Seznam napolni(Object[] tbl) {
    return napolni(tbl,0);
  } // napolni

  // Elemente seznama ll dodamo na konec seznama this.
  // Seznam this pri tem destruktivno spremenimo, prav tako
  // NE napravimo kopije seznama ll.
  public void dodajNaKonec(Seznam ll) {
    if (prazen()) { // this je prazen, postane kar prvi element ll
      prazen = ll.prazen;
      element = ll.element;
      ostali = ll.ostali;
    }
    // če smo na koncu seznama this, prilepimo seznam ll
    else if (ostali == null || ostali.prazen()) ostali = ll;
    // sicer dodamo ll na konec ostanka seznama this
    else ostali.dodajNaKonec(ll);
  } // dodajNaKonec

  // vrnemo kopijo seznama this
  public Seznam kopija() {
    // če je seznam prazen, vrnemo nov prazen seznam
    if (prazen()) return new Seznam();
    // sicer pa vrnemo nov seznam, ki se sestoji iz prvega elementa
    // in kopije ostanka
    return new Seznam(element,ostali.kopija());
  } // kopija

  // vrnemo seznam, ki ima za elemente najprej vse elemente seznama
  // aa in nato vse elemente seznama bb
  public static Seznam zlepi(Seznam aa, Seznam bb) {
    // če je seznam aa prazen, vrnemo kar seznam bb
    if (aa == null || aa.prazen()) return bb;
    // sicer pa vrnemmo seznam iz prvega elementa in zlepljenega
    // ostanka in bb
    return new Seznam(aa.prvi(), zlepi(aa.ostanek(),bb));
  } // zlepi

  // obrnemo vrstni red elementov v seznamu
  public Seznam obrni() {
    // obrnjen prazen seznam je spet prazen seznam
    if (prazen()) return new Seznam();
    // sicer pa na obrnjen ostanek seznama prilepimo seznam s samo
    // prvim elementom
    return zlepi(ostali.obrni(),new Seznam(element,new Seznam()));
  } // obrni

  // vrnemo število elementov v sezamu
  public int steviloElementov() {
    // število elementov praznega seznama je 0
    if (prazen()) return 0;
    // sicer pa je za 1 večje od števila elementov ostanka
    return 1 + ostanek().steviloElementov();
  } // steviloElementov

  // vrnemo index-ti element seznama; če je index zunaj območja,
  // vrnemo null (nikoli ne povzročimo napake)
  public Object elementNa(int index) {
    if (index < 0) return null;                    // index premajhen
    if (prazen()) return null;                      // index prevelik
    if (index == 0) return prvi();        // prvi element ima index 0
    return ostanek().elementNa(index-1);  // (index-1)-ti elt ostanka
  } // elementNa

  // na index-to mesto vstavimo nov element oo; po vstavljanju bo
  // elementNa(index) == oo
  // vrnemo podseznam, ki se začne z novim objektom, ali null, če
  // smo dobili neumen index
  public Seznam vstavi(Object oo, int index) {
    if (index < 0) return null;                    // premajhen index
    if (index == 0) {                       // vstavljanje na začetku
      // ker vrednosti this ne moremo popraviti, bomo najprej this
      // skopirali v nov objekt in nato v this popravili njegove
      // komponente tako, da bo to postal nov element seznama
      Seznam nn = prazen ? new Seznam() : new Seznam(element,ostali);
      element = oo;  ostali = nn;  prazen = false;
      return this;
    }
    Seznam ll = this;                     // trenutni element seznama
    Seznam pp = null;                     // prejšnji element seznama
    // po seznamu se peljemo dokler ne pridemo do konca ali pa do
    // index-tega elementa
    while (index > 0 && !ll.prazen()) {
      index--; pp = ll; ll = ll.ostanek();
    }
    if (index > 0) return null;      // smo na koncu, prevelik indeks
    // pp ne more biti null, ker smo primer index==0 obdelali posebej
    // naredimo nov objekt, ki vsebuje nov element in kaže naprej
    // na ll. nato prejšnji element seznama nastavimo tako, da mu
    // sledi nov element
    Seznam nn = new Seznam(oo,ll);
    pp.ostali = nn;
    return nn;
  } // vstavi

  // iz seznama zbrišemo element na index-tem mestu
  // vrnemo zbrisan objekt ali null, če smo dobili neumen index
  public Object zbrisi(int index) {
    Object zbrisan;                    // zapomnimo si zbrisan objekt
    if (index < 0) return null;                    // premajhen index
    if (prazen) return null; // iz praznega seznama ne moremo brisati
    if (index == 0) {                          // brisanje na začetku
      // ker vrednosti this ne moremo popraviti, iz prvega objekta
      // naredimo drugega
      zbrisan = element;               // zapomnimo si zbrisan objekt
      if (ostali.prazen()) {         // zbrisati moramo edini element
        element = null; ostali = null; prazen = true;
      }
      else {             // brišemo prvi element, naslednik ni prazen
        element = ostali.element;  ostali = ostali.ostali;
      }
      return zbrisan;
    }
    Seznam ll = this;                     // trenutni element seznama
    Seznam pp = null;                     // prejšnji element seznama
    // po seznamu se peljemo dokler ne pridemo do konca ali pa do
    // index-tega elementa
    while (index > 0 && !ll.prazen()) {
      index--; pp = ll; ll = ll.ostanek();
    }
    if (ll.prazen()) return null;    // smo na koncu, prevelik indeks
    zbrisan = ll.element;              // zapomnimo si zbrisan objekt
    // ker smo index==0 obdelali prej, pp ne more biti null
    pp.ostali = ll.ostali;                     // brisanje na sredini
    return zbrisan;
  } // zbrisi

} // class
Za radovedne podajmo še implementacijo razreda seznam, ki bi bila narejena klasično, torej tako, da je prazen seznam vrednost null. Ker potem ne moremo uporabljati objektnih metod na praznem seznamu so v tej implementaciji skoraj vse metode statične.
SSeznam.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
public class SSeznam {

  Object element;
  SSeznam ostali;

  public SSeznam(Object ee, SSeznam oo) {
    element = ee;  ostali = oo;
  } // SSeznam

  public static SSeznam dodaj(Object ee, SSeznam oo) {
    return new SSeznam(ee,oo);
  } // dodaj

  public static Object prvi(SSeznam ss) {
    return ss.element;
  } // prvi

  public static SSeznam ostanek(SSeznam ss) {
    return ss.ostali;
  } // ostanek

  public static boolean prazen(SSeznam ss) {
    return ss == null;
  } // prazen

  public boolean equals(Object oo) {
    return equals(this,(SSeznam)oo);
  } // equals

  public static boolean equals(SSeznam aa, SSeznam bb) {
    boolean ok;
    if (prazen(aa) != prazen(bb)) return false;
    if (prazen(aa) && prazen(bb)) return true;
    if ((prvi(aa)==null) != (prvi(bb)==null)) return false;
    return ( ((prvi(aa) == null) && (prvi(bb) == null))
           || (prvi(aa).equals(prvi(bb))) )
        && equals(ostanek(aa),ostanek(bb));
  } // equals

  public String toString() {
    return toString(this);
  } // toString

  public static String toString(SSeznam ll) {
    String ss = "(";
    while (!prazen(ll)) {
      Object pp = prvi(ll);
      if (pp == null) ss += "()";
      else ss += prvi(ll).toString();
      ll = ostanek(ll);
      if (!prazen(ll)) ss += ", ";
    }
    ss += ")";
    return ss;
  } // toString

  public static SSeznam napolni(Object[] tbl, int index) {
    if (index >= tbl.length) return null;
    return dodaj(tbl[index], napolni(tbl,index+1));
  } // napolni

  public static SSeznam napolni(Object[] tbl) {
    return napolni(tbl,0);
  } // napolni

  public static SSeznam kopija(SSeznam aa) {
    if (prazen(aa)) return null;
    return dodaj(prvi(aa),kopija(ostanek(aa)));
  } // kopija

  public static SSeznam dodajNaKonec(SSeznam aa, SSeznam bb) {
    if (prazen(aa)) return bb;
    if (prazen(ostanek(aa))) aa.ostali = bb;
    else dodajNaKonec(ostanek(aa),bb);
    return aa;
  } // dodajNaKonec

  public static SSeznam zlepi(SSeznam aa, SSeznam bb) {
    if (prazen(aa)) return bb;
    return dodaj(prvi(aa), zlepi(ostanek(aa),bb));
  } // dodajNaKonec

  public static void main(String[] aa) {
    String[] ss = {"Veni", "vidi", "vici"};
    String[] tt = {"Ceterum", "censeo", "Carthaginem", "esse", "delendam"};

    SSeznam ll = SSeznam.napolni(ss);
    SSeznam mm = SSeznam.napolni(ss);
    SSeznam nn = SSeznam.napolni(tt);

    System.out.println(ll);
    System.out.println(mm);
    System.out.println(nn);

    System.out.println(ll.equals(mm));
    System.out.println(ll == mm);
    System.out.println(ll.equals(nn));

    mm = dodaj(null,mm);
    System.out.println(mm);

    SSeznam kk = null;
    kk = dodaj("GHI",kk);
    kk = dodaj("DEF",kk);
    kk = dodaj("ABC",kk);
    System.out.println(kk);

    ll = dodaj(nn,ll);
    System.out.println(ll);

    ll = SSeznam.napolni(ss);
    ll = dodaj(ll,ll);
    System.out.println(ll);

    ll = napolni(ss);
    nn = napolni(tt);

    System.out.println(ll);
    System.out.println(nn);

    mm = dodaj(nn,ll);
    System.out.println(mm);

    ll = dodajNaKonec(ll,nn);
    System.out.println(ll);

    ll = napolni(ss);
    ll = dodaj(ll,ll);
    ll = dodaj("!!!",ll);
    ll = dodajNaKonec(ll,dodaj("???",null));
    System.out.println(ll);

    ll = napolni(ss);
    ll = dodaj(ll,kopija(ll));
    ll = dodaj("!!!",ll);
    ll = zlepi(ll,dodaj("???",null));
    System.out.println(ll);

  } // main

} // class
Kaj je komu bolj všeč (objektne ali statične metode), je stvar okusa. V Javi, ki je objektni programski jezik, je verjetno bolj smiselno programirati z objekti.
Zelo radovednega študenta napotimo še k ogledovanju razreda java.util.LinkedList, ki predstavlja implementacijo seznamov v javanski knjižnici. Tam gre sicer za dvojne sezname (v objektu je poleg kazalca naprej tudi kazalec nazaj), vendar je ogled razreda v src.zip prav poučen.

Rešitve nalog

Naloga 3