Oblig 4 (INF1000 - Vår 2010)

Filmregister
Mål: I denne oppgaven vil du lære hvordan man kan behandle en større mengde data på en objektorientert måte, og få trening i hele pensum.  Oppgaven kombinerer alle programmerings-elementer vi har sett tidligere i kurset, og innfører datastrukturen "HashMap".

Leveringsfrist

Fredag 7. mai kl. 16.00.  Leveres via Joly-systemet.  (Joly kan brukes hjemmefra vha. VPN eller lignende.)

Leveringskrav

Løsningen du lager skal være objektorientert.  Det betyr at programmet skal bruke objekter av klasser du selv definerer, og interaksjon mellom disse for å løse de forskjellige deloppgavene.  Besvarelsen må bruke minst 3 klasser og 2 HashMap-er, men du kan godt utvide dette med flere klasser, HashMap-er, og andre datastrukturer etter ønske.  Du skal bare levere to ting: Programmet ditt skal lese inn data fra to datafiler.  Disse skal du ikke levere.

Oppgaven skal løses individuelt.  Det forutsettes at du har lest og forstått kravene til innleverte oppgaver ved Ifi: http://www.ifi.uio.no/studier/studentinfo.html#krav.

Oppgave

I denne obligen skal du lage et kommandostyrt system som behandler informasjon om filmer.  Programmet ditt skal lese inn to datafiler, og gi mulighet til brukeren for å utføre kommandoer som finner og viser frem forskjellig type informasjon om filmdataene som ble lest inn.  Programmet ditt trenger ikke skrive til fil eller endre datafilene.

Filen filmdata.txt

Denne filen inneholder informasjon om filmer, med én linje for hver film.  Hver linje har 6 eller flere felt, som er adskilt vha. et tabulator-tegn (dette skilletegnet angis som "\t" i Java):
kode  tittel  år  regissør  skuespillere;...  sjangre   [evt.tillegg]...
Noen av de siste feltene kan mangle data, og da står det "-" i feltet.

Filen persondata.txt

Denne filen har data på to felt, adskilt med tabulator-tegn: kode, og fullt navn til en person.  Her står navnene til alle regissører og skuespillere som var angitt i den andre datafilen med bare kode.  Tips: Disse kodene hopper ikke over noen tall, f.eks. hvis det finnes ole9, så vil også ole1 til ole8 finnes.  Dette kan du bruke til å kunne søke raskere etter personer (eller filmkoder, som følger samme prinsipp) når bruker taster 3 bokstaver og ønsker en liste over matchende personer eller filmer.

P.S. Det er litt feil og mangler i datafilene, men disse påvirker ikke en løsning av obligen.  Du trenger altså ikke laste ned evt. senere utgaver av datafilene for å løse obligen (men spesielt interesserte kan se om rettelser er lagt ut ved å se på fildatoene).

Kommandoene

Oppgaven din blir å lage et program som kan lese inn disse to filene, og svare på følgende typer kommandoer som bruker kan gi.  Du kan velge hvordan du vil sette opp menyen, men det anbefales følgende meny:
Eksempler på kommandoer du kan taste inn:
.      = Vis statistikk
AAA1   = Vis info om en film
AAA    = Finn film
tom1   = Vis info om en person
tom    = Finn person
90     = Vis info om et tiår
2009   = Vis info om et år (*)
a      = Vis info om en sjanger (*)
?      = Vis meny
q      = Avslutt

Kommando ('?' = meny): _
(*): Kommandoene markert med stjerne er valgfrie ekstra-oppgaver, men det anbefales å løse disse også.

