Определите дубликаты на основе минимального N символов из меньшей строки сравнения - PullRequest
3 голосов
/ 05 марта 2020

У меня есть два списка, каждый из которых содержит модели с общим полем ID (строковое значение). Я сравниваю идентификаторы для дублирования.

В настоящее время у меня есть оператор LINQ для определения дублированных значений идентификатора, который сохраняет их в списке строк:

List<string> duplicateRecords = testData.TestRecords.GroupBy(aa => aa.ID).Where(x => x.Count() > 1).Select(y => y.Key).ToList();

И второй оператор LINQ, который отображает список уважаемых модели, основанные на дублированном результате идентификатора LINQ:

List<Model> modelRecords = testData.Models.Where(x => duplicateRecords.Any(y => x.ID == y)).ToList();

Эти два оператора LINQ делают именно то, что я ожидал, и это здорово. Но теперь есть недавний запрос для определения дубликатов идентификаторов на основе их минимального N символов во время сравнения. Это минимальное сравнение N должно выполняться для последних N символов в строке.

EX)

  • ID1: 123 == ID2: 123
  • ID1: 0123 == ID2: 123
  • ID1: 123 = = ID2: 0123
  • ID1: 1230! = ID2: 123
  • ID1: 123! = ID2: 1230
  • ID1: 122110123 == ID2: 123

Надеемся, что эти примеры дать некоторое представление о проблеме, которую я пытаюсь решить. Это может быть сделано с помощью foreach циклов, но я пришел к выводу, что код становится очень запутанным и неуправляемым при сложном запросе списка.

Поэтому мой вопрос таков: Как можно Я использую последние N символов меньшей из двух сравниваемых строк для определения дубликатов с помощью LINQ?

Примечание: Я также очень открыт для более элегантных способов решения этой проблемы, было бы действительно полезно исключить любые решения for или foreach.

Ответы [ 4 ]

1 голос
/ 05 марта 2020

Чтобы эффективно найти дубликаты, вам нужно отсортировать ID s по длине, чтобы вы могли минимизировать необходимые сравнения. (Сортировка добавляет некоторые накладные расходы, но значительно уменьшает сравнение, которое необходимо выполнить - в моем тесте, где 9 идентификаторов имеют, а 3 являются дубликатами 8 значений, это 15 сравнений, отсортированных по сравнению с 42 несортированными.) Как только вы их отсортируете по длине, просто сравните каждый с теми, которые равны или длиннее (в случае полных дубликатов), чтобы найти, какие короткие ID необходимо сохранить, отмечая любые совпадения, чтобы вы могли пропустить их, а затем найти все Model s заканчиваются найденными совпадениями.

Создайте List из ID s, упорядоченных по их длине:

var orderedIDs = testData.TestRecords.Select(tr => tr.ID).OrderBy(id => id.Length).ToList();

Я не думаю, что есть способ сделать это эффективно с LINQ, но вложенный for l oop пропуск предыдущих совпадений оптимизирует поиск дубликатов.

Во-первых, переменные для отслеживания ID s and which идентификаторы уже совпадают:

var dupRecordSubIDs = new List<string>();
var alreadyMatched = new bool[testData.TestRecords.Count];

Теперь l oop через идентификаторы и сохраните более короткие совпадающие идентификаторы:

// foreach ID in length order
for (int n1 = 0; n1 < testData.TestRecords.Count-1; ++n1) {
    // skip the ones that already matched a shorter ID
    if (!alreadyMatched[n1]) {
        // remember if the shorter ID was alrady added
        var added_n1 = false;
        // compare the ID to all greater than or equal length IDs
        for (int n2 = n1 + 1; n2 < testData.TestRecords.Count; ++n2) {
            // if not previously matched, see if we have a new match
            if (!alreadyMatched[n2] && orderedIDs[n2].EndsWith(orderedIDs[n1])) {
                // only add the shorter ID once for new matches
                if (!added_n1) {
                    dupRecordSubIDs.Add(orderedIDs[n1]);
                    added_n1 = true;
                }
                // remember which longer IDs are already matched
                alreadyMatched[n2] = true;
            }
        }
    }
}

Теперь найдите все Model s, которые соответствуют одному из идентификаторов с дубликатом:

var modelRecords = testData.Models.Where(m => dupRecordSubIDs.Any(d => m.ID.EndsWith(d))).ToList();
1 голос
/ 05 марта 2020

Я предполагаю, что когда вход содержит 123 и 0123, вы хотите, чтобы результат имел оба значения

var input = new List<Model>()
{
    new Model {ID = "123"},
    new Model {ID = "0123"},
    new Model {ID = "1230"},
    new Model {ID = "12"},
    new Model {ID = "122110123"}
};

var result = input.Where(x => input.Any(y => y != x && (y.ID.EndsWith(x.ID) || x.ID.EndsWith(y.ID)))).ToList();
\\this will return 123, 0123 and 122110123

. Если вы хотите проверить существующий список duplicateRecords, то это должно работать :

List<Model> modelRecords = testData.Models.Where(x => duplicateRecords.Any(y => x.ID.EndsWith(y) || y.EndsWith(x.ID))).ToList();
0 голосов
/ 05 марта 2020

Если я правильно понимаю ваш вопрос, похоже, это всего лишь вопрос обрезания последних N символов в каждом свойстве ID при группировании.

Примерно так:

using System;
using System.Linq;

public class TestRecord
{
    public string ID { get; set; }
}

public class TestModel
{
    public string ID { get; set; }
}

public class Program
{
    public static void Main()
    {
        var N = 3; // This is where you define the desired N length
        var rand = new Random();

        var testRecords =  new TestRecord[]
        {
            new TestRecord {ID = "123"},
            new TestRecord {ID = "0123"},
            new TestRecord {ID = "1230"},
            new TestRecord {ID = "122110123"},
        };

        var testModels = new TestModel[]
        {
            new TestModel {ID = "123"},
            new TestModel {ID = "0123"},
            new TestModel {ID = "1230"},
            new TestModel {ID = "122110123"},
        };

        bool SortEm(string a, string b) => a.Length < b.Length ? b.EndsWith(a) : a.EndsWith(b);

        var models = testRecords
            .Where(record => testRecords.Any(target => record.ID != target.ID && SortEm(target.ID, record.ID)))
            .ToDictionary(
                key => key, 
                key => testModels.Where(testModel => SortEm(key.ID, testModel.ID)).ToArray());

        foreach (var kvp in models)
        {
            System.Console.WriteLine($"For duplicate key ({kvp.Key.ID}) found models: \r\n\t{string.Join("\r\n\t", kvp.Value.Select(x => x.ID))}");
        }
    }
}
0 голосов
/ 05 марта 2020

Я предполагаю, что ID это строка. Если это так, вы можете сделать это:

string match = "123";

var duplicate = list.Where(x=> x.Substring(x.Length - match.Length) == match).ToList();
...