Selecting 3 identical items from list - c#

I need to find if there are 3 identical items in list.
It should compare elements using overridden .Equals() method. I've tried many ways and failed.
It doesn't matter if it returns bool value or items itself.
The function will be called every time after new item is added, so it does not matter how as long as it detects the point when 3 same items are in list.
This is probably something trivial, but my knowledge of Linq is very weak.

Try
return
collection.Any(any => collection.Count(item => item.Equals(any)) == 3);

By grouping items by itself and evaluating if any group contains exactly three items, you will receive expected result.
private bool ContainsTriple<T>(IList<T> items){
return items.GroupBy(i => i).Any(l => l.Count() == 3);
}

To express better my concept:
static class EnumerableExtensions
{
public static IEnumerable<T> FirstRepeatedTimes<T>(this IEnumerable<T> sequence, int threshold)
{
if (!sequence.Any())
throw new ArgumentException("Sequence must contain elements", "sequence");
if (threshold < 2)
throw new ArgumentException("DuplicateCount must be greater than 1", "threshold");
return FirstRepeatedTimesImpl(sequence, threshold);
}
static IEnumerable<T> FirstRepeatedTimesImpl<T>(this IEnumerable<T> sequence, int threshold)
{
var map = new Dictionary<T, int>();
foreach(var e in sequence)
{
if (!map.ContainsKey(e))
map.Add(e, 0);
if (map[e] + 1 == threshold)
{
yield return e;
yield break;
}
map[e] = map[e] + 1;
}
}
}
you would use it like this:
var list = new List<int>() { 1,2,2,3,4,3,3 };
// list contains anything for 3 times?
var found = list.FirstRepeatedTimes(3).Any();
It could potentially consume some more memory, but it enumerates the list at most once. Is this Linq? The way I wrote it, it yields exactly 1 element (the first found), or no element, and you can further compose on top of it if you want. You could use FirstOfDefault() instead of Any(), and have then the found element or 0 (or null if we deal with reference types). This way you have the choice.
It's just another way to see it.

static void Main(string[] args)
{
List<string> col = new List<string>();
col.Add("a");
Console.WriteLine(Has_3(col));
Console.ReadKey();
col.Add("a");
Console.WriteLine(Has_3(col));
Console.ReadKey();
col.Add("a");
Console.WriteLine(Has_3(col));
Console.ReadKey();
col.Add("a");
Console.WriteLine(Has_3(col));
Console.ReadKey();
}
static bool Has_3(List<string> col)
{
return col.Count(x => x == "a").Equals(3);
}

My first thought was that this could probably be done by using the Group() method, something like this:
var ints = new List<int>(new[] { 1, 2, 3, 4, 5, 6, 2, 2 });
var first = ints.GroupBy(n => n)
.Select(g => new { g.Key, Count = g.Count() })
.First(g => g.Count >= 3);
Console.WriteLine("Found {0} instances of {1}", first.Count, first.Key);
This snippet checks for 3 or more of the same item, and selects the first item that meets the criteria, you might want to change this. And adapt it to your specific objects instead of integers.

Here's an extension:
public static bool ContainsNTimes<T>(this IEnumerable<T> sequence, T element, int duplicateCount)
{
if (element == null)
throw new ArgumentNullException("element");
if (!sequence.Any())
throw new ArgumentException("Sequence must contain elements", "sequence");
if (duplicateCount < 1)
throw new ArgumentException("DuplicateCount must be greater 0", "duplicateCount");
bool containsNTimes = sequence.Where(i => i.Equals(element))
.Take(duplicateCount)
.Count() == duplicateCount;
return containsNTimes;
}
Usage:
var list = new List<int>() { 1,2,2,3,4,3,3 };
// list contains 2 for 3 times?
bool contains2ThreeTimes = list.ContainsNTimes(2, 3);
// any element in the list iscontained 3 times (or more)?
bool anyContains3Times = list.Any(i => list.ContainsNTimes(i, 3));
Console.WriteLine("List contains 2 for 3 times? " + contains2ThreeTimes); // false
Console.WriteLine("Any element in the list is contained 3 times (or more)? " + anyContains3Times); // true (3)
Demo: http://ideone.com/Ozk9v
Should be quite efficient since it uses deferred execution. It enumerates the sequences until n-items were found.