Idéen her er at bruker kan taste inn en hvilken som helst av disse typer kommandoer, og programmet finner hva slags kommando ble gitt ut fra antall tegn i kommandoen og om disse tegn er sifre, bokstaver, eller punktum.  Men hvis du foretrekker det kan du også bruke to-stegs kommandoer som i oblig 2 og 3 (menyvalg etterfulgt av spørsmål til bruker).

  1. Vis statistikk: Skal skrive ut totalt antall filmer, og antall filmer i hvert av tiårene 1980-1989, 1990-1999, og 2000-2009.

  2. Vis info om en film, og Finn film: Skal la brukeren angi en film, og skriver deretter ut følgende informasjon om filmen: tittel, år, fullt navn til regissør, og fullt navn til skuespillerne.  Brukeren skal kunne angi film på minst 2 forskjellige måter:
    • kode: Hvis det som brukeren tastet inn var koden til en av filmene (f.eks. "AVA1" for Avatar), skal info om den filmen skrives ut.
    • søk: Hvis bruker bare tastet inn tre bokstaver, og det første (eller flere) er store bokstaver, så skal programmet skrive ut en liste med filmene hvor tittelen (film-koden) begynner med disse bokstavene, og brukeren skal kunne velge ønsket film fra listen.  Du kan bruke de unike film-kodene i datafilen til dette, disse ser bort fra "The " og "A " i begynnelsen av filmnavn.
    • navn (valgfri ekstra-oppgave) (*): Hvis du ønsker det kan du også implementere andre måter å angi filmer på, f.eks. med full tittel til filmen eller full tittel etterfulgt av år.  Du kan også gi enda mer informasjon om filmen, f.eks. sjangre eller evt. serie som filmen tilhører.

  3. Vis info om en person, og Finn person: Skal fungere omtrent som kommandoen ovenfor, med de samme 2 eller 3 måter å angi ønsket person på (men med små bokstaver i stedet).  Informasjonen som vises skal inneholde filmene som personen regisserte, og de som hun spilte i.

  4. Vis info om et tiår: Hvis brukeren taster inn to tallsifre, og det siste er 0, skal programmet vise følgende to ting: (a) «Mest aktiv regissør»: Regissøren som lagde flest filmer det tiåret, og (b) Filmene som står i 2 eller flere av filmlistene 1-5 i det tiåret.  Filmlistene er angitt med kodene 1-5 i felt nr. 6 for hver film, f.eks. kode «2» står på filmer som har fått Oscar.

  5. Vis info om et år (*): Hvis brukeren taster inn et årstall mellom 1900 og 2010 så skal programmet vise følgende to ting om det året: (a) «Årets sjanger» blant comedy, fantasy, horror, eller science-fiction: her skal programmet finne hvilken av disse 4 sjangrene forekommer i flest filmer det året, basert på sjanger-kodene c, f, h, s. Skriv også ut antall filmer resultatet er baserert på; og (b) Hvilken film står i flest æreslister (1-5) det året (uten hensyn til sjanger).

  6. Vis info om en sjanger (*): Taster bruker bare en av bokstavene a, E, H, x (som står for a=action, E=eventyrfilm, H=superhero, x=disaster), så skal programmet vise følgende to ting om valgt sjanger: (a) Skuespilleren som spilte i flest filmer i sjangeren; og (b) Navnene på filmseriene som har minst en film innen sjangeren.
(*): Kommandoene markert med stjerne er valgfrie ekstra-oppgaver, men det anbefales å løse disse også.

Hint

