using System.Collections.Generic;
using System.Text.RegularExpressions;
public static void Main()
SuggestionSearcher ss = new SuggestionSearcher();
SuggestionSearchRequest ssr = new SuggestionSearchRequest();
ssr.Lang = new List<string>() { "en" };
public class SuggestionSearcher
protected IReadOnlyDictionary<int, SuggestionBooks> Data = new Dictionary<int, SuggestionBooks>();
protected readonly Dictionary<int, AdditionalBookParams>
BookParams = new Dictionary<int, AdditionalBookParams>();
private SuggestionQueryParams _initQueryParam;
protected SuggestionQueryParams QueryParam;
protected readonly Regex ReLtMs;
private IReadOnlyDictionary<string, string> _languages = new Dictionary<string,string>();
private const string LtMsPattern =
@"^((?:lt|letters?(?:\|(?:ms|manuscripts?))?)|(?:ms|manuscripts?(?:\|(?:lt|letters?))?))
((?:18(?:4[7-9]|[4-9]\d))|19(?:0\d|1[0-5]))?(.*)";
public SuggestionSearcher()
ReLtMs = new Regex(LtMsPattern, SuggestionQueryParams.ROptions);
public IEnumerable<SuggestionDetails> GetSuggestions(SuggestionSearchRequest request)
if (string.IsNullOrWhiteSpace(request.Query) || request.Query.Length < 2)
return new List<SuggestionDetails>();
_initQueryParam = new SuggestionQueryParams {Query = request.Query};
QueryParam = new SuggestionQueryParams {Query = request.Query};
GetCachedData(request.Lang);
var response = SearchSuggestions();
return response ?? new List<SuggestionDetails>();
private void GetCachedData(List<string> lang = null)
lang.Contains(b.Value.Lang.ToLower()))
.ToDictionary(b => b.Key, b => b.Value);
public List<SuggestionDetails> SearchSuggestions()
Console.WriteLine("SearchSuggestions(): " + QueryParam.QueryParts.Length);
if (QueryParam.QueryParts.Length == 0) return null;
var foundLtMs = TryFindLtMs().ToList();
return FillResultObject(foundLtMs);
QueryParam.Query = _initQueryParam.Query;
Console.WriteLine("try foundByCode");
var foundByCode = TryFindBookByCode().ToList();
Console.WriteLine("foundByCode = " + foundByCode);
IEnumerable<SuggestionBooks> foundByName = new List<SuggestionBooks>();
if (QueryParam.QueryParts.Length > 1 || !foundByCode.Any())
foundByName = TryFindBookByName();
List<SuggestionBooks> searchResult = new List<SuggestionBooks>();
searchResult.AddRange(foundByCode);
if (foundByName != null && foundByName.Any())
if ((searchResult.Any() && BookParams.Values.Any(p => !string.IsNullOrWhiteSpace(p.AdditionalQuery)))
List<(int pubnr, string para_id, string refcode)> parseResult = TryParseAdditionalQuery(searchResult);
if (parseResult.Any(r => r.pubnr > 0))
List<SuggestionBooks> tmpResult = new List<SuggestionBooks>();
foreach (var (pubnr, paraId, refcode) in parseResult)
searchResult = tmpResult;
return FillResultObject(searchResult);
private List<(int pubnr, string para_id, string refcode)> TryParseAdditionalQuery(
List<SuggestionBooks> foundBooks)
List<(string query, int pubnr)> queries = new List<(string query, int pubnr)>();
foreach (var book in foundBooks)
private IEnumerable<SuggestionBooks> TryFindLtMs()
Regex reLtReplace = new Regex(@"(l)e(t)ters?", SuggestionQueryParams.ROptions);
Regex reMsReplace = new Regex(@"(m)anu(s)cripts?", SuggestionQueryParams.ROptions);
var match = ReLtMs.Match(QueryParam.Query);
if (match.Length <= 0 || string.IsNullOrWhiteSpace(match.Groups[2].Value)) {
return new List<SuggestionBooks>();
var result = new List<SuggestionBooks>();
foreach (var pattern in QueryParam.PossibleQueries)
var p = new Regex(pattern + "\\b", SuggestionQueryParams.ROptions);
if (!result.Any()) continue;
var paraIds = result.Select(r => new
foreach (var r in paraIds)
private IEnumerable<SuggestionBooks> TryFindBookByCode()
private IEnumerable<SuggestionBooks> TryFindBookByName()
List<SuggestionBooks> result = new List<SuggestionBooks>();
for (int i = 0; i < QueryParam.PossibleQueriesOrigin.Count; i++)
string part = QueryParam.PossibleQueriesOrigin[i];
var pattern = QueryParam.PossibleQueries[i];
if (!result.Any()) continue;
foreach (var res in result)
private List<SuggestionDetails> FillResultObject(IEnumerable<SuggestionBooks> searchResult)
public class SuggestionQueryParams
private string _query = "";
if (string.IsNullOrWhiteSpace(value)) return;
QueryParts = SplitQuery(Query).Select(r => Regex.Escape(r)).ToArray();
PossibleQueries = MakePossibleQueries(QueryParts);
public List<Regex> PossibleQueries { get; set; } = new List<Regex>();
public List<string> PossibleQueriesOrigin { get; set; } = new List<string>();
public string[] QueryParts { get; set; }
private readonly Regex _reWordSplit;
public const RegexOptions ROptions = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant |
RegexOptions.IgnorePatternWhitespace;
private const string WordSplitPattern = @"[\s:,.]+";
private const string QueryJoinPattern = @"(?:[\p{P}\p{S}\p{Z}])+";
public SuggestionQueryParams()
_reWordSplit = new Regex(WordSplitPattern, ROptions);
private List<Regex> MakePossibleQueries(string[] queryParts)
List<Regex> possibleQueries = new List<Regex>();
List<string> possibleQueriesOrigin = new List<string>();
Stack<string> s = new Stack<string>(queryParts);
possibleQueries.Add(new Regex("^" + string.Join(QueryJoinPattern, queryParts), ROptions));
possibleQueriesOrigin.Add(string.Join(" ", queryParts));
while (s.TryPop(out var st))
possibleQueriesOrigin.Add(string.Join(" ", s.Reverse()));
possibleQueries.Add(s.Count == 1
? new Regex("^" + string.Join(QueryJoinPattern, s.Reverse()) + "\\b", ROptions)
: new Regex("^" + string.Join(QueryJoinPattern, s.Reverse()), ROptions));
PossibleQueriesOrigin = possibleQueriesOrigin;
private string[] SplitQuery(string query)
return _reWordSplit.Split(query);
query = SearchUtil.RemoveSpecialChars(query);
return _reWordSplit.Split(query);
internal class SearchUtil
public static string RemoveSpecialChars(string s)
s = Regex.Replace(s, "[$&+,:;=?@#|\'<>.\"^*()%!\\-\\/\\[\\]\\\\~]", " ");
s = Regex.Replace(s, @"\s+", " ");
public class SuggestionDetails
public SuggestionResultType Type;
public class SuggestionBooks
public class AdditionalBookParams
public string AdditionalQuery;
public string TitleReplacement;
public enum SuggestionResultType
public class SuggestionSearchRequest
public string Query { get; set; }
public List<string> Lang { get; set; }