Semestralka - parsovani souboru
Nacházíte se: / Programování / Java / Školní rok 2008/2009
Zadání
Úkolem je přečíst soubor s textem, spočítat slova, znaky, ... a pak napsat nějaké vyhodnocení.
Můj komentář k zadání
Je to úplně na ... mno ... domysli si bandu sprostejch slov. Jako téma k ničemu, nikde to nepoužiju, max. se tím naučím něco, co můžu použít jinde (tím jsem ztratil čas, protože jsem se to mohl učit na tom, co použiju :\), ale když jsem měl zakázanou síťovou komunikaci, tak mi nic jinýho nezbylo, mno ... škoda slov, příště se na to vy.....
Download
Komplet projekt do netbeans si můžete stáhnout zde (22kB).
Řešení
Jsou potřeba 2 sobory:
- main - řídící složka, akorát se stará o to, aby to celý fungovalo, měla by být co nejjednodušší, ale zase ne příliš.
- Statistics.java.class - třída, která se stará o samotné zpracování
Soubor main.java:
/*
* Word counting:
* Zadaní:
* Úkolem je přečíst soubor s textem, spočítat slova, znaky, ... a pak
* napsat nějaké vyhodnocení.
*
* Časová náročnost: 7 hodin
*
* Poznámky:
* - Komentáře: Vím, že na to jsou nějaký standarty, kašlu na ně, nemam náladu
to hledat.
*/
package semestralka;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
/**
*
* @author Skrivy
*/
public class Main {
/**
* Vysvětlení vlastnosti tridy:
* DefaultFileName - Přiložil jsem soubor s nějakým Lorem impsum textem, tak
to je výchozí soubor
* Input - Jedinej scanner, kterej bude potreba
*/
private static String DefaultFileName = "data.txt";
private static Scanner Input = new Scanner( System.in );
public static void main(String[] args) throws IOException {
String fileName = getFileName( );
boolean CaseSensitive = getYerOrNo( "Parsovat soubory case-sensitive?" , true );
Statistics result = new Statistics( fileName , CaseSensitive );
while ( true ) {
int Section = 0;
while ( Section > 52 || Section < 48 )
Section = readKey( "\n\nMoznosti:\n1) Vypis znaku a jejich vyuziti\n2) Vypis slov a jejich vyuziti\n3) Obecne statistiky\n4) Ukoncit program\nVase volba: " );
switch ( Section ) {
/* 1) Vypis znaku a jejich vyuziti*/
case 49:
result.PrintCharStats();
break;
/* 2) Vypis slov a jejich vyuziti*/
case 50:
result.PrintStringsStats();
break;
/* 3) Obecne statistiky */
case 51:
result.PrintGerallyStatistics();
break;
/* 4) ukoncime program */
case 52:
System.exit(0);
break;
}
System.out.println( "Pokracujte stisknutim klavesy." );
Input.nextLine();
}
//result.PrintCharStats( );
//result.PrintStringsStats( );
//System.out.println( result.GetStringsCount() );
//System.out.println( result.GetCharsCount() );
}
/**
* getFileName():
*
* Cyklem ziskame nazev souboru, ze kteryho budem cist slova
* - kontroluje se existence souboru a readable souboru
*
* Otázkou určitě je, proč to kontroluju tady, když později musím v
souvislosti s tímto použít vyjímku ... ale když už jsem to měl napsaný,
tak proč se s tím přepisovat.
*
*/
private static String getFileName( ) {
String result = new String( );
while (true) {
System.out.print("Cesta k souboru [" + DefaultFileName + "]: ");
result = Input.nextLine();
if ( result.isEmpty() ) {
result = DefaultFileName;
}
File file = new File( result );
if ( file.exists() && file.isFile() )
if ( file.canRead() )
return result;
else
System.out.println( "Nemuzu precist soubor " + file );
else
System.out.println( "Soubor " + file + " neexistuje." );
}
}
/**
* getYesOrNo():
*
* Cyklem ziskame boolean promenou (Ano/Ne) na otazku v prvnim parametru
* V druhym parametru si prenasime vychozi odpoved, kdyz da uzivatel enter
*
*/
private static boolean getYerOrNo( String Question , boolean DefaultAnswer ) {
while (true) {
int PressedKey = readKey( Question + " (" + ( DefaultAnswer == true ? "Y\\n" : "y\\N" ) + "): " );
if ( PressedKey == 10 ) return DefaultAnswer;
if ( PressedKey == 89 || PressedKey == 121 ) return true;
if ( PressedKey == 78 || PressedKey == 110 ) return false;
}
}
/**
* readKey():
*
* Precte 1 jedinny znak z klavesnice :)
*
*/
private static int readKey( String Question ) {
System.out.print( Question );
String result = Input.nextLine();
if ( result.isEmpty() ) return 10;
return (int) result.charAt( 0 );
}
}
A teď už jenom soubor se samotnou třídou, která se postará o zpracování a čtení obsahu souboru + výstup - Statistics.java:
package semestralka;
import java.io.FileReader;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;
public class Statistics {
/**
* Popis pouzitych vlastnosti tridy
* Chars - Pole znaku, index je cislo znaku podle ascii, hodnota je pocet opakovani
* Words - hashtable slov, index je slovo, hodnota pocet opakovani
* WordEnds - cisla znaku podle ascii, ktere ukoncuji slovo - vysvetleno pozdeji
* Word - to je docasna vlastnost pro skladani slov ze znaku
*/
private int[] Chars = new int[0];
private Map Words = new TreeMap();
private int[] WordEnds = {10, 13, 46, 44, 32}; // Znaky, kteryma konci slovo
private String Word = "";
private int MaxWordLenght = 0;
private boolean CaseSensitive;
private int NumChars = 0;
private int NumWords = 0;
/**
* Constructor ma za ukol cist znak po znaku a predavat to metodam
* ExecuteChar (Statistiky znaků) a ExecuteString (Statistiky slov).
*/
public Statistics(String fileName, boolean CaseSensitive) {
this.CaseSensitive = CaseSensitive;
Arrays.sort(this.WordEnds); // Musi bejt serazeny, zeby kvuli optimalizaci?
try {
FileReader reader = new FileReader(fileName);
int Char;
while ((Char = reader.read()) != -1) {
if (this.CaseSensitive == false) {
Char = Character.toLowerCase(Char);
}
this.ExecuteChar(Char);
this.ExecuteString(Char);
}
reader.close();
} catch (java.io.IOException popis) {
System.out.println("Unable to read file " + fileName + ".");
}
}
/* STATISTIKY ZNAKŮ ***************************************************** */
/**
* Co k tomu rict? :) Proste se tvori jedno velke pole - index je
* cislo znaku z ascii tabulky, hodnota je pocet opakovani znaku v poli
*/
private void ExecuteChar(int Char) {
if (this.Chars.length <= Char) {
this.Chars = this.ResizeArray(this.Chars, Char);
}
this.Chars[Char]++;
this.NumChars++;
}
/**
* Tato funkce vypise statistiky jednotlivych znaku. Dlouho jsem premyslel,
* jak prinutit znak cislo 10 a podobny funkcionalni znaky vypisovat se
* "citelne", cili /n /r /t, neprisel jsem na to, tak jsem vytvoril metodu
* ReturnString( int char );
*/
public void PrintCharStats() {
System.out.println("STATISTIKY ZNAKU V TEXTU:\n");
int ColsNum = 5;
int Writed = 0;
for (int i = 0; i < this.Chars.length; i++) {
if (this.Chars[i] != 0) {
Writed++;
System.out.printf("%5s = %5dx ", this.ReturnString(i), this.Chars[i]);
if (Writed % ColsNum == 0) {
System.out.println("");
}
}
}
System.out.println("");
}
/**
* Vratime pocet znaku v souboru
*/
public int GetCharsCount() {
return this.NumChars;
}
/**
* Tohle je fce, ktera netisknutelne nebo blbe tisknutelne znaky pevadi na
* neco "normalniho", hlavne citelneho, co mi nerozhodi tabulku. Nic
* lepsiho me nenapadlo ... :\
*/
private String ReturnString(int Char) {
if (Char == 10) return "file://n/";
if (Char == 13) return "file://r/";
if (Char == 32) return "space";
return String.valueOf((char) Char);
}
/* STATISTIKY SLOV ****************************************************** */
/**
* A trochu slozejsi algoritmus na hledani slov a tvorbu hash table
*
* Tady je nekolik moznych reseni hledani slov:
* 1) Definujeme znaky, ktere jsou slovo, ostatni jsou oddelovace
* 2) Definujeme oddelovace slov, ostatni jsou znaky slova
*
* pouzil jsem moznost 2.
*
* Potom jeste hledani jiz slov, ktery jsme jednou nasli - zase 2 moznosti,
* co me napadly:
* 1) Pouzijeme hash mapu, celkem jednoduche reseni, nevim, jak je to s
* rychlosti a narocnosti na pamet ... neresim.
* 2) Druhou metodu jsem nezkousel, zpociva ve 2 rozmernem poli:
* [int1][int2] = String;
* int1 = delka slova
* int2 = automaticky inkrementovany ukazatel
* String = vlastni slovo
* Principelne je to navrzene tak, aby se prochazela slova (String v [int2])
* pouze se stejnou delkou [int1] a tim se setril procesorovy cas. Jestli se
* opravdu setri nevim, protoze bude narocnejsi sprava takovehoto pole ...
*/
private void ExecuteString(int Char) {
if (Arrays.binarySearch(this.WordEnds, Char) >= 0) {
if (this.Word.length() > 0) {
int Count = 0;
if (this.Words.containsKey(this.Word)) {
Count = (Integer) this.Words.get(this.Word);
}
this.Words.put(this.Word, Count + 1);
if ( this.MaxWordLenght < this.Word.length())
this.MaxWordLenght = this.Word.length();
this.Word = "";
this.NumWords++;
}
} else {
this.Word += (char) Char;
}
}
/**
* Vypiseme vsechna slova a pocet jejich uziti v textu.
*/
public void PrintStringsStats() {
this.MaxWordLenght ++ ;
int Count = 0;
for (Map.Entry row : this.Words.entrySet()) {
Count ++ ;
System.out.printf("%" + this.MaxWordLenght + "s: %5dx", row.getKey(), row.getValue());
if ( ( Count * this.MaxWordLenght ) > 50 ) {
Count = 0;
System.out.println("");
}
}
}
/**
* Vratime pocet slov v souboru
*/
public int GetStringsCount() {
return this.NumWords;
}
/* NĚJAKÝ SYSTÉMOVÝ METODY ********************************************** */
/**
* ResizeArray:
* jde o alokaci pameti pro pole. Jelikoz je toto navrzeno celkem obecne,
* tak je i pocet znaku teoreticky nekonecne velky ...
*/
private int[] ResizeArray(int[] oldArray, int MaxIndex) {
int[] newArray = new int[MaxIndex + 1];
System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
return newArray;
}
/**
* PrintGerallyStatistics:
*
*/
public void PrintGerallyStatistics() {
System.out.println("Obecne statistiky \n");
System.out.println("Pocet znaku: " + this.NumChars );
System.out.println("Pocet slov: " + this.NumWords );
System.out.println("Nejdelsi slovo ma: " + this.MaxWordLenght + " znaku");
}
}
Vyjádření učitele, aneb, co jsem udělal špatně, co by se mohlo zlepšit, ...
Ještě jsem to neodevzdal, uvidíme v lednu. :)