@@ -146,4 +146,93 @@ std::vector<std::string> expandPatterns(
146146 preparedPatterns.end ());
147147
148148 return preparedPatterns;
149+ }
150+
151+
152+ std::vector<std::pair<std::string, bool >> expandPatternsWithOrientation (
153+ const std::vector<std::string> &rawPatterns,
154+ uint8_t editDistance,
155+ const std::string &canonicalFwd) {
156+
157+ // Helper: check if pattern is closer to canonicalFwd than canonicalRev
158+ auto isCloserToFwd = [&](const std::string &pattern) -> bool {
159+ std::string canonicalRev = revCom (canonicalFwd);
160+ size_t patLen = pattern.size ();
161+ size_t canLen = canonicalFwd.size ();
162+
163+ // Same length: direct Hamming comparison
164+ if (patLen == canLen) {
165+ uint8_t distFwd = 0 , distRev = 0 ;
166+ for (size_t i = 0 ; i < patLen; ++i) {
167+ if (pattern[i] != canonicalFwd[i]) ++distFwd;
168+ if (pattern[i] != canonicalRev[i]) ++distRev;
169+ }
170+ // Tie-break: lexicographically smaller canonical wins (canonicalFwd is always lex-smaller)
171+ return distFwd <= distRev;
172+ }
173+
174+ // Different lengths: find best alignment for each
175+ const std::string &shorter = (patLen < canLen) ? pattern : canonicalFwd;
176+ const std::string &longer = (patLen < canLen) ? canonicalFwd : pattern;
177+ const std::string longerRev = (patLen < canLen) ? canonicalRev : revCom (pattern);
178+ size_t shortLen = shorter.size ();
179+ size_t longLen = longer.size ();
180+
181+ uint8_t minDistFwd = 255 , minDistRev = 255 ;
182+ for (size_t offset = 0 ; offset <= longLen - shortLen; ++offset) {
183+ uint8_t dist = 0 ;
184+ for (size_t i = 0 ; i < shortLen; ++i) {
185+ if (shorter[i] != longer[offset + i]) ++dist;
186+ }
187+ minDistFwd = std::min (minDistFwd, dist);
188+ }
189+ for (size_t offset = 0 ; offset <= longLen - shortLen; ++offset) {
190+ uint8_t dist = 0 ;
191+ for (size_t i = 0 ; i < shortLen; ++i) {
192+ if (shorter[i] != longerRev[offset + i]) ++dist;
193+ }
194+ minDistRev = std::min (minDistRev, dist);
195+ }
196+ return minDistFwd <= minDistRev;
197+ };
198+
199+ std::vector<std::pair<std::string, bool >> result;
200+
201+ for (const auto &seed : rawPatterns) {
202+ if (seed.empty ()) continue ;
203+
204+ std::vector<std::string> combinations;
205+ std::string working = seed;
206+ getCombinations (seed, working, 0 , combinations);
207+
208+ for (const auto &combo : combinations) {
209+ // Determine if this seed combo is forward-oriented
210+ bool seedIsForward = isCloserToFwd (combo);
211+
212+ std::vector<std::string> variants;
213+ variants.push_back (combo);
214+
215+ if (editDistance > 0 ) {
216+ auto edits = getEditVariants (combo, editDistance);
217+ variants.insert (variants.end (), edits.begin (), edits.end ());
218+ }
219+
220+ for (const auto &variant : variants) {
221+ // Forward variant inherits seed orientation
222+ result.emplace_back (variant, seedIsForward);
223+ // RevCom is opposite orientation
224+ result.emplace_back (revCom (variant), !seedIsForward);
225+ }
226+ }
227+ }
228+
229+ // Sort and deduplicate, keeping first occurrence (orientation consistent)
230+ std::sort (result.begin (), result.end (),
231+ [](const auto &a, const auto &b) { return a.first < b.first ; });
232+ result.erase (
233+ std::unique (result.begin (), result.end (),
234+ [](const auto &a, const auto &b) { return a.first == b.first ; }),
235+ result.end ());
236+
237+ return result;
149238}
0 commit comments