Related

Sublists of consecutive elements that fit a condition in a list c# linq

So suppose we have a parking(represented as a dictionary<int,bool> :
Every parking lot has its id and a boolean(free,filled).
This way:
Dictionary<int,bool> parking..
parking[0]= true // means that the first parking lot is free
My question is i want to get the all sublist of consecutive elements that matchs in a condition : parking-lot is free.
First i can get elements that fits in this condition easy:
parking.Where(X => X.Value).Select(x => x.Key).ToList();
But then using linq operations i dont know how to get the first generated list that matchs in.
Can i do this without thousand of foreach-while loops checking iterating one by one, is there a easier way with linq?
This method gets a list of consecutive free parking lots
data:
0-free,
1-free,
2-filled ,
3-free
The results will be two lists:
First One will contain => 0 ,1
Second One will contain=> 3
These are the list of consecutive of parking lots that are free.
public List<List<int>> ConsecutiveParkingLotFree(int numberOfConsecutive){}
You can always write your own helper function to do things like this. For example
public static IEnumerable<List<T>> GroupSequential<T, TKey>(
this IEnumerable<T> self,
Func<T, bool> condition)
{
var list = new List<T>();
using var enumerator = self.GetEnumerator();
if (enumerator.MoveNext())
{
var current = enumerator.Current;
var oldValue = condition(current);
if (oldValue)
{
list.Add(current);
}
while (enumerator.MoveNext())
{
current = enumerator.Current;
var newValue = condition(current);
if (newValue)
{
list.Add(current);
}
else if (oldValue)
{
yield return list;
list = new List<T>();
}
oldValue = newValue;
}
if (list.Count > 0)
{
yield return list;
}
}
}
This will put all the items with a true-value in a list. When a true->false transition is encountered the list is returned and recreated. I would expect that there are more compact ways to write functions like this, but it should do the job.
You can apply GroupWhile solution here.
parking.Where(X => X.Value)
.Select(x => x.Key)
.GroupWhile((x, y) => y - x == 1)
.ToList()

How do I sort a list based on a given sequence of one field?

I have a list read from database, something like:
var managers = _repo.Where(xxxx).ToList();
data of managers displays in json is
[
{
"id":"b2071b2d-3d1a-4403-b044-0a59514c4431",
"name":"Lucy",
"mobile":"xxx"
},
{
"id":"639c108f-ec00-4fcd-814a-c3859930b1c1",
"name":"Franck",
"mobile":"xxx2"
},
{
"id":"943b2ad4-8ef0-4cf7-824e-de3a7837a1cd",
"name":"Jerry",
"mobile":"xxx3"
}
]
Now I need to reorder this list by a given name sequence, for example:
var orderByName = _svc.GetManagerSortOrder(); // ["Jerry","Lucy","Franck"]
Right now I'm using foreach to do this, it's too much to code. Is there any better way?
Here is my current solution:
var orderByName = new List<string>() { "Franck", "Jerry" };
var sortedManagers = new List<Manager>();
foreach(string orderName in orderByName) // loop, and sort manager list based on a given name list
{
var manager = managers.Where(a => a.name == orderName).FirstOrDefault();
if (manager == null)
continue;
sortedManagers.Add(manager);
}
if(sortedManagers.Count != managers.Count) // if counts not equal, there is someone not on the name list, so these men need to be appended to the sortedManagers list
{
var sortedManagersId = sortedManagers.Select(s => s.id);
var managersNotInclude = managers.Where(a => !sortedManagersId.Contains(a.id)).ToList();
sortedManagers = sortedManagers.Concat(managersNotInclude).ToList();
}
Unfortunately the following answer would help you, if it wasn't for handling names in your source list, that don't exist in your target list since IndexOf() returns -1 for undiscovered elements: Sort a list from another list IDs
What you need to do is write a customer Comparer that implements ICompare. This could look something like this:
class DependentComparer<T> : IComparer<T>
{
// Backing field to contain the order dependent list
List<T> lookUpTable;
public DependentComparer(List<T> lookUpTable)
{
this.lookUpTable = lookUpTable;
}
public int Compare(T x, T y)
{
// Determine the index of the compared elements in the dependent list
int xIndex = lookUpTable.IndexOf(x);
int yIndex = lookUpTable.IndexOf(y);
// If neither were in the dependent list, they are equal
if ((xIndex == -1) && (yIndex == -1)) return 0;
// If only the y was found, then y is greater than the x element
if (xIndex == -1) return 1;
// If only the x was found, then y is less than the x element
if (yIndex == -1) return -1;
// If both were found, then return the delta of their indicies
return xIndex - yIndex;
}
}
This can be used as:
var orderByName = new List<string>() { "Frank", "Jerry" };
var managersNames = new List<string>() { "Jerry", "Frank", "Lucy" };
managersNames.Sort(new DependentComparer<string>(orderByName));
Output: Frank, Jerry, Lucy
NOTES: This method will take no sorting action on the elements not pressent in the lookUpTable. They will be appended to the end of result in the order in which they were in the source. Also, the example is given as if the lists were of string, not manager. The conversion is easy enough, but let me know if you need an edit.
So you have a sequence of Managers where every Manager has a Name; and you have a sequence of Names, something like ["Jerry","Lucy","Franck"].
You want to order your Managers, such that you first get all Managers named "Jerry", then all Managers names "Lucy", etc. You want to end with managers that were not in your sequence of names.
Consider creating an object that implements IComparer<Manager>: get two Managers, and decides who should go first. For instance: one Manger is named "Lucy", the other one is named "Jerry", so "Jerry" should bo first.
Usage would be:
IEnumerable<Manager> unorderedManagers = ...
IComparer<Manager> managerComparer = ...
IEnumerable<Manager> orderedManagers = unorderedManagers.OrderBy(managerComparer);
So let's create a ManagerComparer class.
class ManagerComparer : IComparer<Manager>
{
public ManagerComparer(IEnumerable<string> names)
{
}
public int Compare(Manager x, Manager y) {...}
// TODO implement
}
If you have two managers as input: x and y, you want the manger with "Jerry" first. If there is no "Jerry" you want "Lucy" first, etc.
To make this efficient, we put all Names in a dictionary: Key is the name, Value is the index. So "Jerry" has index 0, "Lucy" index 1, etc
You get Manager X and Y: get their Names, get the index from the Dictionary. The one with the lowest index comes first.
Sounds like a good idea? Let's do it:
private readonly IDictionary<string, int> managerNames;
public ManagerComparer(IEnumerable<string> names)
{
// TODO: what to do if no names?
this.managerNames = names.Select( (name, i) => new
{
Name = name,
Index = i,
})
.ToDictionary(item => item.Name, item => item.Index);
}
The Compare method.
We know what to do when you get two Managers. Comparer the indexes of the names in the Dictionary. Return either -1 / 0 / +1, depending on the value. This can be done with if/then/else, it is easier to use the existing IComparer<int>:
private static readonly IComparer<int> indexComparer = Comparer<int>.Default;
public int Compare(Manager x, Manager y)
{
// TODO: decide what to return if x or y null: first or last in your result
if (x == null)
{
if (y == null)
return 0;
else
return ... // -1 or +1;
}
else if (y == null)
return ... // -1 or +1;
// if here, both x and y not null
string nameX = x.Name;
string nameY = y.Name;
int indexX = this.managerNames[nameX];
int indexY = this.managerNames[nameY];
// the one with the lowest index comes first:
return indexComparer.Compare(indexX, indexY);
}
Again usage:
IEnumerable<Manager> unorderedManagers = ...
IEnumerable<string> orderByNames = new sting[] {"Jerry", "Lucy", ...}
IComparer<Manager> managerComparer = new ManagerComparer(orderByNames);
IEnumerable<Manager> orderedManagers = unorderedManagers.OrderBy(managerComparer);
A very concise and fast method is offered by Array.Sort that sorts an array according to the order of a given array containing the prescribed order.
var managers = JsonConvert.DeserializeObject<IEnumerable<Manager>>(json).ToArray();
var order = new List<string> { "Jerry", "Lucy", "Franck" };
var indexes = managers
.Select(m => order.IndexOf(m.Name))
.Select(i => i == -1 ? int.MaxValue : i)
.ToArray();
Array.Sort(indexes, managers);
The array indexes looks something like [1,2147483647,2,2147483647,0] when managers contains 5 names with the given names at positions 1, 3, and 5 (Jerry at 5, getting index 0, etc.).
Array.Sort "lines up" both arrays and orders the lineup by the the values in indexes. The result is managers, sorted according to the prescribed order. Note that the physical order of elements in managers is persistently changed.

Comparing two list [duplicate]

This question already has answers here:
Comparing two collections for equality irrespective of the order of items in them
(21 answers)
Closed 5 years ago.
Yet another list-comparing question.
List<MyType> list1;
List<MyType> list2;
I need to check that they both have the same elements, regardless of their position within the list. Each MyType object may appear multiple times on a list. Is there a built-in function that checks this? What if I guarantee that each element appears only once in a list?
EDIT: Guys thanks for the answers but I forgot to add something, the number of occurrences of each element should be the same on both lists.
If you want them to be really equal (i.e. the same items and the same number of each item), I think that the simplest solution is to sort before comparing:
Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t))
Edit:
Here is a solution that performs a bit better (about ten times faster), and only requires IEquatable, not IComparable:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
var cnt = new Dictionary<T, int>();
foreach (T s in list1) {
if (cnt.ContainsKey(s)) {
cnt[s]++;
} else {
cnt.Add(s, 1);
}
}
foreach (T s in list2) {
if (cnt.ContainsKey(s)) {
cnt[s]--;
} else {
return false;
}
}
return cnt.Values.All(c => c == 0);
}
Edit 2:
To handle any data type as key (for example nullable types as Frank Tzanabetis pointed out), you can make a version that takes a comparer for the dictionary:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
var cnt = new Dictionary<T, int>(comparer);
...
If you don't care about the number of occurrences, I would approach it like this. Using hash sets will give you better performance than simple iteration.
var set1 = new HashSet<MyType>(list1);
var set2 = new HashSet<MyType>(list2);
return set1.SetEquals(set2);
This will require that you have overridden .GetHashCode() and implemented IEquatable<MyType> on MyType.
As written, this question is ambigous. The statement:
... they both have the same elements, regardless of their position within the list.
Each MyType object may appear multiple times on a list.
does not indicate whether you want to ensure that the two lists have the same set of objects or the same distinct set.
If you want to ensure to collections have exactly the same set of members regardless of order, you can use:
// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();
If you want to ensure two collections have the same distinct set of members (where duplicates in either are ignored), you can use:
// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();
Using the set operations (Intersect, Union, Except) is more efficient than using methods like Contains. In my opinion, it also better expresses the expectations of your query.
EDIT: Now that you've clarified your question, I can say that you want to use the first form - since duplicates matter. Here's a simple example to demonstrate that you get the result you want:
var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };
// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any();
In addition to Guffa's answer, you could use this variant to have a more shorthanded notation.
public static bool ScrambledEquals<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
var deletedItems = list1.Except(list2).Any();
var newItems = list2.Except(list1).Any();
return !newItems && !deletedItems;
}
Thinking this should do what you want:
list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));
if you want it to be distinct, you could change it to:
list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count
This is a slightly difficult problem, which I think reduces to: "Test if two lists are permutations of each other."
I believe the solutions provided by others only indicate whether the 2 lists contain the same unique elements. This is a necessary but insufficient test, for example
{1, 1, 2, 3} is not a permutation of {3, 3, 1, 2}
although their counts are equal and they contain the same distinct elements.
I believe this should work though, although it's not the most efficient:
static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
if(list1.Count != list2.Count)
return false;
var l1 = list1.ToLookup(t => t);
var l2 = list2.ToLookup(t => t);
return l1.Count == l2.Count
&& l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count());
}
This worked for me:
If you are comparing two lists of objects depend upon single entity like ID, and you want a third list which matches that condition, then you can do the following:
var list3 = List1.Where(n => !List2.select(n1 => n1.Id).Contains(n.Id));
Refer: MSDN - C# Compare Two lists of objects
I use this method )
public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);
public static bool CompareTwoArrays<T1, T2>(this IEnumerable<T1> array1, IEnumerable<T2> array2, CompareValue<T1, T2> compareValue)
{
return array1.Select(item1 => array2.Any(item2 => compareValue(item1, item2))).All(search => search)
&& array2.Select(item2 => array1.Any(item1 => compareValue(item1, item2))).All(search => search);
}
try this!!!
using following code you could compare one or many fields to generate a result list as per your need. result list will contain only modified item(s).
// veriables been used
List<T> diffList = new List<T>();
List<T> gotResultList = new List<T>();
// compare First field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1))).ToList();
// Generate result list
diffList.AddRange(gotResultList);
// compare Second field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2))).ToList();
// Generate result list
diffList.AddRange(gotResultList);
MessageBox.Show(diffList.Count.ToString);

