Find duplicates within multiple lists using linq - c#

I've got 4 classes each inheriting from the same base class. They all have an Id & Path property in common. When my data is loaded from xml, I build 4 lists based on these classes.
I've got the following code which allows me to compare 2 lists:
var list1 = settings.EmailFolders.Select(m => m.Path).ToList().
Intersect(settings.LogPaths.Select(m=>m.Path).ToList()).ToList();
but I'd like
I want to compare the 1st list to the other 3 and find if there are duplicate path.
If none are found, I then want to apply the same logic comparing the second list and compare it to the other 2.
If none are found, I then want to apply the same logic comparing the third list and compare it to the last one.
Please note that I don't want point 1, 2 and 3 in a single process as I'd like to report the relevant error if duplicates are found.
If I re-use the above and add an additional Intersect with an additional list,
var list1 = settings.EmailFolders.Select(m => m.Path).ToList().
Intersect(settings.LogPaths.Select(m=>m.Path).ToList()).
Intersect(settings.SuccessPaths.Select(m=>m.Path).ToList()).
Intersect(settings.FailurePaths.Select(m=>m.Path).ToList()).List();
The above would like applying an and operator between each rather than an or operator which is not what I want. I need to know if any of the Path used in FailurePaths or LogPaths or SuccessPaths are already used in my EmailFolder.Path
UPDATE:
I've updated my question with an answer based on #Devuxer suggestion which is the correct answer but instead of working with string array, I'm basing it on list of classes which all contains a Path property.
List<EmailFolder> emailFolders = LoadEmailFolders();
List<LogPath> logPaths = LoadLogPaths();
List<SuccessPath> successPaths = LoadSuccessPaths();
List<FailurePath> failurePaths = LoadFailurePaths();
var pathCollisions = EmailFolders.Select(a=>a.Path)
.Intersect(LogPaths.Select(b=>b.Path))
.Select(x => new { Type = "LogPath", Path = x })
.Concat(EmailFolders.Select(c=>c.Path)
.Intersect(SuccessPaths.Select(d=>d.Path))
.Select(x => new { Type = "SuccessPath", Path = x }))
.Concat(EmailFolders.Select(e=>e.Path)
.Intersect(FailurePaths.Select(f=>f.Path))
.Select(x => new { Type = "FailurePath", Path = x })).ToList();
Note that in the above example, for simplicity sake, I've just declared my list with a function for each which loading the relevant data but in my case, all the data is deserialized and loaded at once.

I may be slightly misunderstanding your requirements, but it looks like you want something like this:
var emailFolders = new[] { "a", "b", "c" };
var logPaths = new[] { "c", "d", "e" };
var successPaths = new[] { "f", "g", "h" };
var failurePaths = new[] { "a", "c", "h" };
var pathCollisions = emailFolders
.Intersect(logPaths)
.Select(x => new { Type = "Email-Log", Path = x })
.Concat(emailFolders
.Intersect(successPaths)
.Select(x => new { Type = "Email-Success", Path = x }))
.Concat(emailFolders
.Intersect(failurePaths)
.Select(x => new { Type = "Email-Failure", Path = x }));
This results in:
Type | Path
--------------------
Email-Log | c
Email-Failure | a
Email-Failure | c
--------------------
If this is what you were looking for, Intersect was definitely the right way to find the duplicates. I just added some Concat clauses and provided a Type so you could see what type of path collision occurred.
Edit
Well the bullets in your question seem to ask for something different from the last sentence of your question.
If you really want all comparisons, one way to do that is to add more Concat clauses:
var pathCollisions = emailFolders
.Intersect(logPaths)
.Select(x => new { Type = "Email-Log", Path = x })
.Concat(emailFolders
.Intersect(successPaths)
.Select(x => new { Type = "Email-Success", Path = x }))
.Concat(emailFolders
.Intersect(failurePaths)
.Select(x => new { Type = "Email-Failure", Path = x }))
.Concat(logPaths
.Intersect(successPaths)
.Select(x => new { Type = "Log-Success", Path = x }))
.Concat(logPaths
.Intersect(failurePaths)
.Select(x => new { Type = "Log-Failure", Path = x }))
.Concat(successPaths
.Intersect(failurePaths)
.Select(x => new { Type = "Success-Failure", Path = x }));
This doesn't "short-circuit" the way you intended, though (so it will find all the path collisions rather than stop after the first type), but it should at least give you the data you need to generate a report.

