Linq: Converting flat structure to hierarchical - c#

What is the easiest and somewhat efficient way to convert a flat structure:
object[][] rawData = new object[][]
{
{ "A1", "B1", "C1" },
{ "A1", "B1", "C2" },
{ "A2", "B2", "C3" },
{ "A2", "B2", "C4" }
// .. more
};
into a hierarchical structure:
class X
{
public X ()
{
Cs = new List<string>();
}
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; private set; }
}
the result should look like this
// pseudo code which describes structure:
result =
{
new X() { A = "A1", B = "B1", Cs = { "C1", "C2" } },
new X() { A = "A2", B = "B2", Cs = { "C3", "C4" } }
}
Preferably using Linq extension methods. Target class X could be changed (eg. a public setter for the List), only if not possible / useful as it is now.

for this particular case:
.GroupBy( x => new { a = x[0], b = x[1] } )
.Select( x => new { A = x.Key.a, B = x.Key.b, C = x.Select( c => c[2] ) })

Something like this should work if the depth of your hierarchy is limited (as in your example where you have only three levels A, B and C). I simplified your X a little bit:
class X {
public string A { get; set; }
public string B { get; set; }
public List<string> Cs { get; set; }
}
Then you can use nested GroupBy as many times as you need (depending on the depth of the hierarchy). It would be also relatively easy to rewrite this into a recursive method (that would work for arbitrarily deep hierarchies):
// Group by 'A'
rawData.GroupBy(aels => aels[0]).Select(a =>
// Group by 'B'
a.GroupBy(bels => bels[1]).Select(b =>
// Generate result of type 'X' for the current grouping
new X { A = a.Key, B = b.Key,
// Take the third element
Cs = b.Select(c => c[2]).ToList() }));
This is more explicit than the other solutions here, but maybe it will be more readable as it is more straightforward encoding of the idea...

With X members being strings and Cs being a private set, and rawData being an array of arrays of objects, I would add a constructor to X public X(string a, string b, List<string> cs) and then perform this code
var query = from row in rawData
group row by new { A = row[0], B = row[1] } into rowgroup
select new X((string)rowgroup.Key.A, (string)rowgroup.Key.B, rowgroup.Select(r => (string)r[2]).ToList());
This is on the following raw data
object[][] rawData = new object[][]
{
new object[] { "A1", "B1", "C1" },
new object[] { "A1", "B1", "C2" },
new object[] { "A2", "B2", "C3" },
new object[] { "A2", "B2", "C4" }
// .. more
};

I wanted to see if I could write this without anonymous instances. It's not too bad:
IEnumerable<X> myList =
from raw0 in rawData
group raw0 by raw0[0] into g0
let g1s =
(
from raw1 in g0
group raw1 by raw1[1]
)
from g1 in g1s
select new X()
{
A = g0.Key,
B = g1.Key,
C = g1.Select(raw2 => raw2[2]).ToList()
}

Related

Name values list to dictionary to import in MongoDB