Compare two List<T> objects for equality, ignoring order [duplicate]

This question already has answers here:
Comparing two collections for equality irrespective of the order of items in them
(21 answers)
Closed 5 years ago.
Yet another list-comparing question.
List<MyType> list1;
List<MyType> list2;
I need to check that they both have the same elements, regardless of their position within the list. Each MyType object may appear multiple times on a list. Is there a built-in function that checks this? What if I guarantee that each element appears only once in a list?
EDIT: Guys thanks for the answers but I forgot to add something, the number of occurrences of each element should be the same on both lists.
If you want them to be really equal (i.e. the same items and the same number of each item), I think that the simplest solution is to sort before comparing:
Enumerable.SequenceEqual(list1.OrderBy(t => t), list2.OrderBy(t => t))
Edit:
Here is a solution that performs a bit better (about ten times faster), and only requires IEquatable, not IComparable:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2) {
var cnt = new Dictionary<T, int>();
foreach (T s in list1) {
if (cnt.ContainsKey(s)) {
cnt[s]++;
} else {
cnt.Add(s, 1);
}
}
foreach (T s in list2) {
if (cnt.ContainsKey(s)) {
cnt[s]--;
} else {
return false;
}
}
return cnt.Values.All(c => c == 0);
}
Edit 2:
To handle any data type as key (for example nullable types as Frank Tzanabetis pointed out), you can make a version that takes a comparer for the dictionary:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2, IEqualityComparer<T> comparer) {
var cnt = new Dictionary<T, int>(comparer);
...
If you don't care about the number of occurrences, I would approach it like this. Using hash sets will give you better performance than simple iteration.
var set1 = new HashSet<MyType>(list1);
var set2 = new HashSet<MyType>(list2);
return set1.SetEquals(set2);
This will require that you have overridden .GetHashCode() and implemented IEquatable<MyType> on MyType.
As written, this question is ambigous. The statement:
... they both have the same elements, regardless of their position within the list.
Each MyType object may appear multiple times on a list.
does not indicate whether you want to ensure that the two lists have the same set of objects or the same distinct set.
If you want to ensure to collections have exactly the same set of members regardless of order, you can use:
// lists should have same count of items, and set difference must be empty
var areEquivalent = (list1.Count == list2.Count) && !list1.Except(list2).Any();
If you want to ensure two collections have the same distinct set of members (where duplicates in either are ignored), you can use:
// check that [(A-B) Union (B-A)] is empty
var areEquivalent = !list1.Except(list2).Union( list2.Except(list1) ).Any();
Using the set operations (Intersect, Union, Except) is more efficient than using methods like Contains. In my opinion, it also better expresses the expectations of your query.
EDIT: Now that you've clarified your question, I can say that you want to use the first form - since duplicates matter. Here's a simple example to demonstrate that you get the result you want:
var a = new[] {1, 2, 3, 4, 4, 3, 1, 1, 2};
var b = new[] { 4, 3, 2, 3, 1, 1, 1, 4, 2 };
// result below should be true, since the two sets are equivalent...
var areEquivalent = (a.Count() == b.Count()) && !a.Except(b).Any();
In addition to Guffa's answer, you could use this variant to have a more shorthanded notation.
public static bool ScrambledEquals<T>(this IEnumerable<T> list1, IEnumerable<T> list2)
{
var deletedItems = list1.Except(list2).Any();
var newItems = list2.Except(list1).Any();
return !newItems && !deletedItems;
}
Thinking this should do what you want:
list1.All(item => list2.Contains(item)) &&
list2.All(item => list1.Contains(item));
if you want it to be distinct, you could change it to:
list1.All(item => list2.Contains(item)) &&
list1.Distinct().Count() == list1.Count &&
list1.Count == list2.Count
This is a slightly difficult problem, which I think reduces to: "Test if two lists are permutations of each other."
I believe the solutions provided by others only indicate whether the 2 lists contain the same unique elements. This is a necessary but insufficient test, for example
{1, 1, 2, 3} is not a permutation of {3, 3, 1, 2}
although their counts are equal and they contain the same distinct elements.
I believe this should work though, although it's not the most efficient:
static bool ArePermutations<T>(IList<T> list1, IList<T> list2)
{
if(list1.Count != list2.Count)
return false;
var l1 = list1.ToLookup(t => t);
var l2 = list2.ToLookup(t => t);
return l1.Count == l2.Count
&& l1.All(group => l2.Contains(group.Key) && l2[group.Key].Count() == group.Count());
}
This worked for me:
If you are comparing two lists of objects depend upon single entity like ID, and you want a third list which matches that condition, then you can do the following:
var list3 = List1.Where(n => !List2.select(n1 => n1.Id).Contains(n.Id));
Refer: MSDN - C# Compare Two lists of objects
I use this method )
public delegate bool CompareValue<in T1, in T2>(T1 val1, T2 val2);
public static bool CompareTwoArrays<T1, T2>(this IEnumerable<T1> array1, IEnumerable<T2> array2, CompareValue<T1, T2> compareValue)
{
return array1.Select(item1 => array2.Any(item2 => compareValue(item1, item2))).All(search => search)
&& array2.Select(item2 => array1.Any(item1 => compareValue(item1, item2))).All(search => search);
}
try this!!!
using following code you could compare one or many fields to generate a result list as per your need. result list will contain only modified item(s).
// veriables been used
List<T> diffList = new List<T>();
List<T> gotResultList = new List<T>();
// compare First field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField1 == a.MyListTField1))).ToList();
// Generate result list
diffList.AddRange(gotResultList);
// compare Second field within my MyList
gotResultList = MyList1.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2)).ToList().Except(gotResultList.Where(a => !MyList2.Any(a1 => a1.MyListTField2 == a.MyListTField2))).ToList();
// Generate result list
diffList.AddRange(gotResultList);
MessageBox.Show(diffList.Count.ToString);

