Máte zoznam súborov, v ktorom je každý súbor identifikovaný menom a podpisom. Meno je ľubovoľný reťazec v úvodzovkách a podpis je postupnosť nezáporných 32-bitových čísiel v šestnástkovej sústave ako môžete vidieť na obr. 01.
"test_name" 3a 45ffa2 236da0 34cc 21
"/usr/stdio.h" 456fa 34dd 28ab9 457e
".other.txt" 5623d ff3a21 783d 22456
Vašou úlohou je nájsť počet jedinečných súborov v zozname. Súbor je považovaný za jedinečný, ak jeho podpis nie je riadnym prefixom podpisu akéhokoľvek iného súboru v zozname. Podpis s1 je považovaný za riadny prefix podpisu s2 vtedy (a len vtedy), keď s2 začína s s1 a podpisy majú rôzne dĺžky. V prípade, že viaceré súbory majú rovnaký podpis, započítajte ich všetky. Úlohu som rozdelil na dve časti:
Obr. 01: formát zoznamu súborov
- načítanie celého súboru do pamäte a rozparsovanie obsahu do vhodnej dátovej štruktúry
- algoritmus, ktorý počíta požadované hodnoty nad touto dátovou štruktúrou
public class FileDescription { private String name; private List<Long> signature = new ArrayList<Long>(); public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSignatureSize() { return signature.size(); } public Long getSignature(int index) { return signature.get(index); } public void addSignature(long signature) { this.signature.add(signature); } }Je to obyčajný value object, ktorý obsahuje meno súboru, a podpis, ktorý je List Long-ov, pretože do Java int-u sa 32-bitové číslo nezmestí. (kvôli znamienku) Riešenie 1 (Java): Najjednoduchšie riešenie spočíva v tom, že najprv zoradíme FileDescription prvky v Liste podľa veľkosti podpisu od najväčšieho po najmenší a potom prejdeme každý prvok v Liste od začiatku a skontrolujeme, či má nejaké duplikáty. Ak na nejaký duplikát narazíme, proste ho z Listu vymažeme. Na konci metódy nám v Liste ostanú iba unikátne (jedinečné) súbory.
private static class FileDescriptionComparatorLargestFirst implements Comparator<FileDescription>, Serializable { public int compare(FileDescription fileDescription1, FileDescription fileDescription2) { if (fileDescription1.getSignatureSize() < fileDescription2.getSignatureSize()) return 1; if (fileDescription1.getSignatureSize() > fileDescription2.getSignatureSize()) return -1; return 0; } } public int calculateNumberOfUnique1P(List<FileDescription> fileDescriptions) { Collections.sort(fileDescriptions, new FileDescriptionComparatorLargestFirst()); for (int i = 0; i < fileDescriptions.size(); i++) { FileDescription outerFileDescription = fileDescriptions.get(i); for (int j = i + 1; j < fileDescriptions.size(); j++) { FileDescription innerFileDescription = fileDescriptions.get(j); if (outerFileDescription.getSignatureSize() > innerFileDescription.getSignatureSize()) { boolean equal = true; for (int k = 0; k < innerFileDescription.getSignatureSize(); k++) { if (!innerFileDescription.getSignature(k).equals(outerFileDescription.getSignature(k))) { equal = false; break; } } if (equal) { fileDescriptions.remove(innerFileDescription); j--; } } } } return fileDescriptions.size(); }
- algoritmus využíva 3 vnorené for cykly, čo na prvý pohľad naznačuje, že nebude príliš výkonný, ale malý test súbor set_small.dat spracuje bez problémov v zlomku sekundy
- taktiež výsledok zahŕňa List s unikátnymi súbormi a nielen ich počet (aj keď metóda vracia iba číslo)
- vyskúšal som metódu aj na veľkom súbore set_large.dat, ale aby sa súbor vôbec načítal do pamäte je potrebné pridať pamäť pre JVM oproti štandardnému nastaveniu, napríklad takto -Xms128m -Xmx1024m
- kým sa algoritmus dopracoval k výsledku prešlo viac než 20 minút, takže "Tudy cesta nevede." :)
public int calculateNumberOfUnique2P(List<FileDescription> fileDescriptions) { List<FileDescription> uniques = new ArrayList<FileDescription>(); List<FileDescription> duplicates = new ArrayList<FileDescription>(); recurseFileDescriptionListP(fileDescriptions, 0, uniques, duplicates); return uniques.size(); } private void recurseFileDescriptionListP(List<FileDescription> fileDescriptions, Integer level, List<FileDescription> uniques, List<FileDescription> duplicates) { if (level > 0) { if (fileDescriptions.size() == 1) { uniques.add(fileDescriptions.get(0)); return; } if (fileDescriptions.size() > 0) { List<FileDescription> cleanCandidates = new ArrayList<FileDescription>(); Integer largestSizeInList = 0; for (FileDescription fileDescription : fileDescriptions) { if (fileDescription.getSignatureSize() == level) { cleanCandidates.add(fileDescription); } if (fileDescription.getSignatureSize() > largestSizeInList) { largestSizeInList = fileDescription.getSignatureSize(); } } if (cleanCandidates.size() > 0) { if (largestSizeInList.equals(level)) { uniques.addAll(cleanCandidates); return; } if (largestSizeInList > level) { duplicates.addAll(cleanCandidates); fileDescriptions.removeAll(cleanCandidates); } } } } Map<Long, List<FileDescription>> longFileDescriptionMap = new HashMap<Long, List<FileDescription>>(); for (FileDescription fileDescription : fileDescriptions) { Long key = fileDescription.getSignature(level); List<FileDescription> fileDescriptionList = longFileDescriptionMap.get(key); if (fileDescriptionList == null) { fileDescriptionList = new ArrayList<FileDescription>(); longFileDescriptionMap.put(key, fileDescriptionList); } fileDescriptionList.add(fileDescription); } for (List<FileDescription> fileDescriptionList : longFileDescriptionMap.values()) { recurseFileDescriptionList(fileDescriptionList, level + 1, uniques, duplicates); } }
- aby sme zabránili pretečeniu zásobníka pri tejto metóde je potrebné upraviť parametre JVM, napríklad takto -Xms128m -Xmx1024m -Xss64m
- výsledkom algoritmu sú 2 nové Listy, jeden obsahuje unikátne súbory a druhý duplicitné
- beh programu pre veľký testovací súbor set_large.dat trvá menej ako jednu sekundu
- pomerne ľahko by sa dal prepísať aby bežal vo viacerých threadoch
class FileDescription { private: string name; vector<unsigned int> signature; public: string getName() const { return name; } void setName(string name) { this->name = name; } unsigned int getSignature(unsigned int level) const { return signature[level]; } void addSignature(unsigned int signature) { this->signature.push_back(signature); } unsigned int getSignatureSize() const { return this->signature.size(); } };
unsigned int cleanFileDescriptions3P(list<FileDescription*>& fileDescriptions, unsigned int level, unsigned int& uniques, unsigned int& duplicates) { if (fileDescriptions.size() == 1) { uniques++; return 0; } if (fileDescriptions.size() > 1) { list<FileDescription*> toRemove; unsigned int largestSizeInList = 0; for (list<FileDescription*>::iterator iter = fileDescriptions.begin(); iter != fileDescriptions.end(); ++iter) { unsigned int signatureSize = (*iter)->getSignatureSize(); if (signatureSize == level+1) { toRemove.push_back((*iter)); } if (signatureSize > largestSizeInList) { largestSizeInList = signatureSize; } } if (toRemove.size() > 0) { if (largestSizeInList == level+1) { uniques += toRemove.size(); return 0; } if (largestSizeInList > level+1) { duplicates += toRemove.size(); for (list<FileDescription*>::const_iterator iter = toRemove.begin(); iter != toRemove.end(); ++iter) { fileDescriptions.remove(*iter); } } } } return fileDescriptions.size(); } void calculateNumberOfUnique3P(list<FileDescription*>& fileDescriptions, unsigned int& uniques, unsigned int& duplicates) { unsigned int level = 0; map<unsigned int, list<FileDescription*> > fileDescriptionMap; list<list<FileDescription*> > fileDescriptionsListOfLists; fileDescriptionsListOfLists.push_back(fileDescriptions); while (!fileDescriptionsListOfLists.empty()) { for (list<list<FileDescription*> >::const_iterator outerListIter = fileDescriptionsListOfLists.begin(); outerListIter != fileDescriptionsListOfLists.end(); ++outerListIter) { for (list<FileDescription*>::const_iterator innerListIter = (*outerListIter).begin(); innerListIter != (*outerListIter).end(); ++innerListIter) { unsigned int key = (*innerListIter)->getSignature(level); fileDescriptionMap[key].push_back((*innerListIter)); } } fileDescriptionsListOfLists.clear(); for (map<unsigned int, list<FileDescription*> >::const_iterator mapIter = fileDescriptionMap.begin(); mapIter != fileDescriptionMap.end(); ++mapIter) { list<FileDescription*> fileDescriptionsPart = mapIter->second; unsigned int unresolvedListFlag = cleanFileDescriptions3P(fileDescriptionsPart, level, uniques, duplicates); if (unresolvedListFlag>0) { fileDescriptionsListOfLists.push_back(fileDescriptionsPart); } } fileDescriptionMap.clear(); level++; } }
- výsledkom sú dve čísla a to počet duplicitných a počet unikátnych súborov
- výkon je porovnateľný s riešením číslo 2
- set_small.dat: spolu súborov 942, z toho unikátnych 851
- set_large.dat: spolu súborov 106020, z toho unikátnych 96312