I am stuck in a problem where I need to convert a list into Dictionary > BsonDocument to import in MongoDB.
The list has the name of column and the values to insert in that column. But I am getting key already exits exception as soon the compiler enter in loop. Any suggestion?
void Main()
{
List<ListRow> myList = new List<ListRow>();
myList.Add(new ListRow { columnName = "column1", results = new List<string> { "a1", "b1", "c1" } });
myList.Add(new ListRow { columnName = "column2", results = new List<string> { "a2", "b2", "c2" } });
myList.Add(new ListRow { columnName = "column3", results = new List<string> { "a3", "b3", "c3" } });
List<BsonDocument> batch = new List<BsonDocument>();
foreach (var row in myList)
{
var dictionary = row.results.ToDictionary(x => row.columnName, x => x);
batch.Add(dictionary);
}
// Print batch
// Add to MongoDB
}
public class ListRow
{
public string columnName { get; set; }
public List<string> results { get; set; }
}
Expected result to pull
You are trying to make an entry in the iteration. ToDictionary aims for create the whole dictionary.
class Program
{
static void Main(string[] args)
{
List<ListRow> myList = new List<ListRow>
{
new ListRow {columnName = "column1", results = new List<string> {"a1", "b1", "c1"}},
new ListRow {columnName = "column2", results = new List<string> {"a2", "b2", "c2"}},
new ListRow {columnName = "column3", results = new List<string> {"a3", "b3", "c3"}}
};
BsonDocument batch = myList.ToDictionary(x => x.columnName, x => x.results).ToBsonDocument();
// Print batch
// Add to MongoDB
}
}
public class ListRow
{
public string columnName { get; set; }
public List<string> results { get; set; }
}
Ok. If I understand the problem correctly, you want to first transpose the matrix of
"a1", "b1", "c1"
"a2", "b2", "c2"
"a3", "b3", "c3"
than name the columns with column1, column2, column3, etc.. For the transpose problem I got this solution.
From there I worked out this solution, where I needed a root node, which is not clear how should look like from the question>
class Program
{
static void Main(string[] args)
{
List<ListRow> myList = new List<ListRow>
{
new ListRow {columnName = "column1", results = new List<string> {"a1", "b1", "c1"}},
new ListRow {columnName = "column2", results = new List<string> {"a2", "b2", "c2"}},
new ListRow {columnName = "column3", results = new List<string> {"a3", "b3", "c3"}}
};
var result = myList
.SelectMany(listRow => listRow.results.Select((item, index) => new {item, index}))
.GroupBy(i => i.index, i => i.item)
.Select(g => g.Select((x, index) => new {Col = myList[index].columnName, Value = x})
.ToDictionary(x => x.Col, x => x.Value))
.ToList();
BsonDocument batch = new Dictionary<string, List<Dictionary<string, string>>> {{"root", result}}
.ToBsonDocument();
// {{ "root" : [{ "column1" : "a1", "column2" : "a2", "column3" : "a3" }, { "column1" : "b1", "column2" : "b2", "column3" : "b3" }, { "column1" : "c1", "column2" : "c2", "column3" : "c3" }] }}
// or just
BsonArray batchPart = BsonArray.Create(result);
// {[{ "column1" : "a1", "column2" : "a2", "column3" : "a3" }, { "column1" : "b1", "column2" : "b2", "column3" : "b3" }, { "column1" : "c1", "column2" : "c2", "column3" : "c3" }]}
// Print batch
// Add to MongoDB
}
}
public class ListRow
{
public string columnName { get; set; }
public List<string> results { get; set; }
}
Your key : row.columName is repeated when the list is flatten to a dictionary. You can try to generate an unique key for each list item.
List<BsonDocument> batch = new List<BsonDocument>();
foreach (var row in myList)
{
var i = 0;
var dictionary = row.results.ToDictionary(x => $"{row.columnName}_{++i}", x => x);
batch.Add(dictionary);
}

Merge a list of object array c#

