Improved search feature.

- Search is now more like osu!, where subsequent search terms limit the existing result list (as opposed to further expanding it).
- Replaced global tag HashMap with String instance variables, since the previous implementation is incompatible with the above change (resulting in slightly higher memory usage and search times).

Signed-off-by: Jeffrey Han <itdelatrisu@gmail.com>
This commit is contained in:
Jeffrey Han 2014-07-17 01:00:28 -04:00
parent cb396e0d20
commit 0a5e7f66ec
4 changed files with 44 additions and 64 deletions

View File

@ -63,7 +63,7 @@ public class OsuFile implements Comparable<OsuFile> {
public String creator = ""; // beatmap creator public String creator = ""; // beatmap creator
public String version = ""; // beatmap difficulty public String version = ""; // beatmap difficulty
public String source = ""; // song source public String source = ""; // song source
// public String[] tags; // song tags, for searching -> different structure public String tags = ""; // song tags, for searching
public int beatmapID = 0; // beatmap ID public int beatmapID = 0; // beatmap ID
public int beatmapSetID = 0; // beatmap set ID public int beatmapSetID = 0; // beatmap set ID

View File

@ -20,8 +20,7 @@ package itdelatrisu.opsu;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.Iterator;
import java.util.HashSet;
/** /**
* Indexed, expanding, doubly-linked list data type for song groups. * Indexed, expanding, doubly-linked list data type for song groups.
@ -54,12 +53,6 @@ public class OsuGroupList {
*/ */
private int mapCount = 0; private int mapCount = 0;
/**
* Song tags.
* Each tag value is a HashSet which points song group ArrayLists.
*/
private HashMap<String, HashSet<ArrayList<OsuFile>>> tags;
/** /**
* Current list of nodes. * Current list of nodes.
* (For searches; otherwise, a pointer to parsedNodes.) * (For searches; otherwise, a pointer to parsedNodes.)
@ -83,7 +76,6 @@ public class OsuGroupList {
public OsuGroupList() { public OsuGroupList() {
parsedNodes = new ArrayList<OsuGroupNode>(); parsedNodes = new ArrayList<OsuGroupNode>();
nodes = parsedNodes; nodes = parsedNodes;
tags = new HashMap<String, HashSet<ArrayList<OsuFile>>>();
} }
/** /**
@ -105,21 +97,6 @@ public class OsuGroupList {
*/ */
public int getMapCount() { return mapCount; } public int getMapCount() { return mapCount; }
/**
* Adds a tag.
* @param tag the tag string (key)
* @param osuFiles the song group associated with the tag (value)
*/
public void addTag(String tag, ArrayList<OsuFile> osuFiles) {
tag = tag.toLowerCase();
HashSet<ArrayList<OsuFile>> tagSet = tags.get(tag);
if (tagSet == null) {
tagSet = new HashSet<ArrayList<OsuFile>>();
tags.put(tag, tagSet);
}
tagSet.add(osuFiles);
}
/** /**
* Returns the OsuGroupNode at an index, disregarding expansions. * Returns the OsuGroupNode at an index, disregarding expansions.
*/ */
@ -272,7 +249,7 @@ public class OsuGroupList {
/** /**
* Creates a new list of song groups in which each group contains a match to a search query. * Creates a new list of song groups in which each group contains a match to a search query.
* @param query the search query (tag terms separated by spaces) * @param query the search query (terms separated by spaces)
* @return false if query is the same as the previous one, true otherwise * @return false if query is the same as the previous one, true otherwise
*/ */
public boolean search(String query) { public boolean search(String query) {
@ -284,48 +261,30 @@ public class OsuGroupList {
if (lastQuery != null && query.equals(lastQuery)) if (lastQuery != null && query.equals(lastQuery))
return false; return false;
lastQuery = query; lastQuery = query;
String[] terms = query.split("\\s+");
// if empty query, reset to original list // if empty query, reset to original list
if (query.isEmpty()) { if (query.isEmpty() || terms.length < 1) {
nodes = parsedNodes; nodes = parsedNodes;
return true; return true;
} }
// tag search: check if each word is contained in global tag structure // build list from first search term
HashSet<ArrayList<OsuFile>> taggedGroups = new HashSet<ArrayList<OsuFile>>();
String[] terms = query.split("\\s+");
for (String term : terms) {
if (tags.containsKey(term))
taggedGroups.addAll(tags.get(term)); // add all matches
}
// traverse parsedNodes, comparing each element with the query
nodes = new ArrayList<OsuGroupNode>(); nodes = new ArrayList<OsuGroupNode>();
for (OsuGroupNode node : parsedNodes) { for (OsuGroupNode node : parsedNodes) {
// search: tags if (node.matches(terms[0])) {
if (taggedGroups.contains(node.osuFiles)) {
nodes.add(node); nodes.add(node);
continue; continue;
} }
OsuFile osu = node.osuFiles.get(0);
// search: title, artist, creator, source, version (first OsuFile)
if (osu.title.toLowerCase().contains(query) ||
osu.artist.toLowerCase().contains(query) ||
osu.creator.toLowerCase().contains(query) ||
osu.source.toLowerCase().contains(query) ||
osu.version.toLowerCase().contains(query)) {
nodes.add(node);
continue;
} }
// search: versions (all OsuFiles) // remove nodes from list if they don't match all remaining terms
for (int i = 1; i < node.osuFiles.size(); i++) { for (int i = 1; i < terms.length; i++) {
if (node.osuFiles.get(i).version.toLowerCase().contains(query)) { Iterator<OsuGroupNode> iter = nodes.iterator();
nodes.add(node); while (iter.hasNext()) {
break; OsuGroupNode node = iter.next();
} if (!node.matches(terms[i]))
iter.remove();
} }
} }

View File

@ -186,4 +186,32 @@ public class OsuGroupNode implements Comparable<OsuGroupNode> {
else else
return osuFiles.get(osuFileIndex).toString(); return osuFiles.get(osuFileIndex).toString();
} }
/**
* Checks whether the node matches a given search query.
* @param query the search term
* @return true if title, artist, creator, source, version, or tag matches query
*/
public boolean matches(String query) {
OsuFile osu = osuFiles.get(0);
// search: title, artist, creator, source, version, tags (first OsuFile)
if (osu.title.toLowerCase().contains(query) ||
osu.artist.toLowerCase().contains(query) ||
osu.creator.toLowerCase().contains(query) ||
osu.source.toLowerCase().contains(query) ||
osu.version.toLowerCase().contains(query) ||
osu.tags.contains(query))
return true;
// search: version, tags (remaining OsuFiles)
for (int i = 1; i < osuFiles.size(); i++) {
osu = osuFiles.get(i);
if (osu.version.toLowerCase().contains(query) ||
osu.tags.contains(query))
return true;
}
return false;
}
} }

View File

@ -124,7 +124,6 @@ public class OsuParser {
*/ */
private static OsuFile parseFile(File file, ArrayList<OsuFile> osuFiles, boolean parseObjects) { private static OsuFile parseFile(File file, ArrayList<OsuFile> osuFiles, boolean parseObjects) {
OsuFile osu = new OsuFile(file); OsuFile osu = new OsuFile(file);
String tags = ""; // parse at end, only if valid OsuFile
try (BufferedReader in = new BufferedReader(new FileReader(file))) { try (BufferedReader in = new BufferedReader(new FileReader(file))) {
@ -258,7 +257,7 @@ public class OsuParser {
osu.source = tokens[1]; osu.source = tokens[1];
break; break;
case "Tags": case "Tags":
tags = tokens[1]; osu.tags = tokens[1].toLowerCase();
break; break;
case "BeatmapID": case "BeatmapID":
osu.beatmapID = Integer.parseInt(tokens[1]); osu.beatmapID = Integer.parseInt(tokens[1]);
@ -417,12 +416,6 @@ public class OsuParser {
if (osu.combo == null) if (osu.combo == null)
osu.combo = Utils.DEFAULT_COMBO; osu.combo = Utils.DEFAULT_COMBO;
// add tags
if (!tags.isEmpty()) {
for (String tag : tags.split(" "))
Opsu.groups.addTag(tag, osuFiles);
}
// parse hit objects now? // parse hit objects now?
if (parseObjects) if (parseObjects)
parseHitObjects(osu); parseHitObjects(osu);