Disse hint er bare for de som ønsker litt ekstra-hjelp.  Det kan også bli lagt ut flere ekstra-hint senere, men obligen kan altså godt løses uten å lese noen av hintene.

  1. Programskall: Her er et eksempel på et mulig skall for programmet, men du kan lære mer med obligen hvis du setter opp programstrukturen din på egen hånd før du ser på dette eksemplet!
    /* Skriv en kommentar om din besvarelse her.
     * ...
     *
     * Leveringsmåte for UML-diagram: ...
     */
    import easyIO.*;
    import java.util.HashMap;
    
    class Oblig4 {
        public static void main(String[] args) {
            new Filmregister().ordreløkke();
        }
    }
    
    class Person {
        String kode;
        String navn;
    
        boolean erRegissør;
        boolean erSkuespiller;
        String filmerRegissert; // Filmkoder adskilt f.eks. med semikolon.
        String filmerSpilt; // Filmkoder adskilt f.eks. med semikolon.
    
        // filmerRegissert og filmerSpilt kan også deklareres som HashMap-er
        // eller f.eks. overføres til arrayer av Film[]-pekere når alle filmer
        // er lagt inn.  Se hint 3 for flere tips.
    
        // Evt. metoder for å behandle en person.
    }
    
    class Film {
        // Variabler for dataene som gjelder en film.
        // ...
    
        // Evt. metoder for å behandle en film.
    }
    
    class TiAar {
        // Variabler for dataene som gjelder et tiår.
        // ...
    
        // Evt. metoder for å behandle et tiår.
    }
    
    class Filmregister {
        In tast = new In();
        Out skjerm = new Out();
    
        HashMap<String, Person> personer = new HashMap<String, Person>();
        HashMap<String, Film> filmer = new HashMap<String, Film>();
        TiAar[] tiår = new TiAar[11]; // [0]=1900-1909, ..., [10]=2000-2009
    
        /**
          * Konstruktør: Leser datafilene, lagrer innholdet i objekter av
          * klassene Person, Film, (og evt. TiAar), og putter Person- og
          * Film-objektene i HashMap-ene «personer» og «filmer».
          */
        Filmregister() {
            // Leser datafilen "persondata.txt":
            In fil = new In("persondata.txt");
            fil.inLine(); // Hopp over første linje, som ikke har data.
            while (! fil.endOfFile()) {
                // Les en linje fra datafilen:
                String kode = fil.inWord();
                String navn = fil.inLine();
    
                // Opprett Person-objekt, og lagre det i HashMap-en «personer».
                // ...
    
                skjerm.out(kode.charAt(0)); // Testutskrift.
            }
            fil.close();
    
            // Leser datafilen "filmdata.txt":
            fil = new In("filmdata.txt");
            fil.inLine(); // Hopp over første linje, som ikke har data.
            while (! fil.endOfFile()) {
                // Følgende setning leser inn en hel linje fra datafilen og
                // oppretter en array med de forskjellige feltene i linjen.
                // felt[0] vil da inneholde filmkoden, felt[1] tittel, osv.
                String linje = fil.inLine();
                String[] felt = linje.split("\t");
    
                // Opprett Film-objekt med de innleste felt-datane, og evt.
                // TiAar-objekt hvis det ikke finnes allerede, og lagre
                // filmobjektet i HashMap-en «filmer».
                // ...
    
                // For å teste om det var flere enn 6 felt i linjen kan
                // du bruke if-setningen: if (felt.length > 6).
    
                skjerm.out(felt[0].charAt(0)); // Testutskrift.
            }
            fil.close();
        }
    
        void ordreløkke() {
            String ordre = ""; // Kommandoen som bruker taster inn.
            char char0 = '?'; // Første tegn i kommandoen.
    
            visMeny();
    
            while (! ordre.equals("q")) {
    
                // Skriv ut ledetekst og les inn en ordre fra tastatur.
                skjerm.out("Kommando ('?' = meny): ");
                ordre = tast.readLine();
    
                int ordreLengde = ordre.trim().length();
                if (ordreLengde > 0) {
                    char0 = ordre.charAt(0);
                }
    
                if (ordreLengde == 1 && char0 == '?') {
                    visMeny();
    
                } else if (ordreLengde >= 3 && char0 >= 'A' && char0 <= 'Z') {
                    visInfoOmFilm(ordre);
    
                } // else if ...osv...
    
                // Skriv en else-if gren for hver ordretype.
            }
        }
    
        void visMeny() {
            // Skriv ut meny her.
            skjerm.outln("...");
            skjerm.outln("?      = Vis meny");
            skjerm.outln("q      = Avslutt");
        }
    
        void visInfoOmFilm(String kode) {
            // Vis info om filmen som har angitt kode (inn-parameter).
        }
    
        // Lag en metode for hver ordre her.  Disse metodene kan
        // kalle på metoder i de andre klassene.
    }
    

  2. UML-klassediagram: Se eksempel på side 236 i læreboka.  Du kan bruke nesten et hvilket som helst tegneprogram for å lage diagrammet på datamaskin (hvis du vil levere det via Joly eller mail), eller du kan scanne inn en papir-tegning.  Det er scanner på Abel- og VB-termstuen.  Hvis du velger elektronisk levering, skal du bruke en av disse filtypene: .pdf, .png, .gif, .jpg, .txt.

  3. String contains og split: For å teste om en film tilhører en sjanger kan du bruke den forhåndsdefinerte metoden «contains» for tekster, f.eks. hvis «sjangre» er en String-variabel med det som sto i felt 6 for filmen «film», så vil følgende if-setning teste om filmen er en action-film:
    if (film.sjangre.contains("a")) { // ...
    
    Tilsvarende kan du teste for koder på mer enn ett tegn, men det fungerer best hvis teksten man skal lete inn i har et skilletegn på slutten av koden man ser etter (eller både foran og bak), f.eks. hvis du har valgt å lagre skuespillerlista til en film i en String-variabel «stars», og plusset på skilletegn bak (stars = stars + ";";), så kan du teste om Sandra Bullock spiller i filmen slik:
    if (film.stars.contains("san1;")) { // ...
    
    Dette gjelder hvis du lagrer skuespillerlisten til en film i en String-variabel.  To andre måter å lagre slike små lister på er som en array (som kan opprettes vha. stars.split(";")) eller i en liten HashMap.  Du kan se et eksempel på split i koden ovenfor.  NB! Tekst-verdien du bruker split på skal ikke inneholde skilletegnet helt foran, så hvis teksten har ";" som første tegn bør dette fjernes (f.eks. vha. substring) før man utfører split(";"), hvis ikke kan man få en tom streng som første resultat av split.

  4. keySet og substring: Du vil få bruk for metodene for manipulasjon av HashMap-er og String-er, disse kan du lese mer om i kapittel 6 og 9 i læreboka, les bl.a. om substring, indexOf, startsWith, parseInt, og split på side 104-111 om tekster; og om put, get, keySet, values, size, og containsKey på side 180-189 om HashMap-er.

  5. Store og små bokstaver: Du kan teste om et tegn i ordren som brukeren tastet inn er stor eller liten bokstav på mange måter, her er tre alternative måter, velg en av dem:
    if (tegn >= 'A' && tegn <= 'Z') { // ...
    if (Character.isUpperCase(tegn)) { // ...
    if (ordre.toUpperCase().equals(ordre)) { // ...
    
    Den første måten er også vist i siste else-if i koden ovenfor og går ut på å teste om et gitt tegn er mellom 'A' og 'Z', i så fall vet vi at det er en stor bokstav.  Bruk 'a' og 'z' for å teste for små bokstaver.  Neste if-setning viser bruk av den forhåndsdefinerte metoden isUpperCase(tegn) til klassen Character, denne klassen er alltid tilgjengelig i Java, på samme måte som String.  Bruk isLowerCase(tegn) for å teste for små bokstaver.  Siste alternativ viser hvordan du kan teste om alle bokstaver i en ordre er store bokstaver.

  6. Lesing av 6 - 7 felt: Linjene i datafilen "filmdata.txt" kan inneholde 6 eller flere felt.  En enkel måte å lese datafilen på som tar høyde for dette er vist i koden ovenfor der det står split("\t").  Og for å sjekke om 7. felt er serienavn kan du bruke startsWith("s=").  Det går også an å lese datafilen vha. fil.inWord("\t") og lignende.  Dette leser inn felt som har mellomrom inni seg riktig, men da trenger du også en smart måte å skille mellom evt. 7. felt og begynnelsen av neste linje.

Kilder

All data som står i datafilene kommer fra Wikipedia.  Film-listen er tatt fra disse alfabetiske listene.

Kommentarer og rettelser til denne obligen kan sendes til josek [at] ifi.uio.no (Jose Luis Rojas K.)