LCOV - code coverage report
Current view: top level - muse/framework/global - stringsearch.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 84.1 % 82 69
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "stringsearch.h"
      24              : 
      25              : #include <algorithm>
      26              : 
      27           18 : bool muse::operator==(const FuzzyMatch& left, const FuzzyMatch& right)
      28              : {
      29           18 :     return left.beginPos == right.beginPos
      30           18 :            && left.endPos == right.endPos
      31           36 :            && left.editDistance == right.editDistance;
      32              : }
      33              : 
      34            0 : bool muse::operator!=(const FuzzyMatch& left, const FuzzyMatch& right)
      35              : {
      36            0 :     return !(left == right);
      37              : }
      38              : 
      39              : namespace muse {
      40           12 : FuzzyMatcher& FuzzyMatcher::operator()(const std::u32string_view text, const std::u32string_view pattern, const std::size_t maxDistance)
      41              : {
      42           12 :     return match(text, pattern, maxDistance);
      43              : }
      44              : 
      45           12 : FuzzyMatcher& FuzzyMatcher::match(const std::u32string_view text, const std::u32string_view pattern, const std::size_t maxDistance)
      46              : {
      47           12 :     m_textSize = text.size();
      48           12 :     m_patternSize = pattern.size();
      49           12 :     m_suffixTable.clear();
      50           12 :     m_suffixTable.resize((m_patternSize + 1) * (m_textSize + 1));
      51           12 :     m_maxDistance = maxDistance;
      52              : 
      53          195 :     for (std::size_t textSuffixSize = 0; textSuffixSize <= m_textSize; ++textSuffixSize) {
      54          183 :         const std::size_t endPos = text.size() - textSuffixSize;
      55              :         // 0 insertions to transform empty pattern to text suffix.
      56              :         // This allows the match to be at any position in text.
      57          183 :         m_suffixTable[index(0, textSuffixSize)] = SuffixMatch{ endPos, 0 };
      58              :     }
      59              : 
      60           68 :     for (std::size_t patternSuffixSize = 1; patternSuffixSize <= m_patternSize; ++patternSuffixSize) {
      61              :         // patternSuffixSize deletions to transform pattern suffix to empty text
      62           56 :         m_suffixTable[index(patternSuffixSize, 0)] = SuffixMatch{ 0, patternSuffixSize };
      63              : 
      64           56 :         const std::size_t patternIdx = pattern.size() - patternSuffixSize;
      65         1048 :         for (std::size_t textSuffixSize = 1; textSuffixSize <= m_textSize; ++textSuffixSize) {
      66          992 :             const std::size_t textIdx = text.size() - textSuffixSize;
      67              : 
      68          992 :             if (pattern[patternIdx] == text[textIdx]) {
      69          109 :                 m_suffixTable[index(patternSuffixSize, textSuffixSize)] = m_suffixTable[index(patternSuffixSize - 1, textSuffixSize - 1)];
      70          109 :                 continue;
      71              :             }
      72              : 
      73          883 :             const SuffixMatch insertMatch = m_suffixTable[index(patternSuffixSize, textSuffixSize - 1)];
      74          883 :             const SuffixMatch replaceMatch = m_suffixTable[index(patternSuffixSize - 1, textSuffixSize - 1)];
      75          883 :             const SuffixMatch deleteMatch = m_suffixTable[index(patternSuffixSize - 1, textSuffixSize)];
      76          883 :             SuffixMatch minMatch = std::min(insertMatch, std::min(replaceMatch, deleteMatch));
      77              : 
      78          742 :             if (patternSuffixSize > 1 && textSuffixSize > 1
      79          701 :                 && pattern[patternIdx] == text[textIdx + 1]
      80         1625 :                 && pattern[patternIdx + 1] == text[textIdx]) {
      81            4 :                 const SuffixMatch transpositionMatch = m_suffixTable[index(patternSuffixSize - 2, textSuffixSize - 2)];
      82            4 :                 minMatch = std::min(transpositionMatch, minMatch);
      83              :             }
      84              : 
      85          883 :             m_suffixTable[index(patternSuffixSize, textSuffixSize)] = SuffixMatch{ minMatch.endPos, minMatch.editDistance + 1 };
      86              :         }
      87              :     }
      88              : 
      89           12 :     return *this;
      90              : }
      91              : 
      92           32 : std::optional<FuzzyMatch> FuzzyMatcher::findMatch(const std::size_t textBeginPos) const
      93              : {
      94           32 :     if (m_suffixTable.empty() || textBeginPos > m_textSize) {
      95            5 :         return std::nullopt;
      96              :     }
      97              : 
      98          192 :     for (std::size_t beginPos = textBeginPos; beginPos <= m_textSize; ++beginPos) {
      99          183 :         const std::size_t textSuffixSize = m_textSize - beginPos;
     100          183 :         const SuffixMatch match = m_suffixTable[index(m_patternSize, textSuffixSize)];
     101          183 :         if (match.editDistance <= m_maxDistance) {
     102           18 :             return FuzzyMatch{ beginPos, match.endPos, match.editDistance };
     103              :         }
     104              :     }
     105              : 
     106            9 :     return std::nullopt;
     107              : }
     108              : 
     109            1 : bool FuzzyMatcher::empty() const
     110              : {
     111            1 :     return !findMatch(0).has_value();
     112              : }
     113              : 
     114            0 : void FuzzyMatcher::clear()
     115              : {
     116            0 :     m_textSize = 0;
     117            0 :     m_patternSize = 0;
     118            0 :     m_suffixTable.clear();
     119            0 : }
     120              : 
     121           12 : FuzzyMatcher::const_iterator FuzzyMatcher::begin() const
     122              : {
     123           12 :     return Iterator(*this);
     124              : }
     125              : 
     126           42 : FuzzyMatcher::const_iterator FuzzyMatcher::end() const
     127              : {
     128           42 :     return Iterator();
     129              : }
     130              : 
     131         4176 : std::size_t FuzzyMatcher::index(const std::size_t patternSuffixSize, const std::size_t textSuffixSize) const
     132              : {
     133         4176 :     return (m_textSize + 1) * patternSuffixSize + textSuffixSize;
     134              : }
     135              : 
     136           12 : FuzzyMatcher::Iterator::Iterator(const FuzzyMatcher& matcher)
     137           12 :     : m_matcher{&matcher}
     138              : {
     139           12 :     next(0);
     140           12 : }
     141              : 
     142           18 : FuzzyMatcher::Iterator::reference FuzzyMatcher::Iterator::operator*() const
     143              : {
     144           18 :     return *m_match;
     145              : }
     146              : 
     147            0 : FuzzyMatcher::Iterator::pointer FuzzyMatcher::Iterator::operator->() const
     148              : {
     149            0 :     return m_match.operator->();
     150              : }
     151              : 
     152           18 : FuzzyMatcher::Iterator& FuzzyMatcher::Iterator::operator++()
     153              : {
     154           18 :     next(m_match->beginPos + 1);
     155           18 :     return *this;
     156              : }
     157              : 
     158            0 : FuzzyMatcher::Iterator FuzzyMatcher::Iterator::operator++(int)
     159              : {
     160            0 :     Iterator prev = *this;
     161            0 :     operator++();
     162              : 
     163            0 :     return prev;
     164              : }
     165              : 
     166           42 : bool FuzzyMatcher::Iterator::operator==(const Iterator& right) const
     167              : {
     168           42 :     return m_matcher == right.m_matcher && m_match == right.m_match;
     169              : }
     170              : 
     171           42 : bool FuzzyMatcher::Iterator::operator!=(const Iterator& right) const
     172              : {
     173           42 :     return !(*this == right);
     174              : }
     175              : 
     176           30 : void FuzzyMatcher::Iterator::next(const std::size_t textBeginPos)
     177              : {
     178           30 :     m_match = m_matcher->findMatch(textBeginPos);
     179           30 :     if (!m_match) {
     180           12 :         m_matcher = nullptr;
     181              :     }
     182           30 : }
     183              : }
        

Generated by: LCOV version 2.0-1