Joining two lists from each statement - c#

if (Settings.Default.All)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().OrderBy(x => x.TxnID).Reverse());
}
if (Settings.Default.MLhuillier)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => x.ServiceMode == "MLhuillier").OrderBy(x => x.TxnID).Reverse());
}
if (Settings.Default.BPI)
{
List = new ObservableCollection<LexisNexis>(UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => x.ServiceMode == "BPI").OrderBy(x => x.TxnID).Reverse());
}
I want to combine each list from each if statement that returns true. my program just return the last list. TYIA

Simplifying the code
The following should do what you want with little duplication and with at most one traversal through LexisForApprove2.
var orFilters = Settings.Default.All ? null : new List<string>();
if (!Settings.Default.All)
{
if (Settings.Default.MLhuillier) orFilters.Add("MLhuillier");
if (Settings.Default.BPI) orFilters.Add("BPI");
}
var l = orFilters == null
? UnitOfWork.Query.Lexis.LexisForApprove2() // Everything
: orFilters.Any()
? UnitOfWork.Query.Lexis.LexisForApprove2().Where(x => orFilters.Contains(x.ServiceMode))
: new List<LexisNexis>(); // Not 'All' but no others allowed
List = new ObservableCollection<LexisNexis>(l.OrderByDescending(y => y.TxnID));
Distinct
Just for the record, and not recommened for this case, you could use List's AddRange or Linq's Union followed by Distinct, which would work if the LexisNexis objects are good at comparing themselves with others :)

Related

C# How to split a List in two using LINQ [duplicate]

This question already has answers here:
Can I split an IEnumerable into two by a boolean criteria without two queries?
(6 answers)
Closed 2 years ago.
I am trying to split a List into two Lists using LINQ without iterating the 'master' list twice. One List should contain the elements for which the LINQ condition is true, and the other should contain all the other elements. Is this at all possible?
Right now I just use two LINQ queries, thus iterating the (huge) master List twice.
Here's the (pseudo) code I am using right now:
List<EventModel> events = GetAllEvents();
List<EventModel> openEvents = events.Where(e => e.Closer_User_ID == null);
List<EventModel> closedEvents = events.Where(e => e.Closer_User_ID != null);
Is it possible to yield the same results without iterating the original List twice?
You can use ToLookup extension method as follows:
List<Foo> items = new List<Foo> { new Foo { Name="A",Condition=true},new Foo { Name = "B", Condition = true },new Foo { Name = "C", Condition = false } };
var lookupItems = items.ToLookup(item => item.Condition);
var lstTrueItems = lookupItems[true];
var lstFalseItems = lookupItems[false];
You can do this in one statement by converting it into a Lookup table:
var splitTables = events.Tolookup(event => event.Closer_User_ID == null);
This will return a sequence of two elements, where every element is an IGrouping<bool, EventModel>. The Key says whether the sequence is the sequence with null Closer_User_Id, or not.
However this looks rather mystical. My advice would be to extend LINQ with a new function.
This function takes a sequence of any kind, and a predicate that divides the sequence into two groups: the group that matches the predicate and the group that doesn't match the predicate.
This way you can use the function to divide all kinds of IEnumerable sequences into two sequences.
See Extension methods demystified
public static IEnumerable<IGrouping<bool, TSource>> Split<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate)
{
return source.ToLookup(predicate);
}
Usage:
IEnumerable<Person> persons = ...
// divide the persons into adults and non-adults:
var result = persons.Split(person => person.IsAdult);
Result has two elements: the one with Key true has all Adults.
Although usage has now become easier to read, you still have the problem that the complete sequence is processed, while in fact you might only want to use a few of the resulting items
Let's return an IEnumerable<KeyValuePair<bool, TSource>>, where the Boolean value indicates whether the item matches or doesn't match:
public static IEnumerable<KeyValuePair<bool, TSource>> Audit<TSource>(
this IEnumerable<TSource> source,
Func<TSource,bool> predicate)
{
foreach (var sourceItem in source)
{
yield return new KeyValuePair<bool, TSource>(predicate(sourceItem, sourceItem));
}
}
Now you get a sequence, where every element says whether it matches or not. If you only need a few of them, the rest of the sequence is not processed:
IEnumerable<EventModel> eventModels = ...
EventModel firstOpenEvent = eventModels.Audit(event => event.Closer_User_ID == null)
.Where(splitEvent => splitEvent.Key)
.FirstOrDefault();
The where says that you only want those Audited items that passed auditing (key is true).
Because you only need the first element, the rest of the sequence is not audited anymore
GroupBy and Single should accomplish what you're looking for:
var groups = events.GroupBy(e => e.Closer_User_ID == null).ToList(); // As others mentioned this needs to be materialized to prevent `events` from being iterated twice.
var openEvents = groups.SingleOrDefault(grp => grp.Key == true)?.ToList() ?? new List<EventModel>();
var closedEvents = groups.SingleOrDefault(grp => grp.Key == false)?.ToList() ?? new List<EventModel>();
One line solution by using ForEach method of List:
List<EventModel> events = GetAllEvents();
List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();
events.ForEach(x => (x.Closer_User_ID == null ? openEvents : closedEvents).Add(x));
You can do without LINQ. Switch to conventional loop approach.
List<EventModel> openEvents = new List<EventModel>();
List<EventModel> closedEvents = new List<EventModel>();
foreach(var e in events)
{
if(e.Closer_User_ID == null)
{
openEvents.Add(e);
}
else
{
closedEvents.Add(e);
}
}

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();