I have list of object array (List<object[]> a) that come from different sources (files, sql, webservices) and I need a way to join them.
For example, I have this two list:
List<object[]> listA = new List<object[]>();
object[] a = new object[] { 1, "A", 1200, "2016-12-31" };
listA.Add(a);
a = new object[] { 2, "B", 5200, "2016-12-31" };
listA.Add(a);
a = new object[] { 3, "C", 3500, "2016-12-31" };
listA.Add(a);
a = new object[] { 4, "D", 100, "2016-12-31" };
listA.Add(a);
List<object[]> listB = new List<object[]>();
object[] b = new object[] { 44, 859, "2016-12-08" };
listB.Add(b);
b = new object[] { 23, 851, "2016-12-07" };
listB.Add(b);
b = new object[] { 31, 785, "2016-12-09" };
listB.Add(b);
And the result will be this one:
List<object[]> listC = new List<object[]>();
object[] c = new object[] { 1, "A", 1200+859, 44, "2016-12-08" };
listC.Add(c);
c = new object[] { 2, "B", 5200+851, 23, "2016-12-07" };
listC.Add(c);
c = new object[] { 3, "C", 3500+785, 31, "2016-12-09" };
listC.Add(c);
c = new object[] { 4, "D", 100, null, null };
listC.Add(c);
The lists are bigger than in the example and I have to configure how to merge then but if I found a way to do this in linq is the half of the way.
Any ideas?
You can zip both sequences and concat with left items from listA:
listA.Zip(listB, (a, b) =>
new object[] { a[0], a[1], (int)a[2] + (int)b[1], b[0], b[2] })
.Concat(listA.Skip(listB.Count).Select(a =>
new object[] { a[0], a[1], a[2], null, null }))
You can also use group join or select items from second list by index of item in first list.
I also suggest you to use custom classes instead of arrays of objects to make your code more readable, get nice type checking, descriptive properties, and intellisense. E.g.
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
And
public class Bar
{
public int Id { get; set; }
public int Value { get; set; }
public DateTime Date { get; set; }
}
Of course you should use more appropriate names here. Now your code will look like
List<Foo> foos = new List<Foo> {
new Foo { Id=1, Name="A", Value=1200, Date=new DateTime(2016,12,31) },
new Foo { Id=2, Name="B", Value=5200, Date=new DateTime(2016,12,31) },
new Foo { Id=3, Name="C", Value=3500, Date=new DateTime(2016,12,31) },
new Foo { Id=4, Name="D", Value=100, Date=new DateTime(2016,12,31) },
};
List<Bar> bars = new List<Bar> {
new Bar { Id=44, Value=859, Date=new DateTime(2016,12,8) },
new Bar { Id=23, Value=851, Date=new DateTime(2016,12,7) },
new Bar { Id=31, Value=785, Date=new DateTime(2016,12,9) }
};
You can also create custom type to hold combined data.
public class Result
{
public int FooId {get; set; }
public string Name {get; set; }
public int Value {get;set;}
public int? BarId {get;set;}
public DateTime? Date {get; set;}
}
And getting results will look like
var results = foos.Zip(bars, (f, b) => new Result{
FooId = f.Id, Name = f.Name, Value = f.Value + b.Value, BarId = b.Id, Date = b.Date
}).Concat(foos.Skip(bars.Count).Select(f => new Result {
FooId = f.Id, Name = f.Name, Value = f.Value
}));
Try Working Code

Search in IQueryable<object>

