kevintw
kevintw

Reputation: 58

C# LINQ split complex joined string data to a List of objects

I've got two classes like this:

class SomeObject_source
{
    public string Model;
    public string Color;
    public string Size;
}

class SomeObject_target
{
    public string Model;
    public string Color;
    public string Size;
    public double Quantity;
    public double Nett;
}

Then we are getting an instance of the SomeObject_source with the following data structure:

    SomeObject_source someObject_source = new SomeObject_source
    {
        Model = "Model_1|Model_2|Model_3|Model_4|Model_5",
        Color = "Black|Black|White|Blue|Red",
        Size = "S^1^0.5|M^2^1.0|L^1^0.6|S^3^1.5|S^1^0.6"
    };

The Size string has the following pattern: size^quantity^nett. But sometimes there is an object with a simple data like:

    SomeObject_source someObject_source = new SomeObject_source
    {
        Model = "Model_1",
        Color = "Black",
        Size = "S"
    };

I'm trying to figure out how do an elegant single LINQ query to split the following data into a List of SomeObject_target, so that the result would be equal to:

    List<SomeObject_target> someObject_Target_List = new List<SomeObject_target>
    {
        new SomeObject_target
        {
            Model = "Model_1",
            Color = "Black",
            Size = "S",
            Quantity = 1,
            Nett = 0.5
        },
        new SomeObject_target
        {
            Model = "Model_2",
            Color = "Black",
            Size = "M",
            Quantity = 2,
            Nett = 1
        },
        new SomeObject_target
        {
            Model = "Model_3",
            Color = "White",
            Size = "L",
            Quantity = 1,
            Nett = 0.6
        },
        new SomeObject_target
        {
            Model = "Model_4",
            Color = "Blue",
            Size = "S",
            Quantity = 3,
            Nett = 1.5
        },
        new SomeObject_target
        {
            Model = "Model_5",
            Color = "Red",
            Size = "S",
            Quantity = 1,
            Nett = 0.6
        },
    };

For now i'm doing as follow:

    char Delimeter_main = '|';
    char Delimeter_inner = '^';
    List<SomeObject_target> someObject_Target_List_ = new List<SomeObject_target>();
    for (int ind = 0; ind < someObject_source.Model.Split(Delimeter_main).Count(); ind++)
    {
        string Model = someObject_source.Model.Split(Delimeter_main)[ind];
        string Color = someObject_source.Color.Split(Delimeter_main)[ind];
        string Size_unparsed = someObject_source.Size.Split(Delimeter_main)[ind];
        if (Size_unparsed.Contains(Delimeter_inner))
        {
            string size = Size_unparsed.Split(Delimeter_inner)[0];
            double quantity = double.TryParse(Size_unparsed.Split(Delimeter_inner)[1], out double _quantity) ? _quantity : 1;
            double nett = double.TryParse(Size_unparsed.Split(Delimeter_inner)[2], out double _nett) ? _nett : 1;
            someObject_Target_List_.Add(new SomeObject_target
            {
                Model = Model,
                Color = Color,
                Size = size,
                Quantity = quantity,
                Nett = nett
            });
        }
        else
        {
            someObject_Target_List_.Add(new SomeObject_target
            {
                Model = Model,
                Color = Color,
                Size = Size_unparsed,
                Quantity = 1,
                Nett = 1
            });
        }
    }

But this obviously looks weird as well as the data architecture overall. Is there any LINQ query to accomplish that in single elegant query?

Upvotes: 0

Views: 242

Answers (2)

Drag and Drop
Drag and Drop

Reputation: 2734

Normal enumerable.Zip

var result = someObject_source.Model.Split(Delimeter_main)
    .Zip(someObject_source.Color.Split(Delimeter_main), (x, y) => new { x, y })
    .Zip(someObject_source.Size.Split(Delimeter_main), (zip1, z) =>
    {
        var secondSplit = z.Split(Delimeter_inner);
        return new SomeObject_target
        {
            Model = zip1.x,
            Color = zip1.y,
            Size = secondSplit[0],
            Quantity = double.TryParse(secondSplit[1], out double _quantity) ? _quantity : 1,
            Nett = double.TryParse(secondSplit[2], out double _nett) ? _nett : 1,
        };
    });

Extention Method Zip on 3 collection

A little more readable, without the annonymous object wraper for the first Zip result.

var results = someObject_source.Model.Split(Delimeter_main)
    .ZipThree(
        someObject_source.Color.Split(Delimeter_main),
        someObject_source.Size.Split(Delimeter_main), 
        (x, y, z) => {
            var secondSplit = z.Split(Delimeter_inner);
            return new SomeObject_target
            {
                Model = x,
                Color = y,
                Size = secondSplit[0],
                Quantity = double.TryParse(secondSplit[1], out double _quantity) ? _quantity : 1,
                Nett = double.TryParse(secondSplit[2], out double _nett) ? _nett : 1,
            };                    
        }
    );

Upvotes: 1

NetMage
NetMage

Reputation: 26917

Using this Pivot extension method that pivots IEnumerable<IEnumerable<T>> (note: this isn't particularly efficient, but a better/faster method is quite a bit longer):

public static class IEnumerableExt {
    // Pivot IEnumerable<IEnumerable<T>> by grouping matching positions of each sub-IEnumerable<T>
    // itemGroups - source data
    public static IEnumerable<IEnumerable<T>> Pivot<T>(this IEnumerable<IEnumerable<T>> itemGroups) =>
        itemGroups.Select(g => g.Select((item, i) => (item, i)))
                  .SelectMany(g => g)
                  .GroupBy(ii => ii.i, si => si.item);
}

You can process someObject_source as follows:

char Delimeter_main = '|';
char Delimeter_inner = '^';

var someObject_Target_List =
    new[] {
        someObject_source.Model.Split(Delimeter_main),
        someObject_source.Color.Split(Delimeter_main),
        someObject_source.Size.Split(Delimeter_main)
    } // Create IEnumerable<string>[] (== IEnumerable<IEnumerable<string>>)
    .Pivot()
    .Select(t => t.ToList()) // project to IEnumerable<List<string>> to access members
    .Select(t => (model: t[0], color: t[1], rest: t[2].Split(Delimeter_inner))) // project to IEnumerable<ValueTuple> to access members
    .Select(t => new SomeObject_target {
        Model = t.model,
        Color = t.color,
        Size = t.rest[0],
        Quantity = t.rest.Length > 1 ? double.Parse(t.rest[1]) : default(double),
        Nett = t.rest.Length > 2 ? double.Parse(t.rest[2]) : default(double),
    })
    .ToList();

Upvotes: 1

Related Questions