mirror of
				https://github.com/vcmi/vcmi.git
				synced 2025-10-31 00:07:39 +02:00 
			
		
		
		
	optimized search input
This commit is contained in:
		| @@ -41,6 +41,7 @@ | ||||
| #include "../../lib/spells/CSpellHandler.h" | ||||
| #include "../../lib/spells/ISpellMechanics.h" | ||||
| #include "../../lib/spells/Problem.h" | ||||
| #include "../../lib/texts/TextOperations.h" | ||||
| #include "../../lib/GameConstants.h" | ||||
|  | ||||
| #include "../../lib/mapObjects/CGHeroInstance.h" | ||||
| @@ -242,7 +243,7 @@ void CSpellWindow::processSpells() | ||||
| 	mySpells.reserve(CGI->spellh->objects.size()); | ||||
| 	for(auto const & spell : CGI->spellh->objects) | ||||
| 	{ | ||||
| 		bool searchTextFound = !searchBox || boost::algorithm::contains(boost::algorithm::to_lower_copy(spell->getNameTranslated()), boost::algorithm::to_lower_copy(searchBox->getText())); | ||||
| 		bool searchTextFound = !searchBox || TextOperations::textSearchSimilar(searchBox->getText(), spell->getNameTranslated()); | ||||
|  | ||||
| 		if(onSpellSelect) | ||||
| 		{ | ||||
|   | ||||
| @@ -55,6 +55,7 @@ | ||||
| #include "../lib/gameState/TavernHeroesPool.h" | ||||
| #include "../lib/gameState/UpgradeInfo.h" | ||||
| #include "../lib/texts/CGeneralTextHandler.h" | ||||
| #include "../lib/texts/TextOperations.h" | ||||
| #include "../lib/IGameSettings.h" | ||||
| #include "ConditionalWait.h" | ||||
| #include "../lib/CRandomGenerator.h" | ||||
| @@ -1574,7 +1575,7 @@ void CObjectListWindow::init(std::shared_ptr<CIntObject> titleWidget_, std::stri | ||||
|  | ||||
| 		itemsVisible.clear(); | ||||
| 		for(auto & item : items) | ||||
| 			if(boost::algorithm::contains(boost::algorithm::to_lower_copy(item.second), boost::algorithm::to_lower_copy(text))) | ||||
| 			if(TextOperations::textSearchSimilar(text, item.second)) | ||||
| 				itemsVisible.push_back(item); | ||||
|  | ||||
| 		selected = 0; | ||||
|   | ||||
| @@ -17,61 +17,11 @@ | ||||
| #include "../filesystem/Filesystem.h" | ||||
| #include "../modding/ModScope.h" | ||||
| #include "../modding/CModHandler.h" | ||||
| #include "../texts/TextOperations.h" | ||||
| #include "../ScopeGuard.h" | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_BEGIN | ||||
|  | ||||
| // Algorithm for detection of typos in words | ||||
| // Determines how 'different' two strings are - how many changes must be done to turn one string into another one | ||||
| // https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows | ||||
| static int getLevenshteinDistance(const std::string & s, const std::string & t) | ||||
| { | ||||
| 	int n = t.size(); | ||||
| 	int m = s.size(); | ||||
|  | ||||
| 	// create two work vectors of integer distances | ||||
| 	std::vector<int> v0(n+1, 0); | ||||
| 	std::vector<int> v1(n+1, 0); | ||||
|  | ||||
| 	// initialize v0 (the previous row of distances) | ||||
| 	// this row is A[0][i]: edit distance from an empty s to t; | ||||
| 	// that distance is the number of characters to append to  s to make t. | ||||
| 	for (int i = 0; i < n; ++i) | ||||
| 		v0[i] = i; | ||||
|  | ||||
| 	for (int i = 0; i < m; ++i) | ||||
| 	{ | ||||
| 		// calculate v1 (current row distances) from the previous row v0 | ||||
|  | ||||
| 		// first element of v1 is A[i + 1][0] | ||||
| 		// edit distance is delete (i + 1) chars from s to match empty t | ||||
| 		v1[0] = i + 1; | ||||
|  | ||||
| 		// use formula to fill in the rest of the row | ||||
| 		for (int j = 0; j < n; ++j) | ||||
| 		{ | ||||
| 			// calculating costs for A[i + 1][j + 1] | ||||
| 			int deletionCost = v0[j + 1] + 1; | ||||
| 			int insertionCost = v1[j] + 1; | ||||
| 			int substitutionCost; | ||||
|  | ||||
| 			if (s[i] == t[j]) | ||||
| 				substitutionCost = v0[j]; | ||||
| 			else | ||||
| 				substitutionCost = v0[j] + 1; | ||||
|  | ||||
| 			v1[j + 1] = std::min({deletionCost, insertionCost, substitutionCost}); | ||||
| 		} | ||||
|  | ||||
| 		// copy v1 (current row) to v0 (previous row) for next iteration | ||||
| 		// since data in v1 is always invalidated, a swap without copy could be more efficient | ||||
| 		std::swap(v0, v1); | ||||
| 	} | ||||
|  | ||||
| 	// after the last swap, the results of v1 are now in v0 | ||||
| 	return v0[n]; | ||||
| } | ||||
|  | ||||
| /// Searches for keys similar to 'target' in 'candidates' map | ||||
| /// Returns closest match or empty string if no suitable candidates are found | ||||
| static std::string findClosestMatch(const JsonMap & candidates, const std::string & target) | ||||
| @@ -84,7 +34,7 @@ static std::string findClosestMatch(const JsonMap & candidates, const std::strin | ||||
|  | ||||
| 	for (auto const & candidate : candidates) | ||||
| 	{ | ||||
| 		int newDistance = getLevenshteinDistance(candidate.first, target); | ||||
| 		int newDistance = TextOperations::getLevenshteinDistance(candidate.first, target); | ||||
|  | ||||
| 		if (newDistance < bestDistance) | ||||
| 		{ | ||||
|   | ||||
| @@ -236,5 +236,75 @@ std::string TextOperations::getCurrentFormattedDateTimeLocal(std::chrono::second | ||||
| 	return TextOperations::getFormattedDateTimeLocal(std::chrono::system_clock::to_time_t(timepoint)); | ||||
| } | ||||
|  | ||||
| int TextOperations::getLevenshteinDistance(const std::string & s, const std::string & t) | ||||
| { | ||||
| 	int n = t.size(); | ||||
| 	int m = s.size(); | ||||
|  | ||||
| 	// create two work vectors of integer distances | ||||
| 	std::vector<int> v0(n+1, 0); | ||||
| 	std::vector<int> v1(n+1, 0); | ||||
|  | ||||
| 	// initialize v0 (the previous row of distances) | ||||
| 	// this row is A[0][i]: edit distance from an empty s to t; | ||||
| 	// that distance is the number of characters to append to  s to make t. | ||||
| 	for (int i = 0; i < n; ++i) | ||||
| 		v0[i] = i; | ||||
|  | ||||
| 	for (int i = 0; i < m; ++i) | ||||
| 	{ | ||||
| 		// calculate v1 (current row distances) from the previous row v0 | ||||
|  | ||||
| 		// first element of v1 is A[i + 1][0] | ||||
| 		// edit distance is delete (i + 1) chars from s to match empty t | ||||
| 		v1[0] = i + 1; | ||||
|  | ||||
| 		// use formula to fill in the rest of the row | ||||
| 		for (int j = 0; j < n; ++j) | ||||
| 		{ | ||||
| 			// calculating costs for A[i + 1][j + 1] | ||||
| 			int deletionCost = v0[j + 1] + 1; | ||||
| 			int insertionCost = v1[j] + 1; | ||||
| 			int substitutionCost; | ||||
|  | ||||
| 			if (s[i] == t[j]) | ||||
| 				substitutionCost = v0[j]; | ||||
| 			else | ||||
| 				substitutionCost = v0[j] + 1; | ||||
|  | ||||
| 			v1[j + 1] = std::min({deletionCost, insertionCost, substitutionCost}); | ||||
| 		} | ||||
|  | ||||
| 		// copy v1 (current row) to v0 (previous row) for next iteration | ||||
| 		// since data in v1 is always invalidated, a swap without copy could be more efficient | ||||
| 		std::swap(v0, v1); | ||||
| 	} | ||||
|  | ||||
| 	// after the last swap, the results of v1 are now in v0 | ||||
| 	return v0[n]; | ||||
| } | ||||
|  | ||||
| bool TextOperations::textSearchSimilar(const std::string & s, const std::string & t) | ||||
| { | ||||
| 	boost::locale::generator gen; | ||||
| 	std::locale loc = gen("en_US.UTF-8"); // support for UTF8 lowercase | ||||
| 	 | ||||
| 	auto haystack = boost::locale::to_lower(t, loc); | ||||
| 	auto needle = boost::locale::to_lower(s, loc); | ||||
|  | ||||
| 	if(boost::algorithm::contains(haystack, needle)) | ||||
| 		return true; | ||||
|  | ||||
| 	for(int i = 0; i < haystack.size() - needle.size(); i++) | ||||
| 	{ | ||||
| 		auto dist = getLevenshteinDistance(haystack.substr(i, needle.size()), needle); | ||||
| 		if(needle.size() > 2 && dist <= 1) | ||||
| 			return true; | ||||
| 		else if(needle.size() > 4 && dist <= 2) | ||||
| 			return true; | ||||
| 	} | ||||
| 	 | ||||
| 	return false; | ||||
| } | ||||
|  | ||||
| VCMI_LIB_NAMESPACE_END | ||||
|   | ||||
| @@ -70,6 +70,14 @@ namespace TextOperations | ||||
| 	/// get formatted time (without date) | ||||
| 	/// timeOffset - optional parameter to modify current time by specified time in seconds | ||||
| 	DLL_LINKAGE std::string getCurrentFormattedTimeLocal(std::chrono::seconds timeOffset = {}); | ||||
|  | ||||
| 	/// Algorithm for detection of typos in words | ||||
| 	/// Determines how 'different' two strings are - how many changes must be done to turn one string into another one | ||||
| 	/// https://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows | ||||
| 	DLL_LINKAGE int getLevenshteinDistance(const std::string & s, const std::string & t); | ||||
|  | ||||
| 	/// Check if texts have similarity when typing into search boxes | ||||
| 	DLL_LINKAGE bool textSearchSimilar(const std::string & s, const std::string & t); | ||||
| }; | ||||
|  | ||||
| template<typename Arithmetic> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user