private static List <MPObject> FilterBySearchParameters(List <MPObject> listToFilter, SearchParameters searchParameters) { List <MPObject> result = listToFilter.ToList(); if (searchParameters == null) { return(result); } if (searchParameters.OnlyRoutes) { result.RemoveAll(p => !(p is Route)); } if (searchParameters.OnlyAreas) { result.RemoveAll(p => !(p is Area)); } //Note: searchParameters.SpecificLocation is handled by the GetPossibleQueryAndLocationGroups function return(result); }
private static MPObject DetermineBestMatch(List <MPObject> allMatches, string searchQuery, SearchParameters searchParameters = null) { allMatches = FilterBySearchParameters(allMatches, searchParameters); if (allMatches.Count == 0) { return(null); } //=============== CHOOSE THE BEST MATCH =============== //First priority: items where the name matches the search query exactly CASE SENSITIVE List <MPObject> filteredItems = allMatches.Where(p => Utilities.StringsEqual(searchQuery, p.Name, false)).ToList(); //Second priority: items where the name matches the search query exactly (but case-insensitive) if (!filteredItems.Any()) { filteredItems = allMatches.Where(p => Utilities.StringsEqual(searchQuery, p.Name)).ToList(); } //Third priority: items where the name matches the FILTERED (no symbols or spaces, case insensitive) search query exactly if (!filteredItems.Any()) { filteredItems = allMatches.Where(p => Utilities.StringsEqual(Utilities.FilterStringForMatch(searchQuery), p.NameForMatch)).ToList(); } //[IN THE FUTURE]Fourth priority: items with a levenshtein distance less than 3 //Fifth priority: items where the name contains the search query CASE SENSITIVE if (!filteredItems.Any()) { filteredItems = allMatches.Where(p => Utilities.StringContains(p.Name, searchQuery, false)).ToList(); } //Sixth priority: items where the name contains the search query (case-insensitive) if (!filteredItems.Any()) { filteredItems = allMatches.Where(p => Utilities.StringContains(p.Name, searchQuery)).ToList(); } //Seventh priority: items where the name contains the FITLERED search query (case-insensitive) if (!filteredItems.Any()) { filteredItems = allMatches.Where(p => Utilities.StringContains(p.NameForMatch, Utilities.FilterStringForMatch(searchQuery))).ToList(); } //=============== RESULTS PROCESSING =============== //Remove parents where the child was also matched (eg "Alexisizer" area vs "Alexisizer" route) filteredItems.RemoveAll(parent => filteredItems.Find(child => child.ParentIDs.Contains(parent.ID)) != null); //=============== RETURN RESULTS =============== //Return the most popular filtered item if (filteredItems.Any()) { return(FilterByPopularity(filteredItems)); } //Otherwise, if we haven't matched anything above, just return the most popular item out of allMatches return(FilterByPopularity(allMatches)); }
private static List <Tuple <string, string> > GetPossibleQueryAndLocationGroups(string queryText, SearchParameters searchParameters = null) { List <Tuple <string, string> > result = new List <Tuple <string, string> >(); if (searchParameters != null && !string.IsNullOrEmpty(searchParameters.SpecificLocation)) { result.Add(new Tuple <string, string>(queryText, searchParameters.SpecificLocation)); } else { result.Add(new Tuple <string, string>(queryText, "")); //Add the full query as a possible match Regex locationWordsRegex = new Regex(@"(\s+of\s+)|(\s+on\s+)|(\s+at\s+)|(\s+in\s+)"); string possibleSearchText, possibleLocation; if (queryText.Contains(",")) //Location by comma (eg "Send me on my way, Red River Gorge") { possibleSearchText = queryText.Split(',')[0].Trim(); possibleLocation = queryText.Split(',')[1].Trim(); result.Add(new Tuple <string, string>(possibleSearchText, possibleLocation)); } else if (locationWordsRegex.IsMatch(queryText)) //Location by "location word" (eg "Butterfly Blue in Red River Gorge") { foreach (Match match in locationWordsRegex.Matches(queryText)) { possibleSearchText = queryText.Split(new string[] { match.Value }, StringSplitOptions.None)[0].Trim(); possibleLocation = queryText.Split(new string[] { match.Value }, StringSplitOptions.None)[1].Trim(); result.Add(new Tuple <string, string>(possibleSearchText, possibleLocation)); } } } return(result); }
private static SearchResult DetermineBestMatch(List <SearchResult> searchResults, string searchQuery, SearchParameters searchParameters = null) { MPObject bestMatch = DetermineBestMatch(searchResults.Select(p => p.FilteredResult).ToList(), searchQuery, searchParameters); return(searchResults.Find(p => p.FilteredResult == bestMatch)); }
public static SearchResult Search(string queryText, SearchParameters searchParameters = null) { WriteToConsole($"\tGetting info from MountainProject for \"{queryText}\""); Stopwatch searchStopwatch = Stopwatch.StartNew(); SearchResult searchResult; List <SearchResult> possibleResults = new List <SearchResult>(); List <Tuple <string, string> > possibleQueryAndLocationGroups = GetPossibleQueryAndLocationGroups(queryText, searchParameters); foreach (Tuple <string, string> group in possibleQueryAndLocationGroups) { string query = Utilities.FilterStringForMatch(Utilities.EnforceWordConsistency(group.Item1)); string location = Utilities.FilterStringForMatch(Utilities.EnforceWordConsistency(group.Item2)); List <MPObject> possibleMatches = DeepSearch(query, DestAreas); possibleMatches = FilterBySearchParameters(possibleMatches, searchParameters); MPObject filteredResult; SearchResult possibleResult; if (!string.IsNullOrEmpty(location)) { Dictionary <MPObject, Area> resultsWithLocations = GetMatchingResultLocationPairs(possibleMatches, location); filteredResult = DetermineBestMatch(resultsWithLocations.Keys.ToList(), group.Item1, searchParameters); if (filteredResult == null) { continue; } possibleResult = new SearchResult() { AllResults = resultsWithLocations.Keys.ToList(), FilteredResult = filteredResult, RelatedLocation = resultsWithLocations[filteredResult] }; } else { filteredResult = DetermineBestMatch(possibleMatches, group.Item1, searchParameters); if (filteredResult == null) { continue; } possibleResult = new SearchResult() { AllResults = possibleMatches, FilteredResult = filteredResult }; } if (!possibleResult.IsEmpty()) { possibleResults.Add(possibleResult); } } if (possibleResults.Any()) { searchResult = DetermineBestMatch(possibleResults, queryText, searchParameters); } else if (searchParameters != null && !string.IsNullOrEmpty(searchParameters.SpecificLocation)) { //If we used searchParameters to find a specific location, but couldn't find a match at that location, //we should return an empty result searchResult = new SearchResult(); } else { string filteredQuery = Utilities.FilterStringForMatch(queryText); List <MPObject> results = FilterBySearchParameters(DeepSearch(filteredQuery, DestAreas), searchParameters); MPObject filteredResult = DetermineBestMatch(results, queryText, searchParameters); searchResult = new SearchResult() { AllResults = results, FilteredResult = filteredResult }; } WriteToConsole($"\tFound {searchResult.AllResults.Count} matching results from MountainProject in {searchStopwatch.ElapsedMilliseconds} ms"); searchResult.TimeTakenMS = searchStopwatch.ElapsedMilliseconds; return(searchResult); }