C# Linq copying values into model from different sources - c#

So I come with a List list and first make it to a dictionary for faster searches.
Dictionary<Guid, Int32> dictionaryFromList = list
.ToDictionary(x => x.Item1, y => y.Item2);
Now Im loading all the other info in another Dictionary from the cache:
Dictionary<Guid, Model> modelDictionary = _cache
.Where(x => dictionaryFromList.ContainsKey(x.Id))
.Select(y => new {y.Id, y })
.ToDictionary(t => t.Id, t=> t.y);
Now I need two different things:
1. I need to insert more data into some of the Models in the modelDictionary
2. I need to insert the Int32 from the dictionaryFromList into the modelDictionary
My approach for 1. was the following:
HashSet<Guid> toLoadIds = new HashSet<Guid>(modelDictionary
.Where(x => !x.Value.IsLoaded)
.Select(x => x.Key));
context.myTable
.Where(x => toLoadIds.Contains(x.Id))
.Select(x => new {x.value1, x.value2, x.value3, x.value4, x.value5, x.value6 }));
I selected the values now afaik but how should I get them into the right model in the modelDictionary?
For the 2. one I tried doing this:
dictionaryFromList.Select(y => modelDictionary[y.Key].myValue = y.Value);
But it seems like nothing is working properly :(
The previous solution for the 2. was the following when modelDictionary was stil a List
modelDictionary.ForEach(x => x.myValue = dictionaryFromList[x.AddressId]);

When you write:
dictionaryFromList.Select(y => modelDictionary[y.Key].myValue = y.Value);
the execution is deferred until someone runs through the returned select iterator. But you do not pick it up, so no-one does that.
You could do:
dictionaryFromList.Select(y => modelDictionary[y.Key].myValue = y.Value)
.LastOrDefault();
where the final call will force the iteration of the select iterator. But I find that ugly. Why not simply use foreach?
foreach (var y in dictionaryFromList) { modelDictionary[y.Key].myValue = y.Value; }

Related

Where LINQ on HashSet vs. List

I need to count the elements of a list/set having property with given value. The list is huge and I need the performance as good as possible. Should I use a list or a set (when having unique elements)? Is there any faster way?
int counter = myList.Where(x => x.A == myValue || x.B == myValue).Count()
This is already inside of AsParallel().ForAll() for another huge list. And no, I can't change that.
Edit
I already saw this question and it does definitely not solve my problem, I am interested on the differences in (P)LINQ queries.
If you are walking a collection in its entirety, walking the entire list is likely to yield better performance than walking the entire set because of the way list elements are allocated in memory (assuming that you are using List<T>, not a linked list).
If you are performing thousands of such queries on the same data in myList, you could get performance improvements by building three look-up tables - on x.A, x.B, and on the common value when x.A == x.B:
var countByA = myList
.GroupBy(x => x.A)
.ToDictionary(g => g.Key, g => g.Count());
var countByB = myList
.GroupBy(x => x.B)
.ToDictionary(g => g.Key, g => g.Count());
var countByAandB = myList
.Where(x => x.A == x.B)
.GroupBy(x => x.A)
.ToDictionary(g => g.Key, g => g.Count());
Now your query can be converted to three look-ups using the inclusion-exclusion principle:
countByA.TryGetValue(myValue, out var counterA);
countByB.TryGetValue(myValue, out var counterB);
countByAandB.TryGetValue(myValue, out var counterAandB);
int counter = counterA + counterB - counterAandB;

LINQ groupby search in contains

I have the following result:
var result = (from p1 in db.Table
select new ReportInform
{
DataValue = p1.DataValue,
SampleDate = p1.SampleDate
})
.Distinct()
.ToList();
// Next getting list of duplicate SampleDates
var duplicates = result.GroupBy(x => x.SampleDate)
.Where(g => g.Count() > 1)
.Select (x => x)
.ToList();
foreach (var r in result)
{
if (duplicates.Contains(r.SampleDate)) // get error here on incompatbility
{
r.SampleDate = r.SampleDate.Value.AddMilliseconds(index++);
}
}
Cannot convert from 'System.DateTime?' to 'System.Linq.IGrouping
That error is pretty clear but may not be at a first glance. As a programmer, you need to learn how to read, understand and make sense of compiler or runtime errors.
Anyhow it is complaining that it cannot convert DateTime? to System.Linq.IGrouping<System.DateTime, ReportInForm>. Why? Because this query returns an System.Linq.IGrouping<System.DateTime, ReportInForm>
var duplicates = result.GroupBy(x => x.SampleDate)
.Where(g => g.Count() > 1)
.Select (x => x)
.ToList();
The GroupBy method returns IGrouping<System.DateTime, ReportInForm> which has a Key and the Key is the thing you grouped by and a list of items in that group. You are grouping by SampleDate and checking if there are more than one items in that group and then selecting the group. Thus dulplicates has a list of IGrouping<System.DateTime, ReportInForm> and you are asking the runtime to check if it contains a DateTime? and it blows up at this line:
duplicates.Contains(r.SampleDate)
One way to fix this is: What you want to do is to select the key of that group. Thus do this:
.Select (x => x.Key)
If you are expecting duplicates to be of type List<DateTime?> then you meant to write this
.Select(x => x.Key)
instead of
.Select(x => x)

Use IEnumerable.Sum to subtract in LINQ

I've been trying to figure out if there was a way to use IEnumerable's Sum method to subtract two values instead of adding them up?
IEnumerable<T> list = first
.Concat(second)
.GroupBy(x => new { x.crita, x.critb})
.Select(y => new T
{
//other fields//
vara= y.Sum(z => z.vara),
varb= y.Sum(z => z.varb),
varc= y.Sum(z => z.varc),
vard= y.Sum(z => z.vard)
});
I'm using this method to get sums between the two IEnumerables I've joined, but I was hoping to be able to subtract them too.
Any help would be really appreciated.
Also, if someone could tell me how to get products and quotients, that'd be awesome!
Aggregate is the elder brother of Sum that can do subtract and other aggregations too. On the other hand if you want to do element-by-element subtraction, you can use general Select projections.
Reading your comment, one easy trick would be to create a projection of your second IEnumerable that negates vara, varb etc. and then use the same Sum function to compute the aggregates of groups. So basically you would just be doing a + (-b) instead of a - b. Something on the following lines:
IEnumerable<T> list = first
.Concat(second.Select(n => new T() { vara = -n.vara...} )
.GroupBy(x => new { x.crita, x.critb})
.Select(y => new T
{
//other fields//
vara= y.Sum(z => z.vara),
varb= y.Sum(z => z.varb),
varc= y.Sum(z => z.varc),
vard= y.Sum(z => z.vard)
});

c# `dictionary of dictionary` query

could someone tell me the correct way to query this:
dictionary of dictionary
Dictionary<int, Dictionary<Guid, AutoStopWatchAndLog>> _dicDictionaryThread
where what i am looking for is from any of the first level and then from any item in the second where the level is less than x
dics betlow is: Dictionary<int, Dictionary<Guid, AutoStopWatchAndLog>>
var mostlikey = dics.FirstOrDefault(x=>x.Value.Where(y=>y.Value.Level > x));
If you want to project to a new dictionary of dictionaries filtered to the desired items, you will need to project both levels of dictionaries, which would look something like:
var query = _dicDictionaryThread.Select(o => new {o.Key, Value = o.Value
.Where(y=>y.Value.Level > x)
.ToDictionary(y => y.Key, y => y.Value)})
.Where(o => o.Value.Any())
.ToDictionary(o => o.Key, o => o.Value);
If you can easily understand this and explain it to someone else, go for it, otherwise just use a traditional loop - you're not going to get any performance boost from Linq and it will likely take longer to decipher.

Rx how to group by a key a complex object and later do SelectMany without "stopping" the stream?

This is related to my other question here. James World presented a solution as follows:
// idStream is an IObservable<int> of the input stream of IDs
// alarmInterval is a Func<int, TimeSpan> that gets the interval given the ID
var idAlarmStream = idStream
.GroupByUntil(key => key, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
<edit 2:
Question: How do I start the timers immediately without waiting for the first events to arrive? That's the root problem in my question, I guess. For that end, I planned on sending off dummy objects with the IDs I know should be there. But as I write in following, I ended up with some other problems. Nevertheless, I'd think solving that too would be interesting.
Forwards with the other interesting parts then! Now, if I'd like to group a complex object like the following and group by the key as follows (won't compile)
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
then I get into trouble. I'm unable to modify the part about SelectMany, Concat and Observable.Return so that the query would work as before. For instance, if I make query as
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key.First())))
.Subscribe(i => Console.WriteLine(i.Id + "-" + i.IsTest);
Then two events are needed before an output can be observed in the Subscribe. It's the effect of the call to First, I gather. Furthermore, I woul like to use the complex object attributes in the call to alarmInterval too.
Can someone offer an explanation what's going on, perhaps even a solution? The problem in going with unmodified solution is that the grouping doesn't look Ids alone for the key value, but also the IsTest field.
<edit: As a note, the problem probably could be solved firsly by creating an explicit class or struct and then that implements a custom IEquatable and secondly then using James' code as-is so that grouping would happen by IDs alone. It feels like hack though.
Also, if you want to count the number of times you've seen an item before the alarm goes off you can do it like this, taking advantage of the counter overload in Select.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.Select((count, alarm) => new { count, alarm }).TakeLast(1));
Note, this will be 0 for the first (seed) item - which is probably what you want anyway.
You are creating an anonymous type in your Select. Lets call it A1. I will assume your idStream is an IObservable. Since this is the Key in the GroupByUntil you do not need to worry about key comparison - int equality is fine.
The GroupByUntil is an IObservable<IGroupedObservable<int, A1>>.
The SelectMany as written is trying to be an IObservable<A1>. You need to just Concat(Observable.Return(grp.Key)) here - but the the type of the Key and the type of the Group elements must match or the SelectMany won't work. So the key would have to be an A1 too. Anonymous types use structural equality and the return type would be stream of A1 - but you can't declare that as a public return type.
If you just want the Id, you should add a .Select(x => x.Id) after the Throttle:
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key)
.Select(x => x.Id))
.SelectMany(grp => grp.IgnoreElements().Concat(Observable.Return(grp.Key)));
If you want A1 instead - you'll need to create a concrete type that implements Equality.
EDIT
I've not tested it, but you could also flatten it more simply like this, I think this is easier! It is outputing A1 though, so you'll have to deal with that if you need to return the stream somewhere.
var idAlarmStream = idStream
.Select(i => new { Id = i, IsTest = true })
.GroupByUntil(key => key.Id, grp => grp.Throttle(alarmInterval(grp.Key))
.SelectMany(grp => grp.TakeLast(1));

Categories