You can use the Contains by using the property to compare
var result = list1.Where(x => list2.Contains(x.Path));
It will give all the items from list1 which are also in list2
If you want only the count then use .Count() at the end
Same rule apply to compare other 3 lists

Related

Using linq to group and sum List<int> (Zip?)

I have a list of objects I want to group.
Objects have a List parameter, and during grouping I want to make the sum of the lists like this :
for(int i=0;i<MyList1.Count();i++)
{
StatutOperations[i]=StatutOperations1[i]+StatutOperations2[i]...
}
For now using linq I have the following :
liste_rep = liste_rep.GroupBy(l => l.Nom)
.Select(cl => new Repere
{
Quantite = cl.Sum(c => c.Quantite),
IdAff = cl.First().IdAff,
ID = 0,
ListeOperations = cl.First().ListeOperations,
StatutOperations = cl.Zip(StatutOperations)//First().StatutOperations
}).ToList();
The line making problem is the last one, I found how to use Zip function to summ two tables, but what if I want to use it grouping Lists?
Edit : StatusOperations is a list of integers, concretely liste_rep is a list of details, details have a list of n operations, and StatusOperations determines how much details have been operated for each operation.
Example :
ListOperations = CUT, DRILL, PAINT
StatusOperations = 20,20,10
This means 20 details are cut, 20 are drilled and 10 are painted
I want to group the list of details getting totals for each operation.
Edit 2 :
For now I only could manage to do it making myself the grouping :
liste_rep = liste_rep.OrderBy(p => p.Nom).ToList();
if (liste_rep.Count()>1)
{
totalStatut = liste_rep[0].StatutOperations.ConvertAll(s => s = 0);
string oldRep = "";
Repere repere = new Repere();
foreach (Repere rep in liste_rep)
{
if (rep.Nom!=oldRep)
{
newListRep.Add(repere);
repere = new Repere();
repere.Nom = rep.Nom;
repere.StatutOperations = rep.StatutOperations;
}
else
{
repere.StatutOperations=repere.StatutOperations.Zip(rep.StatutOperations, (x, y) => x + y).ToList();
}
oldRep = rep.Nom;
}
}
You can use this
if StatutOperations is a list of int).
Use this at last line.
StatutOperations= cl.Aggregate((opl1, opl2) =>
{ return opl1.StatutOperations.Zip(opl2.StatutOperations, (opin1,opin2)=>opin1+opin2).ToList(); });
in above code Aggregate runs through two elements and aggregate as sum (op1+op2).
Note : Remember use aggregate if and only if list contains more than one element
.
Edit:
Sorry the above code is incorrect as this is applying aggregate on repere type object and hence the expected return value would be of Repere type.
Edited my code now it should work fine now.
liste_rep.GroupBy(l => l.Nom)
.Select(cl => new Repere
{
Quantite = cl.Sum(c => c.Quantite),
IdAff = cl.First().IdAff,
ID = 0,
ListeOperations = cl.First().ListeOperations,
StatutOperations = cl
.Select(x=>x.StatutOperations)
.Aggregate((x,y)=> x.Zip(y,(p,q)=>p+q).ToList());
}).ToList();

Order of groups with dynamic linq

