Как удалить элементы с помощью linq - PullRequest
0 голосов
/ 31 августа 2018

У меня есть следующий массив, представляющий элементы, которые я пытаюсь удалить из определенного экземпляра структуры данных:

string[] diseasesToRemove = new string[] { "D1", "D3", "D5" };

Я могу сделать это, используя следующее:

for (int i = 0; i < healthGroup.DiseaseGroups.Length; i++)
{
    var dgDiseases = new List<Disease>(healthGroup.DiseaseGroups[i].Diseases);
    for (int j = 0; j < dgDiseases.Count; j++)
    {
        if (diseasesToRemove.Contains(dgDiseases[j].Name))
        {                 
            dgDiseases.RemoveAt(j);
            j--;
        }
    }
    healthGroup.DiseaseGroups[i].Diseases = dgDiseases.ToArray();
}

Однако я уверен, что есть лучший способ использовать Linq или что-то еще. Есть ли?

Вот классы:

public class HealthGroup
{
    public DiseaseGroup[] DiseaseGroups { get; set; }

    public HealthGroup(DiseaseGroup[] diseaseGroups)
    {
        DiseaseGroups = diseaseGroups;
    }
}

public class DiseaseGroup
{
    public string Name { get; set; }
    public Disease[] Diseases;

    public DiseaseGroup(string name, Disease[] diseases)
    {
        Name = name;
        Diseases = diseases;
    }
}

public class Disease
{
    public string Name { get; set; } = "My Disease";
    public int Risk { get; set; } = 7;

    public Disease(string name, int risk)
    {
        Name = name;
        Risk = risk;
    }

    public override string ToString()
    {
        return $"{Name} with risk {Risk}";
    }
}

И несколько шаблонов для создания экземпляра:

Disease d1 = new Disease("D1", 1);
Disease d2 = new Disease("D2", 2);
Disease d3 = new Disease("D3", 3);
Disease d4 = new Disease("D4", 4);
Disease d5 = new Disease("D5", 5);
Disease d6 = new Disease("D6", 6);
Disease d7 = new Disease("D7", 7);

DiseaseGroup dg1 = new DiseaseGroup("DG1", new Disease[] { d1, d2 });
DiseaseGroup dg2 = new DiseaseGroup("DG2", new Disease[] { d3, d4, d5 });
DiseaseGroup dg3 = new DiseaseGroup("DG3", new Disease[] { d6, d7 });

HealthGroup healthGroup = new HealthGroup(new DiseaseGroup[] { dg1, dg2, dg3 });

Ответы [ 2 ]

0 голосов
/ 31 августа 2018

Эти виды работ по фильтрации можно выразить в виде объединений. Они решают проблему многократной итерации одной группы для проверки совпадений в другой группе (.Contains здесь O (n) и часто вызывается ... этого не должно происходить).

К сожалению, linq-to-objects выполняет только внутренние соединения из коробки, но широко используемый метод расширения, который я использую, упрощает работу по выполнению лево-внешних объединений на основе множеств.

Если вы выполняете левостороннее объединение между заболеваниями и diseasesToRemove, то вы выбираете элементы из левой коллекции объединения (healthGroup.DiseaseGroups[i].Diseases), которые не соответствуют ничему в правой коллекции ( diseasesToRemove), тогда вы удалите все, что соответствует.

Используя метод расширения .LeftOuterJoin (указан ниже), вы можете фильтровать массивы следующим образом:

for (int i = 0; i < healthGroup.DiseaseGroups.Length; i++)
{
    healthGroup.DiseaseGroups[i].Diseases =
        healthGroup.DiseaseGroups[i].Diseases
            .LeftOuterJoin(
                diseasesToRemove,
                d => d.Name,
                dr => dr,
                (d, dr) => ( d, dr ))
            .Where(x => x.dr == null)
            .Select(x => x.d)
            .ToArray();

}

Метод расширения внешнего левого соединения:

public static class JoinExtensions
{
    public static IEnumerable<TResult> LeftOuterJoin<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftSeq,
        IEnumerable<TRight> rightSeq,
        Func<TLeft, TKey> keySelectorLeft,
        Func<TRight, TKey> keySelectorRight,
        Func<TLeft, TRight, TResult> projectionSelector)
    {
        return leftSeq
            .GroupJoin(
                rightSeq,
                keySelectorLeft,
                keySelectorRight,
                (leftItem, rightItems) => new { leftItem, rightItems })
            .SelectMany(
                x => x.rightItems.DefaultIfEmpty(),
                (x, rightItem) => projectionSelector(x.leftItem, rightItem));
    }
}
0 голосов
/ 31 августа 2018

Вы можете использовать Where, чтобы упростить код до этого:

foreach (var diseaseGroup in healthGroup.DiseaseGroups)
{
    diseaseGroup.Diseases 
        = diseaseGroup.Diseases.Where(g => !diseasesToRemove.Contains(g.Name)).ToArray();
}

Конечно, как и в оригинальном коде, создается новый список. Более производительным вариантом (хотя и не LINQ) может быть Diseases a List<Disease>:

public class DiseaseGroup
{
    public string Name { get; set; }
    public List<Disease> Diseases;

    public DiseaseGroup(string name, Disease[] diseases)
    {
        Name = name;
        Diseases = new List<Disease>(diseases);
    }
}

и затем вы можете использовать подобный код без затрат на создание нового списка и нового массива:

for (int i = 0; i < healthGroup.DiseaseGroups.Length; i++)
{
    for (int j = healthGroup.DiseaseGroups[i].Diseases.Count - 1; j >= 0; --j)
    {
        if (diseasesToRemove.Contains(healthGroup.DiseaseGroups[i].Diseases[j].Name))
        {                 
            healthGroup.DiseaseGroups[i].Diseases.RemoveAt(j);
        }
    }
}

Я также изменил ваш цикл for, чтобы он работал в обратном направлении, чтобы решить проблему, возникшую у вас с --j.

И вместо использования string[] для diseasesToRemove для большого набора предметов, вероятно, было бы лучше использовать HashSet<string> для хранения болезней.


Изменить, чтобы включить запрашиваемый однострочный:

healthGroup.DiseaseGroups = healthGroup.DiseaseGroups.Select(g => { return g.Diseases = g.Diseases.Where(g => !diseasesToRemove.Contains(g.Name)).ToArray(); }).ToArray();

Это немного неправильно, однако выберите: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...