Is it possible to search in an IQueryable
public static IQueryable<object> SearchAllFields(IQueryable<object> query, string term)
{
query = query.Where(q => q.Property1 == term);
query = query.Where(q => q.Property2 == term);
query = query.Where(q => q.Property3 == term);
return query;
}
Lets say I want to compare the search term to each of the properties that the object might have, without knowing before what properties the object might have.
Edit:
I am attempting to create a generic DataTable solution to display any tabular information that might be necessary (orders, books, customers, etc.)
To test the concept I'm using the ApplicationLogs table in my database. The DataTable looks as follows:
Lets say when typing in that search box I want to search for that value in all the columns that might be displayed. The query that populates the table:
IQueryable<object> query = (from log in db.ApplicationLog
orderby log.LogId descending
select new
{
LogId = log.LogId,
LogDate = log.LogDate.Value,
LogLevel = log.LogLevelId == 1 ? "Information" : log.LogLevelId == 2 ? "Warning" : "Error",
LogSource = log.LogSourceId == 1 ? "Www" : log.LogSourceId == 2 ? "Intranet" : "EmailNotification",
LogText = log.LogText
});
As you can see, this query will determine what the properties of the object will be. The example is taken from the logs table, but it can come from any number of tables. Then, if I want to call the generic search method from the original post:
query = DataTableHelper.SearchAllFields(query, pageRequest.Search);
You can use reflection to search through all the properties of the element, but if you want to return all rows where any property matches then you need to use predicatebuilder to build the applied query instead Where().
This example code will return both instances of Foo where A,B and C are "a". And the instances of Bar where E, F and G are "a". Also added example of anonymous type.
class Program
{
private class Foo
{
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
private class Bar
{
public string E { get; set; }
public string F { get; set; }
public string G { get; set; }
}
static void Main(string[] args)
{
var list = new List<Foo>
{
new Foo { A = "a", B = "a", C = "a" },
new Foo { A = "a2", B = "b2", C = "c2" },
new Foo { A = "a3", B = "b3", C = "c3" },
};
var list2 = new List<Bar>
{
new Bar { E = "a", F = "a", G = "a" },
new Bar { E = "a2", F = "b2", G = "c2" },
new Bar { E = "a3", F = "b3", G = "c3" },
};
var q1 = Filter(list.AsQueryable(), "a");
var q2 = Filter(list2.AsQueryable(), "a");
foreach (var x in q1)
{
Console.WriteLine(x);
}
foreach (var x in q2)
{
Console.WriteLine(x);
}
var queryable = list.Select(p => new
{
X = p.A,
Y = p.B,
Z = p.C
}).AsQueryable();
var q3 = Filter(queryable, "a");
foreach (var x in q3)
{
Console.WriteLine(x);
}
Console.ReadKey();
}
private static IQueryable<object> Filter(IQueryable<object> list, string value)
{
foreach (var prop in list.ElementType.GetProperties())
{
var prop1 = prop;
list = list.Where(l => Equals(prop1.GetValue(l, null), value));
}
return list;
}
}

LinQ nested lists and nested selects

Consider these two tables:
ClassID Name
1 C1
2 C2
ClassID List<CourseSession>
1 [Object that has value "A"], [Object that has value "B"]
2 [Object that has value "B"], [Object that has value "C"]
When I join these two tables in Linq, I get:
ID Name List
1 C1 [A, B]
2 C2 [A, B]
Wheras I need to expand them:
ID Name List
1 C1 A
1 C1 B
2 C2 A
2 C2 B
Linq code:
var classes = from row in t.AsEnumerable()
select new
{
ClassID = row.Field<Guid>("ClassID"),
ClassName = row.Field<string>("Name"),
};
var classCourses = from row in classes.AsEnumerable()
select new
{
ID = row.ID,
CourseSessionList = GetAllCoursesByID(row.ID).AsEnumerable()
};
//Attempt to join
var expandedClassCourse = from classRow in classes
join ccRow in classCourses
on classRow.ID equals ccRow.ID
into filteredExpandedClasses
select filteredExpandedClasses;
I'm not sure how to achieve this. Any ideas?
Something like (not sure what your model looks like):
context.CouseSessions.Where(cs => /* condition goes here */)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
or
context.Classes.Where(c => /* condition goes here */)
.SelectMany(c => c.Courses)
.Select(cs =>
new
{
Name = cs.Name,
Class = cs.Class.Name
});
I created two models based on assumption. I hope this helps.
class Info
{
public int Id { get; set; }
public string Name { get; set; }
public List<string> List { get; set; }
}
class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public string s { get; set; }
}
static void Main(string[] args)
{
var infos = new List<Info> { new Info { Id = 1, Name = "c1", List = new List<string> { "A", "B" } }, new Info { Id = 2, Name = "c2", List = new List<string> { "A", "B" } } };
var myClasses = new List<MyClass>();
foreach (var info in infos)
{
myClasses.AddRange(info.List.Select(a => new MyClass { Id = info.Id, Name = info.Name, s = a }));
}
}
(from c in classList
join s in sessionList on c.ClassID equals s.ClassID
select new
{
ID = c.ClassID,
Name = c.Name,
SessionList = s.SessionList
})
.SelectMany(e => e.SessionList.Select(s => new
{
ID = e.ClassID,
Name = e.Name,
Session = s
}))

Selecting several class properties in one query

The following code takes a collection of class C and creates a collection consisting of the values of two properties A and B. A and B are put inside the same collection:
class A
{
public int x { get; set; }
}
class B
{
public int x { get; set; }
}
class C
{
public A A { get; set; }
public B B { get; set; }
}
..........
var items = new List<C>()
{
new C()
{
A = new A() {x = 1},
B = new B() {x = 2}
},
new C()
{
A = new A() {x = 3},
B = new B() {x = 4}
},
};
var qA = from item in items
select (object)item.A;
var qB = from item in items
select (object)item.B;
var qAll = qA.Concat(qB);
Is it possible to do this with one query?
If you really want to flatten properties like that, you can feed arrays to SelectMany():
var qAll = items.SelectMany(item => new object[] { item.A, item.B });
You could use a ForEach:
var qAll = new List<object>();
items.ForEach(item => { qAll.Add(item.A); qAll.Add(item.B) });

Categories

Resources