C# LINQ - Comparing a IEnumerable<string> against an anonmyous list?

The basic question
I have:
IEnumerable<string> listA
var listB (this is an anonymous list generated by a LINQ query)
I want to query a list of objects that contain listA to see if they match to listB:
someObjectList.Where(x => x.listA == listB)
The comparison doesn't work - so how do I ensure that both lists are the same type for comparison?
The detailed question
I am grouping a larger list into a subset that contains a name and related date(s).
var listGroup = from n in list group n by new
{ n.NAME } into d
select new
{
NAME = d.Key.NAME, listOfDates = from x in d select new
{ Date = x.DATE } };
I have a object to hold the values for further processing:
class SomeObject
{
public SomeObject()
{
_listOfDates = new List<DateTime>();
}
private IEnumerable<DateTime> _listOfDates;
public IEnumerable<DateTime> ListOfDates
{
get { return _listOfDates; }
set { _listOfDates = value; }
}
}
I am then iterating over the listGroup and adding into a generic List<> of SomeObject:
foreach(var item in listGroup)
{
SomeObject so = new SomeObject();
// ...do some stuff
if (some match occurs then add into List<SomeObject>)
}
As I iterate through then I want to check the existing List<SomeOjbect> for matches:
var record = someObjectList.Where(x => x.NAME == item.NAME &&
x.ListOfDates == item.listOfDates)
.SingleOrDefault();
The problem is that comparing x.ListOfDates against item.listOfDates doesn't work.
There is no compiler error but I suspect that the returned value lists are different. How to I get the lists to commonize so they can be compared?
Update #1
This seems to work to get the listOfDates into a similar format:
IEnumerable<DateTime> tempList = item.listOfDates.Select(x => x.DATE).ToList()
Then I followed the 'SequenceEqual' suggestion from #Matt Burland
You can just compare one IEnumerable<DateTime> to another IEnumerable<DateTime>, you need to compare the sequence. Luckily, there's Enumerable.SequenceEquals (in both static and extension method flavors) which should work here.
So something like:
var record = someObjectList
.Where(x => x.NAME == item.NAME && x.ListOfDates.SequenceEquals(item.listOfDates))
.SingleOrDefault();

Linq to get difference of two listviews and put it in third in Windows Form C#

