public List <int> FindClosest(int setIndex, MinHash minHasher, double treshold) { //First find potential "close" candidates HashSet <int> potentialSetIndexes = new HashSet <int>(); for (int b = 0; b < m_numBands; b++) { //combine all 5 MH values and then hash get its hashcode int sum = 0; for (int i = 0; i < ROWSINBAND; i++) { sum += m_minHashMatrix[setIndex, b *ROWSINBAND + i]; } foreach (var i in m_lshBuckets[b][sum]) { potentialSetIndexes.Add(i); } } List <int> result_list = new List <int>(); foreach (int candidateIndex in potentialSetIndexes.Where(i => i != setIndex)) { double similarity = minHasher.ComputeSimilarity(m_minHashMatrix, setIndex, candidateIndex); if (similarity > treshold) { result_list.Add(candidateIndex); } } return(result_list); }
static void Main(string[] args) { // Путь к исходному тексту string SENTx_path = args[0]; // Путь к файлу, куда будем записывать дубликаты string result_path = args[1]; // Пороги похожести, зависят от длины предложений. double threshold2 = double.Parse(args[2], System.Globalization.CultureInfo.InvariantCulture); double threshold1 = threshold2 * 0.8; Console.WriteLine("threshold1={0} threshold2={1}", threshold1, threshold2); // Макс. число предложений int max_sent = int.Parse(args[3]); HashSet <long> sample_hashes = new HashSet <long>(); // для устранения повторов в результатах MD5 md5 = MD5.Create(); DateTime started = DateTime.Now; int n_groups = 0; using (System.IO.StreamWriter wrt = new System.IO.StreamWriter(result_path)) { // Загружаем предложения в vals Console.WriteLine("Processing {0}...", SENTx_path); List <string> vals = new List <string>(); using (System.IO.StreamReader rdr = new System.IO.StreamReader(SENTx_path)) { while (!rdr.EndOfStream && vals.Count <= max_sent) { string line = rdr.ReadLine(); if (line == null) { break; } line = NormalizeSent(line.Trim().Replace(" ", " ").Replace(" ", " ")); vals.Add(line); } } Console.WriteLine("{0} lines in {1}", vals.Count, SENTx_path); // ----------------------------------------------------------------------- Dictionary <string, int> shingle2id = new Dictionary <string, int>(); List <HashSet <int> > val_sets = new List <HashSet <int> >(); foreach (string v in vals) { string uv = SetSimilarity2.Tools.NormalizeStr(v); HashSet <int> v_set = new HashSet <int>(); for (int i0 = 0; i0 < uv.Length - 3; ++i0) { string shingle = uv.Substring(i0, 3); int id = -1; if (!shingle2id.TryGetValue(shingle, out id)) { id = shingle2id.Count; shingle2id.Add(shingle, id); } v_set.Add(id); } val_sets.Add(v_set); } /* * // --- отладка --- * { * int iset1 = vals.IndexOf("Ты меня слышишь?"); * int iset2 = vals.IndexOf("Ты слышишь меня?"); * double sim0 = SetSimilarity2.Tools.CalcJackardSim(val_sets[iset1], val_sets[iset2]); * Console.WriteLine("iset1={0} iset2={1} sim={2}", iset1, iset2, sim0); * } * // --------------- */ Console.WriteLine("Hashing..."); // ------------------------------------------------------------------ SetSimilarity2.MinHash minhash = new SetSimilarity2.MinHash(shingle2id.Count); // --- для отладки //string abc = minhash.GetABC(); // --------------- int[,] SIG = minhash.GetSignatureMatrix(val_sets); double sim12 = minhash.ComputeSimilarity(SIG, 0, 1); // Решение с использованием Local Sensitiviy Hash SetSimilarity2.LSH lsh = new SetSimilarity2.LSH(SIG, val_sets); Console.WriteLine("Searching duplicates..."); List <Tuple <int, int> > sim_pairs = new List <Tuple <int, int> >(); for (int iset1 = 0; iset1 < val_sets.Count; ++iset1) { List <int> sets2 = lsh.FindClosest(iset1, minhash, threshold1); List <int> isets_bucket = new List <int>(); isets_bucket.Add(iset1); List <string> toks1 = StringToWords(vals[iset1]); foreach (int iset2 in sets2) { if (iset2 > iset1) { double sim0 = SetSimilarity2.Tools.CalcJackardSim(val_sets[iset1], val_sets[iset2]); if (sim0 > threshold2) { // Дополнительная проверка строк, в частности надо убедиться, // что частица НЕ/НИ осталась перед тем же словом. List <string> toks2 = StringToWords(vals[iset2]); if (SetsAreEqual(toks1, toks2)) { isets_bucket.Add(iset2); //Console.WriteLine("\nistr1={0}\nistr2={1}\nstr1={2}\nstr2={3}\nsim={4:N5}", iset1, iset2, vals[iset1], vals[iset2], sim0); } } } } if (isets_bucket.Count > 1) { List <string> printed = new List <string>(); foreach (int isetx in isets_bucket) { string line = NormalizeSent(vals[isetx]); if (!printed.Contains(line)) { byte[] hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(line)); Int64 ihash1 = BitConverter.ToInt64(hash, 0); Int64 ihash2 = BitConverter.ToInt64(hash, 8); Int64 ihash = ihash1 ^ ihash2; if (!sample_hashes.Contains(ihash)) { sample_hashes.Add(ihash); printed.Add(line); } } } if (printed.Count > 1) { foreach (string l in printed) { wrt.WriteLine("{0}", l); } wrt.WriteLine("\n"); n_groups++; } } if ((iset1 % 1000) == 0) { Console.Write("iset1={0}/{1} n_groups={2}\r", iset1, vals.Count, n_groups); } } } DateTime finished = DateTime.Now; Console.WriteLine("Done via LSH, elapsed time={0} sec", (finished - started).TotalSeconds); return; }