Somewhat similar to this question:
Where do I put the "orderby group.key" in this LINQ statement?
Except I'm using Dynamic.Linq which makes this a bit harder. I have a bunch of data coming from a database and then I'm grouping by some field and then outputing the result. The problem is that the ordering of the groups seems to randomly jump around which isn't very convenient for the end-user. So taking inspiration from the linked question, if I had this:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
// note the first non-dynamic select here was just because I don't think dynamic linq
// will support indexing a string like that and it's not an important detail anyway
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word");
foreach(IGrouping<object, dynamic> g in wordList)
{
Console.WriteLine("Words that being with {0}:",
g.Key.ToString().ToUpper());
foreach (var word in g)
Console.WriteLine(" " + word);
}
Console.ReadLine();
How would I get it to order the keys? At least part of the problem is that the dynamic GroupBy returns an IEnumerable. I thought it might be as easy as:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter)","Word")
.OrderBy("Key");
But that gives me a System.ArgumentException (At least one object must implement IComparable.) when it hits the foreach loop.
My actual code in my project is a little more complicated and looks something like this:
var colGroup = row.GroupBy(string.Format("new({0})",
string.Join(",", c)), string.Format("new({0})",
string.Join(",", v)));
Where c is a list of strings that I need to group by and v is a list of strings that I need to select in each group.
Ok - this is one way to do it, but it might be a little to static to be useful. The problem is that I had this part:
.GroupBy("new (FirstLetter)","Word");
Using new because I can't use a value type as a key (I had another question about that: https://stackoverflow.com/a/26022002/1250301). When with the OrderBy("Key") part, the problem is that it doesn't have a way to compare those dynamic types. I could solve it like this:
var wordList = words.Select(w => new {FirstLetter = w[0].ToString(), Word = w})
.GroupBy("FirstLetter","Word")
.OrderBy("Key");
Making the key a string. Or like this:
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy("new (FirstLetter as k)","Word")
.OrderBy("Key.k");
Making it order by something (a char) that is comparable.
I can make it work with my actual problem like this (but it's kind of ugly):
var colGroup = row.GroupBy(string.Format("new({0})", string.Join(",", c)),
string.Format("new({0})", string.Join(",", v)))
.OrderBy(string.Join(",", c.Select(ob => string.Format("Key.{0}", ob))));
I am not sure what you are trying to do, but is that syntax even compiling?
try:
string[] words = { "boy","car", "apple", "bill", "crow", "brown" };
var wordList = words.Select(w => new {FirstLetter = w[0], Word = w})
.GroupBy(x => x.FirstLetter, x => x.Word)
.OrderBy(x => x.Key);

Getting a amount of rows with same month with LINQ from an MVC model using dateTime, is it possible?

I have this need to know how many rows have the same month from a table and I have no idea of how to do it. I thought I'd try some LINQ but I've never used it before so I don't even know if it's possible. Please help me out!
public ActionResult returTest()
{
ViewData["RowsWithSameMonth"] = // I'm guessing I can put some LINQ here?
var returer = from s in db2.ReturerDB select s;
return View(returer.ToList());
}
The ideal would be to get, maybe a two dimensional array with the month in the first cell and the amount of rows from the db in the second?
I'd like the result to be sort of :
string[,] statistics = new string[,]
{
{"2013-11", "5"},
{"2013-12", "10"},
{"2014-01", "3"}
};
Is this doable? Or should I just query the database and do a whole lot of stuff? I'm thinking that I can solve this on my own, but it would mean a lot of ugly code. Background: self taught C# developer at IT-company with 1 years experience of ugly codesmanship and no official degree of any kind.
EDIT
var returer = from s in db2.ReturerDB select s;
var dateRange = returer.ToList();
var groupedData = dateRange.GroupBy(dateRow => dateRow.ToString())
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
string test01 = "";
string test02 = "";
foreach (var item in groupedData)
{
test01 = item.Month.ToString();
test02 = item.MountCount.ToString();
}
In debug, test01 is "Namespace.Models.ReturerDB" and test02 is "6" as was expected, or at least wanted. What am I doing wrong?
You can do this:
var groupedData = db2.ReturerDB.GroupBy(r => new { r.Date.Year, r.Date.Month })
.Select(g => new { g.Key.Year, g.Key.Month, Count = g.Count() })
.OrderBy(x => x.Year).ThenBy(x => x.Month);
.ToList();
var result = groupedData
.ToDictionary(g => string.Format("{0}-{1:00}", g.Year, g.Month),
g => g.Count);
Which will give you
Key Value
---------------
2013-11 5
2013-12 10
2014-01 3
(Creating a dictionary is slightly easier than a two-dimensional array)
This will work against a SQL back-end like entity framework of linq-to-sql, because the expressions r.Date.Year and r.Date.Month can be translated into SQL.
with a nod to mehrandvd, here is how you'd achieve this using linq method chain approach:
var dateRange = { // your base collection with the dates};
// make sure you change MyDateField to match your won datetime field
var groupedData = dateRange
.GroupBy(dateRow => dateRow.MyDateField.ToString("yyyy-mm"))
.OrderBy(monthGroup => monthGroup.Key)
.Select(monthGroup => new
{
Month = monthGroup.Key,
MountCount = monthGroup.Count()
});
This would give you the results you required, as per the OP.
[edit] - as requested, example of how to access the newly created anonymous type:
foreach (var item in groupedData)
{
Console.WriteLine(item.Month);
Console.WriteLine(item.MountCount);
}
OR, you could return the whole caboodle as a jsonresult to your client app and iterate inside that, i.e the final line of your view would be:
return Json(groupedData, JsonRequestBehavior.AllowGet);
hope this clarifies.
What you need is grouping.
Considering you have a list of dates a solution would be this:
var dateRows = // Get from database
var monthlyRows = from dateRow in dateRows
group dateRow by dateRow.ToString("yyyy/mm") into monthGroup
orderby monthGroup.Key
select new { Month=monthGroup.Key, MountCount=monthGroup.Count };
// Your results would be a list of objects which have `Month` and `MonthCount` properties.
// {Month="2014/01", MonthCount=24}
// {Month="2014/02", MonthCount=28}

Create a new anonymous type list to union onto existing anonymous type list

I have a list of anonymous types that I get from my database:
var takenChannels = (from b in bq.GetStuff(db)
where b.RecordType == "H" && b.TourStartDateTime.Date == date
select new { Start = b.TourStartDateTime, End = b.TourEndDateTime, Channel = b.RadioChannel, TourArea = b.TourArea }).ToList();
Then I use this list info to do some stuff in a foreach loop. I want to add to this list a new anonymous item for when I come back round in the loop.
Something like:
takenChannels.Union{new[] { new{Start = DateTime.Now, End = DateTime.Now.AddDays(1), Channel = 25, TourArea = "Area" }});
Obviously this doesn't work. How do I do it?
Edit 1:
takenChannels.Add(new { Start = s, End = e, Channel = channel, TourArea = booking.TourArea });
This is the closest I've got so far (Thanks to Daniel)... but the error I get is:
Error 6 Argument 1: cannot convert from 'AnonymousType#2' to 'AnonymousType#1'
This answer might be a bit late, but since this is the question I found when Googling for the same problem, I think I should complete it with a working answer.
There is no problem to Union multiple times over anonymous types. It is important that all properties are declared in all instances and that they have the same data type. if not, you get the error above.
In your specific case, does the database perhaps return TourStartDateTime or TourEndDateTime as DateTime??
Is RadioChannel an int from the database or perhaps an int? or string?
Is TourArea a string in the database?
Just make sure the data types match and you should be fine. Below is a working snippet of code I use in my own program:
var regions = (
new[] { new { Id = "-1", Name = "---", Pattern = (string)null } }
).Union(
from x in db.Userlists where x.ListType == 2 select new { Id = x.UserlistID.ToString(), Name = x.Name, Pattern = (string)null }
).Union(
from x in db.Lookups where x.Category == "Stock" select new { Id = x.Key, Name = x.Key, Pattern = x.Value }
).ToArray();
You can simply Add to the list:
takenChannels.Add(new { Start = ... });

Linq group by using reflection

I have data table "Car" which have 3 cols (owner, carType, colour). My question is how can i make the grouping portion more dynamic by using reflection. my idea is add the grouping col in to array, then use the reflection on the query grouping part. however i was struck at the reflection..
var gcols = new string[] { "owner", "carType" };
var reseult = dt.AsEnumerable()
.GroupBy(x => new
{
carType = x.Field<string>("carType"),
colour = x.Field<string>("colour")
})
.Select(x => new
{
CarType = x.Key.carType,
Colour = x.Key.colour,
count = x.Count()
})
.OrderBy(x => x.CarType).ToList();
If you added this extension method to object:
public static T Field<T>(this object source, string FieldName)
{
var type = source.GetType();
var field = type.GetField(FieldName);
return (T)field.GetValue(source);
}
You'd be able to use the syntax you've posted in your code.
I've not added any safety checking here so it'll need cleaning up, but it'll get you going. Ideally you'd want to check that the type of field is the same as T and a few other checks.

Categories