I have List of (PatchFacilityManager) and a List of (Int) facilityManagerId. I want to make the below code efficient. Is there any way to remove these two foreach loop.
foreach (PatchFacilityManager PM in patchFacilityManager)
{
foreach (int FM in facilityManagerId)
{
if (PM.FacilityManagerId == FM)
{
PM.IsSelected = true;
}
}
}
Here's one way,
foreach (PatchFacilityManager PM in patchFacilityManager)
{
PM.IsSelected = facilityManagerId.Contains(PM.FacilityManagerId);
}
EDIT
This solution is efficient in two three ways IMHO as compared to the code given in the question.
First, it does not test for the condition and the result of the expression is straight away assigned into PM.IsSelected. As per LukeH's comment, it is mandatory to not set the PM.IsSelected to false, so the condition is unavoidable. However this improvement is applicable if the asked needs to set it to false. . From question asker's comment, his case seem to go right with this optimization. So no need for conditional assignment.
Second, it does not iterate through whole list, since List.Contains(int), returns true and come out of loop on the first occurrence of the int passed in argument.
Third, when framework gives you the functionality List.Contains(int), then why re-invent the wheel. So from maintenance perspective this is also more efficient.
var ids = new HashSet<int>(facilityManagerId);
foreach (PatchFacilityManager pfm in patchFacilityManager)
{
if (ids.Contains(pfm.FacilityManagerId))
pfm.IsSelected = true;
}
patchFacilityManager
.Where(c => facilityManagerId.Contains(c.FacilityManagerId))
.ForEach(c => c.IsSelected = true);
You could store the facility manager id's in an array in sorted order, and then look them up using BinarySearch instead of a foreach.
patchFacilityManager
.Where(m => facilityManagerId.Contains(m.FacilityManagerId))
.ToList()
.ForEach(m => m.IsSelected = true);
or
patchFacilityManager
.Join(facilityManagerId, m => m.FacilityManagerId, f => f, (m,f) => m)
.ToList()
.ForEach(m => m.IsSelected = true);
Another variant using LINQ syntax:
var match = for PM in patchFacilityManager
join FM in facilityManager on PM.FacilityManagerId equals FM
select PM;
foreach(var PM in match)
{
PM.IsSelected = true;
}
Just for fun and outside the box - an approach when the lists are sorted:
static void Main(string[] args)
{
List<int> nums = new List<int>(new int[] { 1, 2, 3, 4, 5, 6, 7, 8 });
List<int> ids = new List<int>(new int[] { 2, 4, 5 });
for (int i = 0, j = 0; i < nums.Count && j < ids.Count; i++)
{
int num = nums[i];
int id = ids[j];
if (num == id)
{
Console.WriteLine("Match = " + id);
j++;
}
}
Console.ReadLine();
}
I presume no knowledge of the performance benefit or detrement of this idea. Of course you can modify this to use a main foreach for the numbers and you manually use the Enumerator of the IDs inside the main foreach if you have allergies to fors.
Related
This one should be easy, but seems to be eluding me.
Given this variable: (which contains ~30 records)
var seriesData = new List<List<object>>();
How do I loop through every record, and omit any record that contains a null, anywhere inside?
Typically, each list inside will look like one of the following:
["02/16/2019", 5, 7, 10]
["02/17/2019", 3, 15, 2]
and sometimes:
["02/18/2019", 5, {null}, 10]
This is what I have tried, but, it's not working:
foreach (List<object> row in seriesData)
{
if (row.Contains(null)) seriesData.Remove(row);
}
The result I'm ending up with is completely empty?
You can use RemoveAll which accepts predicate:
seriesData.RemoveAll(row => row.Any(x => x == null))
If you can use LINQ, this should be easy:
seriesData = seriesData
// filter the lists (x) where all items in them (y) are not null
.Where(x => x.All(y => y != null))
// and get the result
.ToList();
Without LinQ, you may do something like this:
int i = 0;
while (i < seriesData.Count)
{
if (seriesData[i].Contains(null))
{
seriesData.RemoveAt(i);
} else {
i++;
}
}
This may very well be the most performant solution and not require LinQ if you don't use it already. If, on the other hand, you already use LinQ, then style may be more important than performance.
As an exercise, I write a version that changes the order of entries but has a lower complexity. As stated by #Lee, the above code may have an O(n^2) complexity. Here is another version, maybe some benchmarking if performance is really important would help:
int i = 0, last;
while (i < seriesData.Count)
{
if (seriesData[i].Contains(null))
{
last = seriesData.Count - 1;
seriesData[i] = seriesData[last];
seriesData.RemoveAt(last);
} else {
i++;
}
}
There are many ways to skin a cat. Here is yet another one that doesn't modify your original list:
var nonulls = seriesData.Where(sd => !sd.Any(o => o == null));
I have a list:
List<string> myList = new List<string>{ "dog", "cat", "dog", "bird" };
I want the output to be list of:
"dog (1)", "cat", "dog (2)", "bird"
I've already looked through this question but it is only talking about count the duplicates, my output should be with its duplicate index. like
duplicate (index)
I've tried this code:
var q = list.GroupBy(x => x)
.Where(y => y.Count()>1)
.Select(g => new {Value = g.Key + "(" + g.Index + ")"})
but it does not seem to work because:
Need to return all of my list back \ Or just modify the existing one. (and my answer returning only the duplicate ones)
For duplicate values need to add a prefix based on their "duplicate index".
How to do this in C#? Is there a way using Linq?
The accepted solution works but is extremely inefficient when the size of the list grows large.
What you want to do is first get the information you need in an efficient data structure. Can you implement a class:
sealed class Counter<T>
{
public void Add(T item) { }
public int Count(T item) { }
}
where Count returns the number of times (possibly zero) that Add has been called with that item. (Hint: you could use a Dictionary<T, int> to good effect.)
All right. Now that we have our useful helper we can:
var c1 = new Counter<string>();
foreach(string item in myList)
c1.Add(item);
Great. Now we can construct our new list by making use of a second counter:
var result = new List<String>();
var c2 = new Counter<String>();
foreach(string item in myList)
{
c2.Add(item);
if (c1.Count(item) == 1))
result.Add(item);
else
result.Add($"{item} ({c2.Count(item)})");
}
And we're done. Or, if you want to modify the list in place:
var c2 = new Counter<String>();
// It's a bad practice to mutate a list in a foreach, so
// we'll be sticklers and use a for.
for (int i = 0; i < myList.Count; i = i + 1)
{
var item = myList[i];
c2.Add(item);
if (c1.Count(item) != 1))
myList[i] = $"{item} ({c2.Count(item)})";
}
The lesson here is: create a useful helper class that solves one problem extremely well, and then use that helper class to make the solution to your actual problem more elegant. You need to count things to solve a problem? Make a thing-counter class.
This solution is not quadratic with respect to size of list, and it modifies the list in place as preferred in OP.
Any efficient solution will involve a pre-pass in order to find and count the duplicates.
List<string> myList = new List<string>{ "dog", "cat", "dog", "bird" };
//map out a count of all the duplicate words in dictionary.
var counts = myList
.GroupBy(s => s)
.Where(p => p.Count() > 1)
.ToDictionary(p => p.Key, p => p.Count());
//modify the list, going backwards so we can take advantage of our counts.
for (int i = myList.Count - 1; i >= 0; i--)
{
string s = myList[i];
if (counts.ContainsKey(s))
{
//add the suffix and decrement the number of duplicates left to tag.
myList[i] += $" ({counts[s]--})";
}
}
One way to do this is to simply create a new list that contains the additional text for each item that appears more than once. When we find these items, we can create our formatted string using a counter variable, and increment the counter if the list of formatted strings contains that counter already.
Note that this is NOT a good performing solution. It was just the first thing that came to my head. But it's a place to start...
private static void Main()
{
var myList = new List<string> { "dog", "cat", "dog", "bird" };
var formattedItems = new List<string>();
foreach (var item in myList)
{
if (myList.Count(i => i == item) > 1)
{
int counter = 1;
while (formattedItems.Contains($"{item} ({counter})")) counter++;
formattedItems.Add($"{item} ({counter})");
}
else
{
formattedItems.Add(item);
}
}
Console.WriteLine(string.Join(", ", formattedItems));
Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output
Ok, #EricLippert challenged me and I couldn't let it go. Here's my second attempt, which I believe is much better performing and modifies the original list as requested. Basically we create a second list that contains all the duplicate entries in the first. Then we walk backwards through the first list, modifying any entries that have a counterpart in the duplicates list, and remove the item from the duplicates list each time we encounter one:
private static void Main()
{
var myList = new List<string> {"dog", "cat", "dog", "bird"};
var duplicates = myList.Where(item => myList.Count(i => i == item) > 1).ToList();
for (var i = myList.Count - 1; i >= 0; i--)
{
var numDupes = duplicates.Count(item => item == myList[i]);
if (numDupes <= 0) continue;
duplicates.Remove(myList[i]);
myList[i] += $" ({numDupes})";
}
Console.WriteLine(string.Join(", ", myList));
Console.Write("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output
I want to create a loop to check a list of titles for duplicates.
I currently have this:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var x in productTitles)
{
var title = x.Text;
productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var y in productTitles.Skip(productTitles.IndexOf(x) + 1))
{
if (title == y.Text)
{
Assert.Fail("Found duplicate product in the table");
}
}
}
But this is taken the item I skip out of the array for the next loop so item 2 never checks it's the same as item 1, it moves straight to item 3.
I was under the impression that skip just passed over the index you pass in rather than removing it from the list.
You can use GroupBy:
var anyDuplicates = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.GroupBy(p => p.Text, p => p)
.Any(g => g.Count() > 1);
Assert.That(anyDuplicates, Is.False);
or Distinct:
var productTitles = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text)
.ToArray();
var distinctProductTitles = productTitles.Distinct().ToArray();
Assert.AreEqual(productTitles.Length, distinctProductTitles.Length);
Or, if it is enough to find a first duplicate without counting all of them it's better to use a HashSet<T>:
var titles = new HashSet<string>();
foreach (var title in SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text))
{
if (!titles.Add(title))
{
Assert.Fail("Found duplicate product in the table");
}
}
All approaches are better in terms of computational complexity (O(n)) than what you propose (O(n2)).
You don't need a loop. Simply use the Where() function to find all same titles, and if there is more than one, then they're duplicates:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach(var x in productTitles) {
if (productTitles.Where(y => x.Text == y.Text).Count() > 1) {
Assert.Fail("Found duplicate product in the table");
}
}
I would try a slightly different way since you only need to check for duplicates in a one-dimensional array.
You only have to check the previous element with the next element within the array/collection so using Linq to iterate through all of the items seems a bit unnecessary.
Here's a piece of code to better understand:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
for ( int i = 0; i < productionTitles.Length; i++ )
{
var currentObject = productionTitles[i];
for ( int j = i + 1; j < productionTitles.Length; j++ )
{
if ( currentObject.Title == productionTitles[j].Title )
{
// here's your duplicate
}
}
}
Since you've checked that item at index 0 is not the same as item placed at index 3 there's no need to check that again when you're at index 3. The items will remain the same.
The Skip(IEnumerable, n) method returns an IEnumerable that doesn't "contain" the n first element of the IEnumerable it's called on.
Also I don't know what sort of behaviour could arise from this, but I wouldn't assign a new IEnumerable to the variable over which the foreach is being executed.
Here's another possible solution with LINQ:
int i = 0;
foreach (var x in productTitles)
{
var possibleDuplicate = productTitles.Skip(i++).Find((y) => y.title == x.title);
//if possibleDuplicate is not default value of type
//do stuff here
}
This goes without saying, but the best solution for you will depend on what you are trying to do. Also, I think the Skip method call is more trouble than it's worth, as I'm pretty sure it will most certainly make the search less eficient.
This question already has answers here:
Interleaving multiple (more than 2) irregular lists using LINQ
(5 answers)
Closed 5 years ago.
Suppose I have list of list. I want to create new list from given list of list such that elements are in order of example given below.
Inputs:-
List<List<int>> l = new List<List<int>>();
List<int> a = new List<int>();
a.Add(1);
a.Add(2);
a.Add(3);
a.Add(4);
List<int> b = new List<int>();
b.Add(11);
b.Add(12);
b.Add(13);
b.Add(14);
b.Add(15);
b.Add(16);
b.Add(17);
b.Add(18);
l.Add(a);
l.Add(b);
Output(list):-
1
11
2
12
3
13
4
14
15
16
And output list must not contain more than 10 elements.
I am currently doing this using foreach inside while but I want to know how can I do this using LINQ.
int loopCounter = 0,index=0;
List<int> o=new List<int>();
while(o.Count<10)
{
foreach(List<int> x in l)
{
if(o.Count<10)
o.Add(x[index]);
}
index++;
}
Thanks.
Use the SelectMany and Select overloads that receive the item's index. That will be used to apply the desired ordering. The use of the SelectMany is to flatten the nested collections level. Last, apply Take to retrieve only the desired number of items:
var result = l.SelectMany((nested, index) =>
nested.Select((item, nestedIndex) => (index, nestedIndex, item)))
.OrderBy(i => i.nestedIndex)
.ThenBy(i => i.index)
.Select(i => i.item)
.Take(10);
Or in query syntax:
var result = (from c in l.Select((nestedCollection, index) => (nestedCollection, index))
from i in c.nestedCollection.Select((item, index) => (item, index))
orderby i.index, c.index
select i.item).Take(10);
If using a C# 6.0 and prior project an anonymous type instead:
var result = l.SelectMany((nested, index) =>
nested.Select((item, nestedIndex) => new {index, nestedIndex, item}))
.OrderBy(i => i.nestedIndex)
.ThenBy(i => i.index)
.Select(i => i.item)
.Take(10);
To explain why Zip alone is not enough: zip is equivalent to performing a join operation on the second collection to the first, where the
attribute to join by is the index. Therefore Only items that exist in the first collection, if they have a match in the second, will appear in the result.
The next option is to think about left join which will return all items of the first collection with a match (if exists) in the second. In the case described OP is looking for the functionality of a full outer join - get all items of both collection and match when possible.
I know you asked for LINQ, but I do often feel that LINQ is a hammer and as soon as a developer finds it, every problem is a nail. I wouldn't have done this one with LINQ, for a readability/maintainability point of view because I think something like this is simpler and easier to understand/more self documenting:
List<int> r = new List<int>(10);
for(int i = 0; i < 10; i++){
if(i < a.Count)
r.Add(a[i]);
if(i < b.Count)
r.Add(b[i]);
}
You don't need to stop the loop early if a and b collectively only have eg 8 items, but you could by extending the test of the for loop
I also think this case may be more performant than LINQ because it's doing a lot less
If your mandate to use LINQ is academic (this is a homework that must use LINQ) then go ahead, but if it's a normal everyday system that some other poor sucker will have to maintain one day, I implore you to consider whether this is a good application for LINQ
This will handle 2 or more internal List<List<int>>'s - it returns an IEnumerable<int> via yield so you have to call .ToList() on it to make it a list. Linq.Any is used for the break criteria.
Will throw on any list being null. Add checks to your liking.
static IEnumerable<int> FlattenZip (List<List<int>> ienum, int maxLength = int.MaxValue)
{
int done = 0;
int index = 0;
int yielded = 0;
while (yielded <= maxLength && ienum.Any (list => index < list.Count))
foreach (var l in ienum)
{
done++;
if (index < l.Count)
{
// this list is big enough, we will take one out
yielded++;
yield return l[index];
}
if (yielded > maxLength)
break; // we are done
if (done % (ienum.Count) == 0)
index += 1; // checked all lists, advancing index
}
}
public static void Main ()
{
// other testcases to consider:
// in total too few elememts
// one list empty (but not null)
// too many lists (11 for 10 elements)
var l1 = new List<int> { 1, 2, 3, 4 };
var l2 = new List<int> { 11, 12, 13, 14, 15, 16 };
var l3 = new List<int> { 21, 22, 23, 24, 25, 26 };
var l = new List<List<int>> { l1, l2, l3 };
var zipped = FlattenZip (l, 10);
Console.WriteLine (string.Join (", ", zipped));
Console.ReadLine ();
}
So I wrote this simple console app to aid in my question asking. What is the proper way to use a lambda expression on line 3 of the method to get the common members. Tried a Join() but couldn't figure out the correct syntax. As follow up... is there a non-LINQ way to do this in one line that I missed?
class Program
{
static void Main(string[] args)
{
List<int> c = new List<int>() { 1, 2, 3 };
List<int> a = new List<int>() { 5, 3, 2, 4 };
IEnumerable<int> j = c.Union<int>(a);
// just show me the Count
Console.Write(j.ToList<int>().Count.ToString());
}
}
You want Intersect():
IEnumerable<int> j = c.Intersect(a);
Here's an OrderedIntersect() example based on the ideas mentioned in the comments. If you know your sequences are ordered it should run faster — O(n) rather than whatever .Intersect() normally is (don't remember off the top of my head). But if you don't know they are ordered, it likely won't return correct results at all:
public static IEnumerable<T> OrderedIntersect<T>(this IEnumerable<T> source, IEnumerable<T> other) where T : IComparable
{
using (var xe = source.GetEnumerator())
using (var ye = other.GetEnumerator())
{
while (xe.MoveNext())
{
while (ye.MoveNext() && ye.Current.CompareTo(xe.Current) < 0 )
{
// do nothing - all we care here is that we advanced the y enumerator
}
if (ye.Current.Equals(xe.Current))
yield return xe.Current;
else
{ // y is now > x, so get x caught up again
while (xe.MoveNext() && xe.Current.CompareTo(ye.Current) < 0 )
{ } // again: just advance, do do anything
if (xe.Current.Equals(ye.Current)) yield return xe.Current;
}
}
}
}
If you by lambda syntax mean a real LINQ query, it looks like this:
IEnumerable<int> j =
from cItem in c
join aitem in a on cItem equals aItem
select aItem;
A lambda expression is when you use the => operator, like in:
IEnumerable<int> x = a.Select(y => y > 5);
What you have with the Union method really is a non-LINQ way of doing it, but I suppose that you mean a way of doing it without extension methods. There is hardly a one-liner for that. I did something similar using a Dictionary yesterday. You could do like this:
Dictaionary<int, bool> match = new Dictaionary<int, bool>();
foreach (int i in c) match.Add(i, false);
foreach (int i in a) {
if (match.ContainsKey(i)) {
match[i] = true;
}
}
List<int> result = new List<int>();
foreach (KeyValuePair<int,bool> pair in match) {
if (pair.Value) result.Add(pair.Key);
}