I'm not sure if there is a more efficient way of doing what I'm doing using LINQ... I have two enumerations:
enumA(string): { "Andy", "Bill", "Charlie", "Doug" }
enumB(foo): { "Doug", "Edward", "George", "Bill" } (Note that enumB actually contains objects)
var dictionary = new Dictionary<string,
foreach (var a in enumA)
{
var b = enumB.SingleOrDefault(x => String.Equals(x.ToString(), a));
if (b != null)
dictionary[a] = b;
}
It just seems bad to me to enumerate over enumB over and over again and create a dictionary this way when I'm sure there is probably a more "correct" way to create a dictionary using LINQ.
You can do it efficiently using a
join
and an
ToDictionairy
call afterwards.
var listA = "abcd";
var listB = "cdef";
var tuples = from charFromA in listA
join charFromB in listB
on charFromA.ToString() equals charFromB.ToString() // instead of ToString(), do something complex
select new { A = charFromA, B = charFromB };
var dictionairy = tuples.ToDictionary(keySelector: t => t.A,elementSelector: t => t.B);
var query = from b in enumB.Where(x => x != null)
join a in enumA on b.ToString() equals a
select new { a, b };
var dictionary = query.ToDictionary(x => x.a, x => x.b);
Or with fluent API:
var dictionary = enumB.Where(b => b != null)
.Join(enumA,
b => b.ToString(),
a => a,
(b, a) => new { a, b })
.ToDictionary(x => x.a, x => x.b);
var dictionary = enumA
.Join(enumB, a => a, b => b.ToString(), (a, b) => new { A = a, B = b })
.ToDictionary(i => i.A, i => i.B);
This will throw an exception if there are any duplicate keys, so you can use:
var dictionary = enumA
.Join(enumB, a => a, b => b.ToString(), (a, b) => new { A = a, B = b })
.Aggregate(new Dictionary<string, Foo>(), (dict, i) => {
dict[i.A] = i.B;
return dict;
});
which will keep the last matching value.
var dict = enumA.Join(enumB, a => a, b => b, (a, b) => new {a, b})
.GroupBy(x => x.a)
.ToDictionary(x => x.Key, x => x.First());
GroupBy is to eliminate possible duplicates in enumB.
if enumB duplicates don't exist you can simplify it to
var dict = enumA.Join(enumB, a => a, b => b, (a, b) => new {a, b})
.ToDictionary(x => x.a, x => x.b);
Related
var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).AsQueryable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});
Where .AsQueryable() will cause an error
.AsQueryable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
No error will be reported when changing to AsEnumerable()
.ASEnumerable()
.OrderBy(a => a.OrderSN)
.GroupBy(a => new { a.Commodity, a.CommodityName })
But I don't want to send this code to the database for the time being, because it will be sent after paging query. I don't know how to deal with it?
//////////////I pasted my complete code and talked about my actual needs
Query the code and query the database page by page. For example, only one page and 10 rows of records are checked. Here is OK.
var AA= _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.a.a.Code,
CommodityName = a.a.a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
});
PageHealper<object> page = new PageHealper<object>();
page.Start(pageNum, pageSize);
page = await page.RestPage(AA);
At this time, I grouped and sorted again, and now I found that:
It is not to operate the paging query results, but to query all the AA databases.
Based on the previous pagination query, the number of rows and page numbers are obtained. Here, the number of rows is changed by grouping and merging.
That's why I want to put grouping and sorting together, and finally pagination.
var BB = AA.AsEnumerable().OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName, a.Specification, a.SpecificationName })
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
}); ;
page.Data = BB.ToList<object>();
return page;
Checkout this article https://weblogs.asp.net/zeeshanhirani/using-asqueryable-with-linq-to-objects-and-linq-to-sql about what AsQueryable does.
I think you dont really need AsQueryable there... LINQ to SQL does not like something about that query.
It does not like the String.Join(...) because it cannot translate it.
So one thing you can do is put .AsEnumerable() after the GroupBy() this will do everything up to in SQL and everything after in memory.
Ex:
var commodity = _appDbContext.ArchivesCCommodity.Where(lambda)
.GroupJoin(_appDbContext.ArchivesCCommoditySpecification, a => a.Code, b => b.Commodity, (a, b) => new { a, b })
.SelectMany(a => a.b.DefaultIfEmpty(), (a, b) => new { a.a, b })
.GroupJoin(_appDbContext.ArchivesCSpecificationDetail, a => a.a.a.b.SpecificationDetail, d => d.Code, (a, d) => new { a, d })
.SelectMany(a => a.d.DefaultIfEmpty(), (a, d) => new
{
Commodity = a.a.a.Code,
CommodityName = a.a.a.Name,
SpecificationDetailName = d.Name,
OrderSN = d.OrderSN
}).OrderBy(a => a.OrderSN).GroupBy(a => new { a.Commodity, a.CommodityName })
.AnEnumerable()
.Select(a => new
{
Commodity = a.Key.Commodity,
CommodityName = a.Key.CommodityName,
SpecificationDetailName = string.Join(" - ", a.Select(a => a.SpecificationDetailName)),
SpecificationDetailTotal = string.Join(" - ", a.Select(a => a.SpecificationDetailName)) == "" ? 0 : a.Count()
});
I got a class:
public class result
{
public int A;
public int B;
public int C;
}
I make a list of it:
public static List<result> results = new List<result>();
I then fill that list with random data
Somthing like 10,000,000 enteries, where a, b and c will have a value of 0 to 24.
I would like to show in my console what combo has been found and how many
in SQL it would be somthing like:
SELECT A, B, C, COUNT(*) AS total
FROM results
GROUP BY A, B, C
And i have tried so many things, i think i can write a book about it.
some of the stuff i tried:
var query1 = results.GroupBy(x => new { x.A, x.B, x.C }).Select(group => new { Value = group.Key, Count = group.Count() });
var query2 = from r in results group r by new { r.A, r.B, r.C } into rGroup select rGroup;
var query3 = results.GroupBy(x => new { x.A, x.B, x.C }) .Where(g => g.Count() > 1).ToDictionary(x => x.Key, y => y.Count());
var query4 = from r in results
group r by new { r.A, r.B, r.C } into rGroup
select new { key = rGroup.Key, cnt = rGroup.Count() };
But nothing seems to work.
I would like to get back a list with the a,b,c values and a count of how many have been found.
Yet im unable to get it working, i tried hours of googleing and tried everything, at this point i am completly lost.
For completeness sake a full example.
Same solutions as nlawalker though, producing a dict.
public class result
{
public int A;
public int B;
public int C;
public result(int a, int b, int c)
{
A = a;
B = b;
C = c;
}
}
static void Main(string[] args)
{
Random r = new Random(23);
var data = new List<result>();
for (int i = 0; i < 100; i++)
data.Add(new result(r.Next(1, 3), r.Next(1, 3), r.Next(1, 3)));
var dic = data
.GroupBy(k => new { k.A, k.B, k.C })
.ToDictionary(g => g.Key, g => g.Count());
foreach (var kvp in dic)
Console.WriteLine($"({kvp.Key.A},{kvp.Key.B},{kvp.Key.C}) : {kvp.Value}");
Console.ReadLine();
}
Output:
(2,2,2) : 13
(1,2,2) : 11
(2,1,1) : 9
(1,1,1) : 16
(1,2,1) : 14
(1,1,2) : 15
(2,1,2) : 7
(2,2,1) : 15
You got the GroupBy part right - you just need to select the groups into another object that has the A, B and C values of the group, along with the count of the group:
results.GroupBy(x => new { x.A, x.B, x.C })
.Select(g => new { g.Key.A, g.Key.B, g.Key.C, Count = g.Count()})
labelMap = new Dictionary<string, int>();
branchLineMap = new Dictionary<string, int>();
if one key of the first dictionary matches another key of the other dictionary then I need to make a new dictionary with the value of branchlineMap to become the key and the value of LabelMap to become the value. How do I do this while iterating over the whole dictionary?
Using Where and ToDictionary methods, you can do it like this:
var newDictionary = labelMap
.Where(x => branchLineMap.ContainsKey(x.Key))
.ToDictionary(x => branchLineMap[x.Key], x => x.Value);
You could join the two, using LINQ.
Query syntax:
var newDict = (from b in branchLineMap
join l in labelMap on b.Key equals l.Key
select new { b = b.Value, l = l.Value })
.ToDictionary(x => x.b, x => x.l);
Same thing, using method syntax:
var newDict = branchLineMap.Join(labelMap, b => b.Key, l => l.Key,
(b, l) => new { b = b.Value, l = l.Value })
.ToDictionary(x => x.b, x => x.l);
My problem is hard to solve. i need your help this problem. There is many to many relation in codefirst. But i can not resolve this. i would like to use Predicate func. But i can not resolve it? how to use "Method(Predicate func)"
public int Method<T>(Predicate<T> func)
{
var s1 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s2 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s3 = this.Uow.X.GetAll().Where(func)
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
return s1.ToString().Count() + s2.ToString().Count() + s3.ToString().Count();
}
Here is the way to do it. I also removed the ToString()s on the last line since you don't need an object to be a string to count it.
Call like this: int result = Method<TypeGoesHere>(p => p == aValue);
public int Method<T>(Expression<Func<T, Boolean>> Predicate)
{
var s1 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s2 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
var s3 = this.Uow.X.GetAll().Where(Predicate)
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.GroupBy(t => t, (k, g) => new
{
Tag = k,
Count = g.Count()
})
.OrderByDescending(g => g.Count);
return s1.Count() + s2.Count() + s3.Count();
}
I think you probably want an extension method for whatever type this is. It would look like this (if thisType was type of this). This does not need to be a template since you (but not me) know the type of X.GetAll(). I'm guessing List<int> which would make T int. I've also changed the code to be briefer but have the same functionality. (It might be your original code was doing the wrong thing but this does the same thing.)
Call like this:
thisType something = new something();
someType aValue = X; // don't know the type here.
// do stuff with something
int result = something.Method(p => p == aValue);
Code:
public static int Method(this thisType me, Expression<Func<someType, Boolean>> Predicate)
{
var allOfEm = me.Uow.X.GetAll().Where(Predicate);
var s1 = allOfEm
.SelectMany(a => a.OrganizationalUnits.Where(q => Identity.Y.Contains(q.Z)))
.Distinct();
var s2 = allOfEm
.SelectMany(a => a.Classes.Where(q => Identity.Y.Contains(q.K)))
.Distinct();
var s3 = allOfEm
.SelectMany(a => a.Courses.Where(q => Identity.Y.Contains(q.L)))
.Distinct();
return s1.Count() + s2.Count() + s3.Count();
}
I'd like to take an object like this:
SortedList<string, SortedList<int, SortedList<DateTime, double>>> Data
and, for a given 'int' value (key of first nested sorted list), restructure it like this:
SortedList<DateTime, SortedList<string, double>>
or, better yet, this:
SortedList<DateTime, double[]>
where each 'double[]' has as many elements as there are KeyValue pairs in the SortedList.
I'm guessing Linq is the way to go, but can't figure it out. Thanks for any suggestions.
digEmAll beat me to it, but here's the second case in query comprehension syntax:
int desiredInt = //whatever...
var query = from pair in Data
from pair2 in pair.Value
where pair2.Key == desiredInt
from pair3 in pair2.Value
group pair3.Value by pair3.Key into grp
select new { grp.Key, Value = grp.ToArray() };
var result = new SortedList<DateTime, double[]>(query.ToDictionary(a => a.Key, a => a.Value));
int givenKey = ...;
var variant1 = new SortedList<DateTime, SortedList<string, double>>(
Data.Select(pair => new { str = pair.Key, dateValues = pair.Value[givenKey] })
.Where(pair => pair.dateValues != null)
.SelectMany(pair => pair.dateValues.Select(dateValue => new { pair.str, date = dateValue.Key, value = dateValue.Value }))
.GroupBy(pair => pair.date)
.ToDictionary(group => group.Key, group => new SortedList<string, double>(group.ToDictionary(triple => triple.str, triple => triple.value)))
);
var variant2 = new SortedList<DateTime, double[]>(
Data.Select(pair => new { str = pair.Key, dateValues = pair.Value[givenKey] })
.Where(pair => pair.dateValues != null)
.SelectMany(pair => pair.dateValues.Select(dateValue => new { pair.str, date = dateValue.Key, value = dateValue.Value }))
.GroupBy(pair => pair.date)
.ToDictionary(group => group.Key, group => group.Select(triple => triple.value).ToArray())
);
Your transformation is not possible if you use the full resolution of DateTime unless your system regularizes the inserted DateTime value somehow. Even very rapid inserts can occur on a different tick. If you do regularize it then you can get your values as follows:
Dictionary<DateTime, double[]> results = (from d1 in Data
from d2 in d1.Value
where d2.Key == 1
from d3 in d2.Value
group d3 by d3.Key into d3Group
select new {Key = d3Group.Key, Value = (from d4 in d3Group
select d4.Value).ToArray()
}).ToDictionary(element => element.Key, element => element.Value);
SortedList<DateTime, double[]> newSortedList = new SortedList<DateTime, double[]>(results);
The second case is pretty neat:
var dateGroups = Data.SelectMany(x => x.Value)
.SelectMany(x => x.Value)
.GroupBy(x => x.Key)
.ToSortedList(g => g.Key,
g => g.Select(x => x.Value).ToArray());
The first case instead seems wrong, I suspect it should be:
SortedList<DateTime, SortedList<string, double[]>>
If so, the code to get that is the following:
var dict =
(from x in Data
from y in x.Value
from z in y.Value
select new { StrKey = x.Key, IntKey = y.Key, DateKey = z.Key, Value = z.Value })
.GroupBy(x => x.DateKey)
.ToSortedList(g1 => g1.Key,
g1 => g1.GroupBy(x => x.StrKey)
.ToSortedList(g2 => g2.Key,
g2 => g2.Select(y => y.Value).ToArray()));
Where ToSortedList is the following extension:
public static class Exts
{
public static SortedList<TK, TV> ToSortedList<TEl, TK, TV>(
this IEnumerable<TEl> elements,
Func<TEl, TK> keySelector,
Func<TEl, TV> valueSelector)
{
if(elements == null || keySelector == null || valueSelector == null)
throw new ArgumentNullException("An argument of ToSortedList is null");
var dict = new SortedList<TK, TV>();
foreach (var el in elements)
dict.Add(keySelector(el), valueSelector(el));
return dict;
}
}
Phoog's answer is good, but maybe you should consider ILookup instead of SortedList...
ILookup<DateTime, double> result =
(
from pair1 in Data
from pair2 in pair1.Value
where pair2.Key == givenInt
from pair3 in pair2.Value
from theDouble in pair3.Value
select new {theDateTime = pair3.Key, theDouble = theDouble }
)
.ToLookup(x => x.theDateTime, x => x.theDouble);