简体   繁体   中英

Nested If to LINQ and Lambda

Net 6.0

Our company uses a metric layout that takes records that look like this

AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B

and transforms it into this

AAA|BLANK|BLANK|BLANK

AAA|0000A|BLANK|BLANK
AAA|0000B|BLANK|BLANK

AAA|0000A|000AA|BLANK
AAA|0000A|000AB|BLANK
AAA|0000B|000BA|BLANK
AAA|0000B|000BB|BLANK

AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AA|0003A
AAA|0000A|000AA|0004A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B

I have a custom object

public class HierarchyStructure
{
    public string Employer { get; set; }
    public string Level1 { get; set; }
    public string Level2 { get; set; }
    public string Level3 { get; set; }
    public string Level4 { get; set; }
    public bool isRxlvl3 { get; set; }
    public bool isExpired { get; set; }
    public string lvl4SubType { get; set; }

    public HierarchyStructure(string employer,
                              string? level1 = null,
                              string? level2 = null,
                              string? level3 = null,
                              string? level4 = null,
                              bool isRxlvl3 = false,
                              bool isExpired = false,
                              string? lvl4SubType = null)
    {
        this.Employer = employer;
        this.Level1 = string.IsNullOrEmpty(level1) ? string.Empty : level1;
        this.Level2 = string.IsNullOrEmpty(level2) ? string.Empty : level2;
        this.Level3 = string.IsNullOrEmpty(level3) ? string.Empty : level3;
        this.Level4 = string.IsNullOrEmpty(level4) ? string.Empty : level4;
        this.isRxlvl3 = isRxlvl3;
        this.isExpired = isExpired;
        this.lvl4SubType = string.IsNullOrEmpty(lvl4SubType) ? string.Empty : lvl4SubType;
    }

}

I am trying to populate from a static level into a formatted output list

    private List<HierarchyStructure> AllLevels => _allLevels;
    private List<HierarchyStructure> LocationLevels => _LocationLevels;

I developed methods for each level call, but I can't figure out how to merge them into one method that either calls itself recursively or dynamically selects the right values. I do not think it is possible to combine these due to the different comparisons and return values.

    public bool GetDistinctEmployers()
    {

        var selectedLevels = (from levels in AllLevels
                              select new HierarchyStructure(levels.Employer));

        foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);

        return true;
    }

    public bool GetDistinctLevel1(string emp)
    {

        var selectedLevels = (from levels in AllLevels
                              where levels.Employer == emp
                              select new HierarchyStructure(levels.Employer,levels.Level1));

        foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);

        return true;

    }

    public bool GetDistinctLevel2(string lvl)
    {

        var selectedLevels = (from levels in AllLevels
                              where levels.Level1 == lvl
                              select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2));

        foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);

        return true;

    }

    public bool GetDistinctLevel3(string lvl)
    {

        var selectedLevels = (from levels in AllLevels
                              where levels.Level2 == lvl
                              select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3));

        foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);

        return true;

    }

    public bool GetDistinctLevel4(string lvl)
    {

        var selectedLevels = (from levels in AllLevels
                              where levels.Level3 == lvl
                              select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4));

        foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);

        return true;

    }

When I try and combine all these together, it ends up being soooo nested and I feel like there must be a better way to accomplish this. I am fairly certain this level of nesting will error my quality gate on my pipeline.

    public void ProcessLevels()
    {
        if(GetDistinctEmployers())
        {
            foreach(string employer in AllLevels.Select(x => x.Employer))
            {
                if(GetDistinctLevel1(employer))
                {
                    foreach(string level1 in AllLevels.Select(x => x.Level1))
                    {
                        if(GetDistinctLevel2(level1))
                        {
                            foreach(string level2 in AllLevels.Select(x => x.Level2))
                            {
                                if(GetDistinctLevel3(level2))
                                {
                                    foreach(string level3 in AllLevels.Select(x => x.Level3))
                                    {
                                        if(GetDistinctLevel4(level3))
                                        {
                                            SaveData();
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

        }
    }

I am also getting another inline quality flag that my expressions in this block can be shortened using LINQ. I dont understand how my one line can be simplified.

...AllLevels.Select(x => x.Employer)
...AllLevels.Select(x => x.Level1)
... etc

A view of the data output I am trying to get.

所需数据输出的视图

Perhaps you need something that first expands each source row into all of the possible levels, and runs those results through a.Distinct() and an.OrderBy().

Given:

AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B

The expanded data would be:

AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0002A
...
AAA
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B

Then a.Distinct() and.OrderBy()

AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
...
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
...

You would need a generating function to expand each source row into an enumerated list than can be fed into a.SelectMany(), and may also need to define custom comparators to be used by the.Distinct() and a.OrderBy() functions.

Something like:

private static IEnumerable<HierarchyStructure> HierarchyGenerator(this HierarchyStructure level)
{
    yield return new HierarchyStructure(levels.Employer);
    yield return new HierarchyStructure(levels.Employer,levels.Level1);
    yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2);
    yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3);
    yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4);
}
...
LocationLevels = AllLevels
    .SelectMany(levels => levels.HierarchyGenerator())
    .Distinct(...custom HierarchyStructure IEqualityComparer...)
    .OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
    .ToList();

There are other ways, such as generating distinct values are each level, unioning them all together, and then feeding them to the sort.

Something like:

var level0 = AllLevels
    .Select(levels => newHierarchyStructure(levels.Employer))
    .Distinct(IEqualityComparer...);
...
var level4 = AllLevels
    .Select(levels => new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4))
    .Distinct(IEqualityComparer...);

LocationLevels = level0
    .Union(level1)
    .Union(level2)
    .Union(level3)
    .Union(level4)
    .OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
    .ToList();

There may be some performance tradeoffs in selecting where the distinct operations are applied. Using intermediate anonymous objects might also help, such as:

var level1 = AllLevels
    .Select(levels => new {levels.Employer, levels.Level1})
    .Distinct()
    .Select(levels => new HierarchyStructure(levels.Employer,levels.Level1));

Here the .Distinct() uses the default comparator for the anonymous object, which compares each contained value.

The custom comparers can also be incorporated into the HierarchyStructure class by implementing the IComparable interface. The HierarchyGenerator() function could also be made a member function within the HierarchyStructure class.

(My apologies in advance for any syntax errors. This is untested. I'll update the above given any comments.)

This seems to be able to be solved quite easily with LINQ.

I'll start with this input :

string[] input = new[]
{
    "AAA|0000A|000AA|0001A",
    "AAA|0000A|000AA|0002A",
    "AAA|0000A|000AB|0001B",
    "AAA|0000A|000AB|0002B",
    "AAA|0000B|000BA|0001A",
    "AAA|0000B|000BB|0001B",
};

Now I can transform this into the output like this:

string[][] parts = input.Select(x => x.Split('|')).ToArray();

int max = parts.Max(p => p.Length);

string[][] expanded =
    Enumerable
        .Range(0, max)
        .SelectMany(i => parts.Select(p => p.Take(i + 1).ToArray()).ToArray())
        .DistinctBy(xs => String.Join("|", xs))
        .OrderBy(xs => String.Join("|", xs))
        .ToArray();

string[] output =
    expanded
        .Select(e => String.Join("|", e))
        .ToArray();

That gives me:

AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B
AAA|0000B|000BA
AAA|0000B|000BA|0001A
AAA|0000B|000BB
AAA|0000B|000BB|0001B

If you just want to stop to fill a grid, just use expanded .

With this approach it doesn't matter how many levels deep the source data is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM