winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ARPCorrelationAlgorithms.cpp (9261B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "winget/ARPCorrelationAlgorithms.h"
      5 
      6 using namespace AppInstaller::Manifest;
      7 using namespace AppInstaller::Repository;
      8 using namespace AppInstaller::Utility;
      9 
     10 namespace AppInstaller::Repository::Correlation
     11 {
     12     using WordSequence = WordsEditDistanceMatchConfidenceAlgorithm::WordSequence;
     13 
     14     namespace
     15     {
     16         // A simple matrix class to hold score tables without having to allocate multiple arrays.
     17         struct Matrix
     18         {
     19             Matrix(size_t rows, size_t columns) : m_rows(rows), m_columns(columns), m_data(rows* columns) {}
     20 
     21             double& At(size_t i, size_t j)
     22             {
     23                 return m_data[i * m_columns + j];
     24             }
     25 
     26         private:
     27             size_t m_rows;
     28             size_t m_columns;
     29             std::vector<double> m_data;
     30         };
     31 
     32         double EditDistanceScore(const std::vector<std::string>& s1, const std::vector<std::string>& s2)
     33         {
     34             // Naive implementation of edit distance (scaled over the sequence size).
     35             // This considers only the operations of adding and removing elements.
     36 
     37             if (s1.empty() || s2.empty())
     38             {
     39                 return 0;
     40             }
     41 
     42             // distance[i, j] = distance between s1[0:i] and s2[0:j]
     43             // We don't need to hold more than two rows at a time, but it's simpler to keep the whole table.
     44             Matrix distance(s1.size() + 1, s2.size() + 1);
     45 
     46             for (size_t i = 0; i < s1.size(); ++i)
     47             {
     48                 for (size_t j = 0; j < s2.size(); ++j)
     49                 {
     50                     double& d = distance.At(i, j);
     51                     if (s1[i] == s2[j])
     52                     {
     53                         // If the two elements are equal, the distance is the same as from one element before.
     54                         // In case we are on the first element of one of the two sequences, the distance is
     55                         // equal to the cost of adding all the previous elements in the other
     56                         if (i == 0)
     57                         {
     58                             d = static_cast<double>(j);
     59                         }
     60                         else if (j == 0)
     61                         {
     62                             d = static_cast<double>(i);
     63                         }
     64                         else
     65                         {
     66                             d = distance.At(i - 1, j - 1);
     67                         }
     68                     }
     69                     else
     70                     {
     71                         // If the two elements are distinct, the score is the cost of removing the last element
     72                         // in one sequence plus the cost of editing the remainder of both.
     73                         if (i > 0 && j > 0)
     74                         {
     75                             d = 1 + std::min(distance.At(i - 1, j), distance.At(i, j - 1));
     76                         }
     77                         else if (i > 0)
     78                         {
     79                             d = 1 + distance.At(i - 1, j);
     80                         }
     81                         else if (j > 0)
     82                         {
     83                             d = 1 + distance.At(i, j - 1);
     84                         }
     85                         else
     86                         {
     87                             // Remove one and add the other
     88                             d = 2;
     89                         }
     90                     }
     91                 }
     92             }
     93 
     94             // Maximum distance is equal to the sum of both lengths (removing all elements from one and adding all the elements from the other).
     95             // We use that to scale to [0,1].
     96             // A smaller distance represents a higher match, so we subtract from 1 for the final score
     97             double editDistance = distance.At(s1.size() - 1, s2.size() - 1);
     98             return 1 - editDistance / (static_cast<uint64_t>(s1.size()) + static_cast<uint64_t>(s2.size()));
     99         }
    100     }
    101 
    102     WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(const WordSequence& name, const WordSequence& publisher) : Name(name), Publisher(publisher)
    103     {
    104         NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end());
    105         NamePublisher.insert(NamePublisher.end(), name.begin(), name.end());
    106     }
    107 
    108     WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(WordSequence&& name, WordSequence&& publisher) : Name(std::move(name)), Publisher(std::move(publisher))
    109     {
    110         NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end());
    111         NamePublisher.insert(NamePublisher.end(), name.begin(), name.end());
    112 
    113     }
    114 
    115     void WordsEditDistanceMatchConfidenceAlgorithm::Init(const AppInstaller::Manifest::Manifest& manifest)
    116     {
    117         // We will use the name and publisher from each localization.
    118         m_namesAndPublishers.clear();
    119 
    120         WordSequence defaultPublisher;
    121         if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher))
    122         {
    123             defaultPublisher = NormalizeAndPreparePublisher(manifest.DefaultLocalization.Get<Manifest::Localization::Publisher>());
    124         }
    125 
    126         if (manifest.DefaultLocalization.Contains(Manifest::Localization::PackageName))
    127         {
    128             WordSequence defaultName = NormalizeAndPrepareName(manifest.DefaultLocalization.Get<Manifest::Localization::PackageName>());
    129             m_namesAndPublishers.emplace_back(defaultName, defaultPublisher);
    130 
    131             for (const auto& loc : manifest.Localizations)
    132             {
    133                 if (loc.Contains(Manifest::Localization::PackageName) || loc.Contains(Manifest::Localization::Publisher))
    134                 {
    135                     auto name = loc.Contains(Manifest::Localization::PackageName) ? NormalizeAndPrepareName(loc.Get<Manifest::Localization::PackageName>()) : defaultName;
    136                     auto publisher = loc.Contains(Manifest::Localization::Publisher) ? NormalizeAndPreparePublisher(loc.Get<Manifest::Localization::Publisher>()) : defaultPublisher;
    137 
    138                     m_namesAndPublishers.emplace_back(std::move(name), std::move(publisher));
    139                 }
    140             }
    141         }
    142     }
    143 
    144     double WordsEditDistanceMatchConfidenceAlgorithm::ComputeConfidence(const ARPEntry& arpEntry) const
    145     {
    146         // Name and Publisher are available as multi properties, but for ARP entries there will only be 0 or 1 values.
    147         NameAndPublisher arpNameAndPublisher(
    148             NormalizeAndPrepareName(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Name).get()),
    149             NormalizeAndPreparePublisher(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Publisher).get()));
    150 
    151         // Get the best score across all localizations
    152         double bestMatchingScore = 0;
    153         for (const auto& manifestNameAndPublisher : m_namesAndPublishers)
    154         {
    155             // Sometimes the publisher may be included in the name, for example Microsoft PowerToys as opposed to simply PowerToys.
    156             // This may happen both in the ARP entry and the manifest. We try adding it in case it is in one but not in both.
    157             auto nameScore = EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.Name);
    158 
    159             // Ignore cases where the name is not at all similar to avoid matching due to publisher only
    160             if (nameScore < m_nameMatchingScoreMinThreshold)
    161             {
    162                 continue;
    163             }
    164 
    165             auto publisherScore = EditDistanceScore(manifestNameAndPublisher.Publisher, arpNameAndPublisher.Publisher);
    166             auto namePublisherScore = std::max(
    167                 EditDistanceScore(manifestNameAndPublisher.NamePublisher, arpNameAndPublisher.Name),
    168                 EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.NamePublisher));
    169 
    170             // Use the best between considering name and publisher as a single string or separately.
    171             auto score = std::max(
    172                 nameScore * m_nameMatchingScoreWeight + publisherScore * (1 - m_nameMatchingScoreWeight),
    173                 namePublisherScore);
    174             bestMatchingScore = std::max(bestMatchingScore, score);
    175         }
    176 
    177         // Factor in whether this entry is new
    178         auto result = bestMatchingScore * m_stringMatchingWeight + (arpEntry.IsNewOrUpdated ? 1 : 0) * (1 - m_stringMatchingWeight);
    179 
    180         return result;
    181     }
    182 
    183     WordSequence WordsEditDistanceMatchConfidenceAlgorithm::PrepareString(std::string_view s) const
    184     {
    185         return Utility::SplitIntoWords(Utility::FoldCase(s));
    186     }
    187 
    188     WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPrepareName(std::string_view name) const
    189     {
    190         return PrepareString(m_normalizer.NormalizeName(name).Name());
    191     }
    192 
    193     WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPreparePublisher(std::string_view publisher) const
    194     {
    195         return PrepareString(m_normalizer.NormalizePublisher(publisher));
    196     }
    197 }