1

Is there an elegant way to flatten multiple rows in C# (using Linq or not)?

E.g. suppose

var rows = new List<Row> {
   new Row() { Col1 = 1, Col2 = null ,Col3 = null},
   new Row() { Col1 = null, Col2 = 2 ,Col3 = null},
   new Row() { Col1 = null, Col2 = null ,Col3 = 3},
   new Row() { Col1 = 2, Col2 = null ,Col3 = null},
};

I want to call something like

var res = rows.flatten();

which would yield

var res = new List<Row> {
   new Row() { Col1 = 1, Col2 = 2 ,Col3 = 3},
   new Row() { Col1 = 2, Col2 = null ,Col3 = null}
   };

Any ideas?

  • 1
    its not really clear what you want to achieve. the second version is not flattened as such, just has the data shifted about to remove nulls? – Tim Rutter Oct 26 '19 at 18:04
  • 1
    Can you give your definition of ["flatten"](https://softwareengineering.stackexchange.com/questions/70743/what-does-flatten-mean), please? Typically, [flattening a list](https://stackoverflow.com/questions/958949/difference-between-select-and-selectmany) happens to lists of lists. – ChiefTwoPencils Oct 26 '19 at 18:46
  • Hi guys i might be using the wrong word... but the result is an accurate description of what i am trying to achieve. – Joseph Bartholomew Oct 26 '19 at 22:27
  • so you want to remove all nulls by shifting the data up vertically – Tim Rutter Oct 27 '19 at 06:55
  • What is the Row class? And does it really have members Col1, Col 2 etc or does it in fact have a list of Column objects? – Tim Rutter Oct 27 '19 at 06:57
  • Hi Tim, the Row class is just a simplified object i made up, but its objects col1, col2 are properties. the root of the problem is am capturing data from a text file where different lines make up one Invoice. so i was wondering if there is a easy way to collapse the lines down to one object easily. – Joseph Bartholomew Oct 28 '19 at 13:33
  • 1
    What should your flatten rule be if the Col2 in the 1st row is not null but 2? Do you still what the same result as in your question or same # of rows but Col2 = 4, or should the result have 3 rows? – Carlo Bos Oct 28 '19 at 19:43

1 Answers1

1

This is maybe not the prettiest solution, but it works and gives you the expected result.

It constructs a new list starting with the 1st element of the original list. Then considering the next item, checks if it can collapse this with the first, if so then collapse. This repeats until its no longer able to collapse. In that case add a new entry to the result list and the process starts over until its exhausted the original list of items to consider.

Since I'm using reflection you can use any class with arbitrarily named/typed properties.

void Main()
{
    var rows = new List<Row> {
        new Row() { Col1 = 1, Col2 = null, Col3 = null},
        new Row() { Col1 = null, Col2 = 2, Col3 = null},
        new Row() { Col1 = null, Col2 = null, Col3 = 3},
        new Row() { Col1 = 2, Col2 = null, Col3 = null},
    };
    var flattened = rows.Flatten();
}

public static class MyExtensions
{
    public static List<T> Flatten<T>(this List<T> list)
    {
        if (list == null || !list.Any() || list.Count() == 1 || list.First().GetType().GetProperties().Count() == 0)
        {
            return list;
        }

        var index = 0;
        var runner = 0;
        var result = new List<T>();
        do
        {
            result.Add(list[runner]);

            for (int r = runner + 1; r < list.Count; r++)
            {
                if (CanCollapse(result[index], list[r]))
                {
                    Collapse(result[index], list[r]);
                    runner++;
                }
                else
                {
                    break;
                }
            }
            runner++;
            index++;
        } while (runner < list.Count());

        return result;
    }

    private static bool CanCollapse<T>(T target, T next)
    {
        foreach (var p in target.GetType().GetProperties())
        {
            var targetValue = p.GetValue(target, null);
            var nextValue = p.GetValue(next, null);

            if (targetValue != null && nextValue != null && !targetValue.Equals(nextValue))
            {
                return false;
            }
        }
        return true;        
    }

    private static void Collapse<T>(T target, T next)
    {
        foreach (var p in target.GetType().GetProperties())
        {
            var targetValue = p.GetValue(target, null);
            var nextValue = p.GetValue(next, null);

            if (nextValue != null && targetValue == null)
            {
                p.SetValue(target, nextValue);  
            }
        }
    }
}

public class Row
{
    public int? Col1 { get; set; }
    public int? Col2 { get; set; }
    public int? Col3 { get; set; }
}
Carlo Bos
  • 3,105
  • 2
  • 16
  • 29
  • Hi Carlo Bos, sorry for late response, i am caught up with some other work... i will try it in a few days and update this post... thanks for the effort in advance... i really appreciate it. would have message you but i didn't see the option – Joseph Bartholomew Oct 31 '19 at 05:10
  • @JosephBartholomew if the above answer works, please up-vote and/or mark it as the solution – Carlo Bos Nov 29 '19 at 18:34