Как оценить уникальность текста?

Возникла задача оценить уникальность текста. Я разбиваю тексты на Шинглы и рассчитываю их косинусное сходство.

public abstract class ShingleBased
{
    private const int Default_K = 3;

    protected int K { get; }

    private static readonly Regex SpaceReg = new("\\s+");

    protected ShingleBased(int k)
    {
        if (k <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(k), "k should be positive");
        }

        K = k;
    }

    protected ShingleBased() : this(Default_K) { }

    public IDictionary<string, int> GetProfile(string s)
    { 
        Dictionary<string, int> shingles = new();

        string stringNoPunctuation = string.Concat(s.Where(x => !char.IsPunctuation(x)));

        string stringNoSpace = SpaceReg.Replace(stringNoPunctuation, " ");

        for (int i = 0; i < (stringNoSpace.Length - K + 1); i++)
        {
            var shingle = stringNoSpace.Substring(i, K);

            if (shingles.TryGetValue(shingle, out var old))
            {
                shingles[shingle] = old + 1;
            }
            else
            {
                shingles[shingle] = 1;
            }
        }

        return new ReadOnlyDictionary<string, int>(shingles);
    }
}

public class Cosine : ShingleBased, INormalizedStringDistance
{
    public Cosine() { }

    public Cosine(int k) : base(k) { }

    public double Distance(string s1, string s2)
    {
        return Similarity(s1, s2);
    }

    /// <summary>
    /// Вычисление косинусного сходства
    /// </summary>
    private double Similarity(string s1, string s2)
    {
        if (string.IsNullOrWhiteSpace(s1))
            throw new ArgumentNullException(nameof(s1));

        if (string.IsNullOrWhiteSpace(s2))
            throw new ArgumentNullException(nameof(s2));

        if (s1.Equals(s2))
            return 1;

        if (s1.Length < K || s2.Length < K)
            return 0;

        IDictionary<string, int> profile1 = GetProfile(s1);
        IDictionary<string, int> profile2 = GetProfile(s2);

        return DotProduct(profile1, profile2) / (Norm(profile1) * Norm(profile2));
    }

    /// <summary>
    /// Вычисление нормы L2:sqrt(Sum_i( v_i²)).
    /// </summary>
    private static double Norm(IDictionary<string, int> profile)
    {
        double agg = 0;

        foreach (var entry in profile)
        {
            agg += 1.0 * entry.Value * entry.Value;
        }

        return Math.Sqrt(agg);
    }

    private static double DotProduct(IDictionary<string, int> profile1,
        IDictionary<string, int> profile2)
    {
        IDictionary<string, int> smallProfile = profile2;
        IDictionary<string, int> largeProfile = profile1;

        if (profile1.Count < profile2.Count)
        {
            smallProfile = profile1;
            largeProfile = profile2;
        }

        double agg = 0;

        foreach (var entry in smallProfile)
        {
            if (!largeProfile.TryGetValue(entry.Key, out int i)) continue;

            agg += 1.0 * entry.Value * i;
        }

        return agg;
    }
}

Как это всё оценить в процентах?


Ответы (0 шт):