How to perform .Max() on a property of all objects in a collection and return the object with maximum value [duplicate]

This question already has answers here:
How to use LINQ to select object with minimum or maximum property value
(20 answers)
Closed 7 years ago.
I have a list of objects that have two int properties. The list is the output of another linq query. The object:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
I want to find and return the object in the list which has the largest Height property value.
I can manage to get the highest value of the Height value but not the object itself.
Can I do this with Linq? How?
We have an extension method to do exactly this in MoreLINQ. You can look at the implementation there, but basically it's a case of iterating through the data, remembering the maximum element we've seen so far and the maximum value it produced under the projection.
In your case you'd do something like:
var item = items.MaxBy(x => x.Height);
This is better (IMO) than any of the solutions presented here other than Mehrdad's second solution (which is basically the same as MaxBy):
It's O(n) unlike the previous accepted answer which finds the maximum value on every iteration (making it O(n^2))
The ordering solution is O(n log n)
Taking the Max value and then finding the first element with that value is O(n), but iterates over the sequence twice. Where possible, you should use LINQ in a single-pass fashion.
It's a lot simpler to read and understand than the aggregate version, and only evaluates the projection once per element
This would require a sort (O(n log n)) but is very simple and flexible. Another advantage is being able to use it with LINQ to SQL:
var maxObject = list.OrderByDescending(item => item.Height).First();
Note that this has the advantage of enumerating the list sequence just once. While it might not matter if list is a List<T> that doesn't change in the meantime, it could matter for arbitrary IEnumerable<T> objects. Nothing guarantees that the sequence doesn't change in different enumerations so methods that are doing it multiple times can be dangerous (and inefficient, depending on the nature of the sequence). However, it's still a less than ideal solution for large sequences. I suggest writing your own MaxObject extension manually if you have a large set of items to be able to do it in one pass without sorting and other stuff whatsoever (O(n)):
static class EnumerableExtensions {
public static T MaxObject<T,U>(this IEnumerable<T> source, Func<T,U> selector)
where U : IComparable<U> {
if (source == null) throw new ArgumentNullException("source");
bool first = true;
T maxObj = default(T);
U maxKey = default(U);
foreach (var item in source) {
if (first) {
maxObj = item;
maxKey = selector(maxObj);
first = false;
} else {
U currentKey = selector(item);
if (currentKey.CompareTo(maxKey) > 0) {
maxKey = currentKey;
maxObj = item;
}
}
}
if (first) throw new InvalidOperationException("Sequence is empty.");
return maxObj;
}
}
and use it with:
var maxObject = list.MaxObject(item => item.Height);
Doing an ordering and then selecting the first item is wasting a lot of time ordering the items after the first one. You don't care about the order of those.
Instead you can use the aggregate function to select the best item based on what you're looking for.
var maxHeight = dimensions
.Aggregate((agg, next) =>
next.Height > agg.Height ? next : agg);
var maxHeightAndWidth = dimensions
.Aggregate((agg, next) =>
next.Height >= agg.Height && next.Width >= agg.Width ? next: agg);
And why don't you try with this ??? :
var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));
OR more optimise :
var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);
mmm ?
The answers so far are great! But I see a need for a solution with the following constraints:
Plain, concise LINQ;
O(n) complexity;
Do not evaluate the property more than once per element.
Here it is:
public static T MaxBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
.Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1;
}
public static T MinBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
.Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1;
}
Usage:
IEnumerable<Tuple<string, int>> list = new[] {
new Tuple<string, int>("other", 2),
new Tuple<string, int>("max", 4),
new Tuple<string, int>("min", 1),
new Tuple<string, int>("other", 3),
};
Tuple<string, int> min = list.MinBy(x => x.Item2); // "min", 1
Tuple<string, int> max = list.MaxBy(x => x.Item2); // "max", 4
I believe that sorting by the column you want to get the MAX of and then grabbing the first should work. However, if there are multiple objects with the same MAX value, only one will be grabbed:
private void Test()
{
test v1 = new test();
v1.Id = 12;
test v2 = new test();
v2.Id = 12;
test v3 = new test();
v3.Id = 12;
List<test> arr = new List<test>();
arr.Add(v1);
arr.Add(v2);
arr.Add(v3);
test max = arr.OrderByDescending(t => t.Id).First();
}
class test
{
public int Id { get; set; }
}
In NHibernate (with NHibernate.Linq) you could do it as follows:
return session.Query<T>()
.Single(a => a.Filter == filter &&
a.Id == session.Query<T>()
.Where(a2 => a2.Filter == filter)
.Max(a2 => a2.Id));
Which will generate SQL like follows:
select *
from TableName foo
where foo.Filter = 'Filter On String'
and foo.Id = (select cast(max(bar.RowVersion) as INT)
from TableName bar
where bar.Name = 'Filter On String')
Which seems pretty efficient to me.
Based on Cameron's initial answer, here is what I've just added at my enhanced version of SilverFlow library's FloatingWindowHost (copying from FloatingWindowHost.cs at http://clipflair.codeplex.com source code)
public int MaxZIndex
{
get {
return FloatingWindows.Aggregate(-1, (maxZIndex, window) => {
int w = Canvas.GetZIndex(window);
return (w > maxZIndex) ? w : maxZIndex;
});
}
}
private void SetTopmost(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
Canvas.SetZIndex(element, MaxZIndex + 1);
}
Worth noting regarding the code above that Canvas.ZIndex is an attached property available for UIElements in various containers, not just used when being hosted in a Canvas (see Controlling rendering order (ZOrder) in Silverlight without using the Canvas control). Guess one could even make a SetTopmost and SetBottomMost static extension method for UIElement easily by adapting this code.
You can also upgrade Mehrdad Afshari's solution by rewriting the extention method to faster (and better looking) one:
static class EnumerableExtensions
{
public static T MaxElement<T, R>(this IEnumerable<T> container, Func<T, R> valuingFoo) where R : IComparable
{
var enumerator = container.GetEnumerator();
if (!enumerator.MoveNext())
throw new ArgumentException("Container is empty!");
var maxElem = enumerator.Current;
var maxVal = valuingFoo(maxElem);
while (enumerator.MoveNext())
{
var currVal = valuingFoo(enumerator.Current);
if (currVal.CompareTo(maxVal) > 0)
{
maxVal = currVal;
maxElem = enumerator.Current;
}
}
return maxElem;
}
}
And then just use it:
var maxObject = list.MaxElement(item => item.Height);
That name will be clear to people using C++ (because there is std::max_element in there).

Categories

Resources