I am looking for efficient way to combine multiple hashsets (based on object key) while preserving non key values (such as Version below).
class MyObject {
public string Key {get; set;}
public long Version {get; set;}
override GetHashCode() { *key* }
override Equals(...) { *key* }
}
in the and, i need to combine hash sets into a master list, but also all Versions.
I can make the union of 3 sets in this way:
for (var o in List1.Union(List2).Union(List3))
Console.WriteLine("{0} : {1}", o.Key, o.Version)
this only shows the versions from one of the lists (List1, or whatever list contains the item).
I need to compile these into a result with all versions..
Something i wish i could do like this:
for (var o in List1.Union(List2).Union(List3).Select((a,b,c) => new DiffObj(){Key=a.Key,VersionA=a.Version,VersionB=b.Version,VersionC=c.Version}))
Console.WriteLine("{0} : {1},{2},{3}", o.Key, o.VrsionA, o.VersionB, VersionC);
is that possible with hashsets?
update
it is important to keep track which list had which version (in the final result).
It sounds like you want a grouping:
var grouped = list1.Select(x => new { List=1, Item=x })
.Concat(list2.Select(x => new { List=2, Item=x }))
.Concat(list3.Select(x => new { List=3, Item=x }))
.GroupBy(pair => pair.Item);
Then you can just iterate over each group, which will contain equal values according to Equals and GetHashCode, but which can still be distinct. The List value will indicate which list each item came from.
Related
public class Country
{
public List<State> States { get; set; } = new List<State>();
}
public class State
{
public List<City> Cities { get; set; } = new List<City>();
}
public class City
{
public decimal IdSeaLevel { get; set; }
}
IdSeaLevel Has these possible expected values: 0, 1, 2.
Then is needed to check al values inserted by user to prevent some different value.
Suppose that the user send us an country (object of Country class) with its list filled (and nested too).
How to get all Distinct IdSeaLevel inserted value by the user?
I was thinking like:
List<decimal> AllowedIdSeaLevellist = new List<decimal>(new decimal[] { 0, 1, 2 });
Now, I get a Distict inserted Values
HashSet<decimal> SentIdSeaLevelSet = country.States
.Select(s => s.Cities.IdSeaLevel).ToHashSet();
Check
bool badRequest= SentIdSeaLevelSet
.Where(s => AllowedIdSeaLevellist.All(a => a != s)).Any();
.SelectMany will map List of lists into single list (flattened)
var allSeaLevels = country.States
.SelectMany(s => s.Cities)
.Select(city => city.SeaLevelId)
.ToHashSet();
To get "invalid" sea levels you can alternatively to gather them while looping through sealevels.
var validSeaLevels = new[] { 0, 1, 2 }.ToHashSet();
var invalidSeaLevels = country.States
.SelectMany(s => s.Cities)
.Select(city => city.SeaLevelId)
.Where(level => validSeaLevels.Contains(level) == false)
.ToArray();
if (invalidSeaLevels.Any())
{
return BadRequest(invalidSeaLevels);
}
This type of deep linking is where SelectMany<T> becomes helpful:
HashSet<decimal> SentIdSeaLevelSet = country.States
.SelectMany(s => s.Cities.Select(c => c.IdSeaLevel)).Distinct().ToHashSet()
We want to project the IdSeaLevel but Cities is a List, so at some point you need the inner Cities.Select() but that can be inside or after the SelectMany which effectively flattens the hierarchy so that all of the nested Cities become a single list, the following would also work:
HashSet<decimal> SentIdSeaLevelSet = country.States
.SelectMany(s => s.Cities).Select(c => c.IdSeaLevel).Distinct().ToHashSet()
I prefer to use projection first inside the SelectMany it we never need any other properties from the City objects (the first example) but different applications and structures might dictate that the second expression performs better.
For the final comparison your logic looks ok, another way to compare lists is by using except:
bool badRequest= SentIdSeaLevelSet
.Except(AllowedIdSeaLevellist).Any();
This is functionally equivalent to your previous comparison and works because the types of the collections being compared are the same, runtime might be marginally faster but at this level you base your decision on code readability, which is a subjective topic on its own, but I prefer the except version specifically when we are comparing lists.
I have a List<Map> and I wanted to update the Map.Target property based from a matching value from another List<Map>.
Basically, the logic is:
If mapsList1.Name is equal to mapsList2.Name
Then mapsList1.Target = mapsList2.Name
The structure of the Map class looks like this:
public class Map {
public Guid Id { get; set; }
public string Name { get; set; }
public string Target { get; set; }
}
I tried the following but obviously it's not working:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
// populate the 2 lists here
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
The count of items in list 1 will be always greater than or equal to the count of items in list 2. No duplicates in both lists.
Assuming there are a small number of items in the lists and only one item in list 1 that matches:
list2.ForEach(l2m => list1.First(l1m => l1m.Name == l2m.Name).Target = l2m.Target);
If there are more than one item in List1 that must be updated, enumerate the entire list1 doing a First on list2.
list1.ForEach(l1m => l1m.Target = list2.FirstOrDefault(l2m => l1.Name == l2m.Name)?.Target ?? l1m.Target);
If there are a large number of items in list2, turn it into a dictionary
var d = list2.ToDictionary(m => m.Name);
list1.ForEach(m => m.Target = d.ContainsKey(m.Name) ? d[m.Name].Target : m.Target);
(Presumably list2 doesn't contain any repeated names)
If list1's names are unique and everything in list2 is in list1, you could even turn list1 into a dictionary and enumerate list2:
var d=list1.ToDictionary(m => m.Name);
list2.ForEach(m => d[m.Name].Target = m.Target);
If List 2 has entries that are not in list1 or list1 has duplicate names, you could use a Lookup instead, you'd just have to do something to avoid a "collection was modified; enumeration may not execute" you'd get if you were trying to modify the list it returns in response to a name
mapsList1.Where(m1 => mapsList2.Where(m2 => m1.Name == m2.Name) ) // don't know what to do next
LINQ Where doesn't really work like that / that's not a statement in itself. The m1 is the entry from list1, and the inner Where would produce an enumerable of list 2 items, but it doesn't result in the Boolean the outer Where is expecting, nor can you do anything to either of the sequences because LINQ operations are not supposed to have side effects. The only thing you can do with a Where is capture or use the sequence it returns in some other operation (like enumerating it), so Where isn't really something you'd use for this operation unless you use it to find all the objects you need to alter. It's probably worth pointing out that ForEach is a list thing, not a LINQ thing, and is basically just another way of writing foreach(var item in someList)
If collections are big enough better approach would be to create a dictionary to lookup the targets:
List<Map> mapsList1 = new List<Map>();
List<Map> mapsList2 = new List<Map>();
var dict = mapsList2
.GroupBy(map => map.Name)
.ToDictionary(maps => maps.Key, maps => maps.First().Target);
foreach (var map in mapsList1)
{
if (dict.TryGetValue(map.Name, out var target))
{
map.Target = target;
}
}
Note, that this will discard any possible name duplicates from mapsList2.
So I have a collection of objects who have multiple properties, two of these are groupname and personname. Now I need to count in the collection how many of each object belong to a certain group and person. So in other words, I need to group by groupname, then personname and then count how many objects have this combination. First I created this
public MultiKeyDictionary<string, string, int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
List<string> groups = gr;
groups.Add("");
List<string> names = na;
names.Add("");
List<Home> Filtered = homes.ToList();
Filtered.ForEach(h => h.RemoveNull());
var result = new MultiKeyDictionary<string, string, int>();
int counter1 = 0;
foreach (var g in groups)
{
int counter2 = 0;
foreach (var n in names)
{
int counter3 = 0;
foreach (Home h in Filtered)
{
if (h.GroupName == g && h.PersonName == n)
{
counter3++;
if (counter3 > 100)
break;
}
}
if (counter3 > 0)
{
result.Add(g,n,counter3);
}
counter2++;
}
counter1++;
}
Which may look good, but the problem is that the "home" parameter can contain more than 10000 objects, with more than 1500 unique names and around 200 unique groups. Which causes this to iterate like a billion times really slowing my program down. So I need an other way of handling this. Which made me decide to try using linq. Which led to this creation:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName })
.Select(y => (MultiKeyDictionary<string, string, int>)result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
Which gives an error "Cannot convert type 'void' to 'MultiKeyDictionary<string,string,int>' and I have no idea how to solve it. How can I make it so that the result of this query gets stored all in one MultikeyDictionary without having to iterate over each possible combination and counting all of them.
Some information:
MultiKeyDictionary is a class I defined (something I found on here actually), it's just a normal dictionary but with two keys assosiated to one value.
The RemoveNull() method on the Home object makes sure that all the properties of the Home object are not null. If it is the case the value gets sets to something not null ("null", basic date, 0, ...).
The parameters are:
homes = a list of Home objects received from an other class
gr = a list of all the unique groups in the list of homes
na = a list of all the unique names in the list of homes
The same name can occur on different groups
Hopefully someone can help me get further!
Thanks in advance!
Select must return something. You are not returning but only adding to an existing list. Do this instead:
var newList = Filtered.GroupBy(x => new { x.GroupName, x.PersonName }):
var result = new MultiKeyDictionary<string, string, int>);
foreach(var y in newList)
{
result.Add(y.Key.GroupName, y.Key.PersonName, y.ToList().Count));
}
The reason you are getting error below:
"Cannot convert type 'void' to 'MultiKeyDictionary'
is because you are trying to cast the returned value from Add which is void to MultiKeyDictionary<string,string,int> which clearly cannot be done.
If MultiKeyDictionary requires the two keys to match in order to find a result, then you might want to just use a regular Dictionary with a Tuple as a composite type. C# 7 has features that make this pretty easy:
public Dictionary<(string, string), int> GetPersonsPerGroup(IEnumerable<Home> homes ,List<string> gr, List<string> na)
{
return Filtered.GroupBy(x => (x.GroupName, x.PersonName))
.ToDictionary(g => g.Key, g => g.Count);
}
You can even associate optional compile-time names with your tuple's values, by declaring it like this: Dictionary<(string groupName, string personName), int>.
Your grouping key anonymous object should work fine as a standard Dictionary key, so no reason to create a new type of Dictionary unless it offers special access via single keys, so just convert the grouping to a standard Dictionary:
var result = Filtered.GroupBy(f => new { f.GroupName, f.PersonName })
.ToDictionary(fg => fg.Key, fg => fg.Count());
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
UPDATE:
Sorry, I think I omitted an important section in regards to the above. The list is contained within another list i.e. List> myitemList; which will contain other irrelevant fields which may require further filtering. I'm not sure???
NOTE: Sorry formatting is messed up once again!
To give a bit of context to this:
Item1 (of type List)
Item Name Value
(Item 1) Type 1
(Item 2) Description Test
(Item 3) Code A
(Item 4) Net 100.00
Item2 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test1
(Item 3) Code B
(Item 4) Net 95.55
Item3 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test2
(Item 3) Code A
(Item 4) Net 35.95
As you can see each list of type List contains 4 Field entries where my Field is defined with Name (String) and Value (Object)
Each of these list is then added to a main list. So I need to loop through the main list and in turn I want to end up with a dictionary what will contain the "Code" and sum of "Net" for each list. So at the end, I should just end up with
A, 135.95
B, 95.55
I don't know if the above make sense. I hope it does!
UPDATE
The fact that I'm dealing with a list> actually didn't make a different as I actually wanted to sum up one list at the time, so provide answer is correct! Thank you!
The code that you posted is almost what you need - for using it on the list you need to simplify it slightly:
var result = myList // No flattening
.GroupBy(x => x.Code) // Group the items by the Code
.ToDictionary(g => g.Key, g => g.Sum(v => v.Value)); // Total up the values
var list = new List<Field>
{
new Field { Code = "A", Value = 10 },
new Field { Code = "A", Value = 20 },
new Field { Code = "B", Value = 30 },
};
var dic = list
.GroupBy(z => z.Code)
.ToDictionary(z => z.Key, z => z.Sum(f => f.Value));
You can do:
Dictionary<string, int> dictionary =
list.GroupBy(r => r.ID)
.ToDictionary(grp => grp.Key, grp => grp.Sum(r => r.Value));
Considering you have class like:
public class MyClass
{
public string ID { get; set; }
public int Value { get; set; }
}
I have got two lists of two different type which has the following common properties.
Id -->used to identify corresponding objects
Bunch of other Properties
ModificationDate
Need to compare these two lists based on Modification date.If the modified date is different (first list ModificationDate greater than second list's ModificationDate then, copy all the properties if that item from first list to second.
Please let me know the best way to do this.
EDITED:Second list may or maynot contain all elements of the first and vice versa.My first list is always the source list. so if an item is present in list 1 and not present in list 2 we need to add it in list 2. also if an item present in list 2 but in not in list 1 then remove it from list2.
Finding added/deleted items
var list1 = new List<MyType>();
var list2 = new List<MyType>();
// These two assume MyType : IEquatable<MyType>
var added = list1.Except(list2);
var deleted = list2.Except(list1);
// Now add "added" to list2, remove "deleted" from list2
If MyType does not implement IEquatable<MyType>, or the implementation is not based solely on comparing ids, you will need to create an IEqualityComparer<MyType>:
class MyTypeIdComparer : IEqualityComparer<MyType>
{
public bool Equals(MyType x, MyType y)
{
return x.Id.CompareTo(y.Id);
}
public int GetHashCode(MyType obj)
{
return obj.Id.GetHashCode();
}
}
Which will allow you to do:
// This does not assume so much for MyType
var comparer = new MyTypeIdComparer();
var added = list1.Except(list2, comparer);
var deleted = list2.Except(list1, comparer);
Finding modified items
var modified = list1.Concat(list2)
.GroupBy(item => item.Id)
.Where(g => g.Select(item => item.ModificationDate)
.Distinct().Count() != 1);
// To propagate the modifications:
foreach(var grp in modified) {
var items = grp.OrderBy(item => item.ModificationDate);
var target = items.First(); // earliest modification date = old
var source = grp.Last(); // latest modification date = new
// And now copy properties from source to target
}
This might be able to help. The Linq library has lots of decent functions, such as Except, Intersection.
http://msdn.microsoft.com/en-us/library/bb397894.aspx
The provided link was helpful in comparing two lists of different types
Comparing Collections in .Net