Как оценить уникальность текста?
Возникла задача оценить уникальность текста. Я разбиваю тексты на Шинглы и рассчитываю их косинусное сходство.
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;
}
}
Как это всё оценить в процентах?