I have two list views which have same data but differing in the number of records. I want to get the non-matching listviewitems in third list view. I have using the following code but it is not helping. The variables x and y are making problem.
var list1Source = lvFace.Items.Cast<ListViewItem>();
var list2Source = lvDBdata.Items.Cast<ListViewItem>();
lvDataToUpload = list1Source.Where(
(x => list2Source.All(y => y.Text != x.Text));
You are looking for LINQ Except method
var lvExcept1 = list1Source.Except(list2Source);
var lvExcept2 = list2Source.Except(list1Source);
lvDataToUpload = lvExcept1.Union(lvExcept2);
But you need to override Equals and GetHashCode methods for your ListViewItem class. If there is no option to do this (ListViewItem is Windows Forms class, not yours), you can define your own equality comparer:
public class ListViewItemComparer : IEqualityComparer<ListViewItem>
{
bool IEqualityComparer<ListViewItem>.Equals(ListViewItem x, ListViewItem y)
{
return (x.Text == y.Text);
}
int IEqualityComparer<ListViewItem>.GetHashCode(ListViewItem obj)
{
if (Object.ReferenceEquals(obj, null))
return 0;
return obj.Text.GetHashCode();
}
}
And final code is:
var lvExcept1 = list1Source.Except(list2Source, new ListViewItemComparer());
var lvExcept2 = list2Source.Except(list1Source, new ListViewItemComparer());
lvDataToUpload = lvExcept1.Union(lvExcept2);
LINQ doesn't have a "set difference" operator itself... but you can use Except twice:
var list1Text = list1Source.Select(x => x.Text);
var list2Text = list2Source.Select(x => x.Text);
var difference = list1Text.Except(list2Text)
.Concat(list2Text.Except(list1Text))
.ToList();
Try this
listIntersection = list1Source.Intersect(list2Source); // Gets matching elements
listUnion = list1Source.Union(list2Source); // Gets all elements
lvDataToUpload = listUnion.Except(listIntersection);

Use LINQ to group a sequence by date with no gaps

I'm trying to select a subgroup of a list where items have contiguous dates, e.g.
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
1 41 Meeting with John 03/06/2010
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
5 41 Meeting with Kay 14/06/2010
6 41 Meeting Continues 15/06/2010
I'm using a pivot point each time, so take the example pivot item as 3, I'd like to get the following resulting contiguous events around the pivot:
ID StaffID Title ActivityDate
-- ------- ----------------- ------------
2 41 Meeting with John 08/06/2010
3 41 Meeting Continues 09/06/2010
4 41 Meeting Continues 10/06/2010
My current implementation is a laborious "walk" into the past, then into the future, to build the list:
var activity = // item number 3: Meeting Continues (09/06/2010)
var orderedEvents = activities.OrderBy(a => a.ActivityDate).ToArray();
// Walk into the past until a gap is found
var preceedingEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID);
DateTime dayBefore;
var previousEvent = activity;
while (previousEvent != null)
{
dayBefore = previousEvent.ActivityDate.AddDays(-1).Date;
previousEvent = preceedingEvents.TakeWhile(a => a.ID != previousEvent.ID).LastOrDefault();
if (previousEvent != null)
{
if (previousEvent.ActivityDate.Date == dayBefore)
relatedActivities.Insert(0, previousEvent);
else
previousEvent = null;
}
}
// Walk into the future until a gap is found
var followingEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
DateTime dayAfter;
var nextEvent = activity;
while (nextEvent != null)
{
dayAfter = nextEvent.ActivityDate.AddDays(1).Date;
nextEvent = followingEvents.SkipWhile(a => a.ID != nextEvent.ID).Skip(1).FirstOrDefault();
if (nextEvent != null)
{
if (nextEvent.ActivityDate.Date == dayAfter)
relatedActivities.Add(nextEvent);
else
nextEvent = null;
}
}
The list relatedActivities should then contain the contiguous events, in order.
Is there a better way (maybe using LINQ) for this?
I had an idea of using .Aggregate() but couldn't think how to get the aggregate to break out when it finds a gap in the sequence.
Here's an implementation:
public static IEnumerable<IGrouping<int, T>> GroupByContiguous(
this IEnumerable<T> source,
Func<T, int> keySelector
)
{
int keyGroup = Int32.MinValue;
int currentGroupValue = Int32.MinValue;
return source
.Select(t => new {obj = t, key = keySelector(t))
.OrderBy(x => x.key)
.GroupBy(x => {
if (currentGroupValue + 1 < x.key)
{
keyGroup = x.key;
}
currentGroupValue = x.key;
return keyGroup;
}, x => x.obj);
}
You can either convert the dates to ints by means of subtraction, or imagine a DateTime version (easily).
In this case I think that a standard foreach loop is probably more readable than a LINQ query:
var relatedActivities = new List<TActivity>();
bool found = false;
foreach (var item in activities.OrderBy(a => a.ActivityDate))
{
int count = relatedActivities.Count;
if ((count > 0) && (relatedActivities[count - 1].ActivityDate.Date.AddDays(1) != item.ActivityDate.Date))
{
if (found)
break;
relatedActivities.Clear();
}
relatedActivities.Add(item);
if (item.ID == activity.ID)
found = true;
}
if (!found)
relatedActivities.Clear();
For what it's worth, here's a roughly equivalent -- and far less readable -- LINQ query:
var relatedActivities = activities
.OrderBy(x => x.ActivityDate)
.Aggregate
(
new { List = new List<TActivity>(), Found = false, ShortCircuit = false },
(a, x) =>
{
if (a.ShortCircuit)
return a;
int count = a.List.Count;
if ((count > 0) && (a.List[count - 1].ActivityDate.Date.AddDays(1) != x.ActivityDate.Date))
{
if (a.Found)
return new { a.List, a.Found, ShortCircuit = true };
a.List.Clear();
}
a.List.Add(x);
return new { a.List, Found = a.Found || (x.ID == activity.ID), a.ShortCircuit };
},
a => a.Found ? a.List : new List<TActivity>()
);
Somehow, I don't think LINQ was truly meant to be used for bidirectional-one-dimensional-depth-first-searches, but I constructed a working LINQ using Aggregate. For this example I'm going to use a List instead of an array. Also, I'm going to use Activity to refer to whatever class you are storing the data in. Replace it with whatever is appropriate for your code.
Before we even start, we need a small function to handle something. List.Add(T) returns null, but we want to be able to accumulate in a list and return the new list for this aggregate function. So all you need is a simple function like the following.
private List<T> ListWithAdd<T>(List<T> src, T obj)
{
src.Add(obj);
return src;
}
First, we get the sorted list of all activities, and then initialize the list of related activities. This initial list will contain the target activity only, to start.
List<Activity> orderedEvents = activities.OrderBy(a => a.ActivityDate).ToList();
List<Activity> relatedActivities = new List<Activity>();
relatedActivities.Add(activity);
We have to break this into two lists, the past and the future just like you currently do it.
We'll start with the past, the construction should look mostly familiar. Then we'll aggregate all of it into relatedActivities. This uses the ListWithAdd function we wrote earlier. You could condense it into one line and skip declaring previousEvents as its own variable, but I kept it separate for this example.
var previousEvents = orderedEvents.TakeWhile(a => a.ID != activity.ID).Reverse();
relatedActivities = previousEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, prevItem) => items.OrderBy(a => a.ActivityDate).First().ActivityDate.Subtract(prevItem.ActivityDate).Days.Equals(1) ? ListWithAdd(items, prevItem) : items).ToList();
Next, we'll build the following events in a similar fashion, and likewise aggregate it.
var nextEvents = orderedEvents.SkipWhile(a => a.ID != activity.ID);
relatedActivities = nextEvents.Aggregate<Activity, List<Activity>>(relatedActivities, (items, nextItem) => nextItem.ActivityDate.Subtract(items.OrderBy(a => a.ActivityDate).Last().ActivityDate).Days.Equals(1) ? ListWithAdd(items, nextItem) : items).ToList();
You can properly sort the result afterwards, as now relatedActivities should contain all activities with no gaps. It won't immediately break when it hits the first gap, no, but I don't think you can literally break out of a LINQ. So it instead just ignores anything which it finds past a gap.
Note that this example code only operates on the actual difference in time. Your example output seems to imply that you need some other comparison factors, but this should be enough to get you started. Just add the necessary logic to the date subtraction comparison in both entries.

Categories