0

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?

8
  • 1
    "But this obviously looks weird as well as the data architecture overall." Huuum? Looks pretty clear to me. Don´t expect linq to be any neater or whatever. Commented Jan 25, 2021 at 15:21
  • 3
    I'd change this to do the splitting of the Model, Color, and Size strings once before the loop. And then split the individual size values once. Commented Jan 25, 2021 at 15:24
  • 1
    Also a purely Linq solution for this would likely look even "weirder". Commented Jan 25, 2021 at 15:28
  • Also the if statement should be if (Size_unparsed.Contains(Delimeter_inner)) to check for the ^ delimiter and not the | delimiter. And you'd want Size = size instead of Size = Size_unparsed Commented Jan 25, 2021 at 15:33
  • Is this arcane source solid enought for the number of elment in Model, Color, and Size to match ? And perhaps a Zip can tie the 3 first split into a nicer object Commented Jan 25, 2021 at 15:33

2 Answers 2

1

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,
            };                    
        }
    );
Sign up to request clarification or add additional context in comments.

6 Comments

this seems a lot more complicated than just using a loop.
@Hogan, yes I will use the loop with minor change over that. Because it will be easier to debug and change. But it's more for how to LinQ it.
You can obviously inline size, quantity and nett
@Charlieface, I copy pasted from author. and did bother but yes. But As i didn't wrote the Size_unparsed.Contains(Delimeter_inner) else block. I let it there cause it's either to get the way to code in this block
Won't this cause an error in the second example where secondSplit[1] doesn't exist and will throw an exception?
|
1

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();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.