public IEnumerable <string> LookupWords(string input)
        {
            var escaped = Regex.Escape(start + input + end);

            if (PatternMatchesEveryWord(input))
            {
                return(Enumerable.Empty <string>());
            }

            var groupsReplaced = groupMatcher.Replace(escaped, match =>
            {
                var text            = match.Groups[1].Value;
                var kanjiCandidates = lookup.SelectRadical(searcher.Search(text).Select(r => r.Radical)).Kanji;
                return("(" + string.Join("|", kanjiCandidates.Select(k => k.ToString())) + ")");
            });
            var regex = new Regex(
                groupsReplaced
                .Replace(@"\?", ".")
                .Replace(@"?", ".")
                .Replace(@"\*", $"[^{end}]*?")
                .Replace(@"*", $"[^{end}]*?"));

            return(regex.Matches(allWords)
                   .Cast <Match>()
                   .Select(m => m.Value.TrimStart(startArr).TrimEnd(endArr))
                   .ToList());
        }
        public Option <KanjiLookupResult, Error> SelectRadicals(
            string query,
            string sort,
            string select   = null,
            string deselect = null)
        {
            // validation and normalization
            query = query ?? "";
            var sortingCriteriaIndexOpt = sort == null
                ? Option.Some <int>(0)
                : radicalLookup.SortingCriteria
                                          .FindIndexOrNone(criterion => criterion.Description == sort);

            if (!sortingCriteriaIndexOpt.HasValue)
            {
                return(Option.None <KanjiLookupResult, Error>(
                           new Error(ErrorCodes.InvalidInput, "Invalid sorting criterion", new[] { nameof(sort), sort })));
            }

            var sortingCriteriaIndex = sortingCriteriaIndexOpt.ValueOrFailure();

            // get corresponding radicals
            var radicalSearchResults = radicalSearcher.Search(query);
            var usedRadicals         = EnumerableExt.DistinctBy(radicalSearchResults, r => r.Text)
                                       .Select(r => new KeyValuePair <string, string>(r.Text, r.Radical.ToString()));

            // select
            if (select != null)
            {
                query += " " + select;
            }

            // unselect
            if (deselect != null)
            {
                foreach (var kvp in usedRadicals.Where(x => x.Value == deselect))
                {
                    var name = kvp.Key;
                    query = query.Replace(name, "");
                }
            }

            query = query.Trim();

            // search again, with the new radicals
            radicalSearchResults = radicalSearcher.Search(query);
            var radicals = radicalSearchResults
                           .Select(result => result.Radical)
                           .ToList();

            if (radicals.Count == 0)
            {
                return(Option.Some <KanjiLookupResult, Error>(new KanjiLookupResult
                                                              (
                                                                  newQuery: query,
                                                                  kanji: Array.Empty <string>(),
                                                                  radicals: radicalLookup.AllRadicals
                                                                  .Select(r => new RadicalState(r.ToString(), isAvailable: true, isSelected: false))
                                                                  .ToList()
                                                              )));
            }

            var selectionResult = radicalLookup.SelectRadical(radicals, sortingCriteriaIndex);
            var usedRadicalsSet = new HashSet <CodePoint>(radicalSearchResults.Select(r => r.Radical));

            return(Option.Some <KanjiLookupResult, Error>(new KanjiLookupResult
                                                          (
                                                              newQuery: query,
                                                              kanji: selectionResult.Kanji
                                                              .Select(k => k.ToString())
                                                              .ToList(),
                                                              radicals: selectionResult.PossibleRadicals
                                                              .Select(k => new RadicalState(k.Key.ToString(), k.Value || usedRadicalsSet.Contains(k.Key), usedRadicalsSet.Contains(k.Key)))
                                                              .ToList()
                                                          )));
        }