How to get biggest element in HashSet of object by field? - c#

Suppose I have the class
public class Point {
public float x, y, z;
}
And I've created this hashset:
HashSet<Point> H;
How can I get the element of H with the biggest z? It doesn't need necessarily to use Linq.

You can use Aggregate to mimic MaxBy functionality (note that you need to check if collection has any elements first):
var maxByZ = H.Aggregate((point, point1) => point.z > point1.z ? point : point1);
When .NET 6 is out it should have built in MaxBy.

You could do this:
int maxZ = H.Max(point => point.Z);
var maxPointByZ = H.Where(point => point.Z == maxZ).FirstOrDefault();
This works by first retrieving the largest value of Z in the set:
H.Max(point2 => point2.Z) //Returns the largest value of Z in the set
And then by doing a simple where statement to get the record where Z is equal to that value. If there are multiple values, it will get the first one, so you may want to sort the enumerable in advance.

Related

When running a method that uses linq to get the palindromes from a string, why does SelectMany order the resulting strings?

So, i tried making a method that resulted in all the possible palindromes from a string, using linq. This is what the method is like:`
public IEnumerable<string> MakingPalindromesFromAString(string source)
{
return Enumerable
.Range(1, source.Length)
.SelectMany(length => Enumerable.Range(0, source.Length - length + 1)
.Select(a => source.Substring(a, length)))
.Where(b => b.SequenceEqual(b.Reverse()))
.ToArray();
}
Now, what I don't understand is why (or how) does SelectMany order the Enum? Because, for this example:
"xxyxxz"
the output (from how i tried to run the method by hand) should have been this:
z xxyxx xx x xyx x y xx x x
but instead it's this:
x x y x x z xx xx xyx xxyxx
Does anyone have any idea why this happens?
The first value of the range will be "1".
So when you will call SelectMany the first value of length will be "1".
The first value of the second range will be "0".
So the select after that will make a substring from 0 to 1.
And since your string is "xxyxxz" it seems coherent your first result is "x", isn't it ?
The SelectMany flattens (merges) the arrays to one array.
The result is expected, as first you create an array with words that have 1 character (see first Range()), and you start by grabbing characters from the beginning of the string (see seconds Range). Then you go on with words with 2 characters, etc...
SelectMany() merges all those arrays in the order they have been created.
"xxyxxz"
[x, x, y, x, x, z]
[xx, xx]
[xyx]
[xxyxx]
=> SelectMany() produces:
[x, x, y, x, x, z, xx, xx, xyx, xxyxx]

Alternatives to nested Select in Linq

Working on a clustering project, I stumbled upon this, and I'm trying to figure out if there's a better solution than the one I've come up with.
PROBLEM : Given a List<Point> Points of points in R^n ( you can think at every Point as a double array fo dimension n), a double minDistance and a distance Func<Point,Point,double> dist , write a LINQ expression that returns, for each point, the set of other points in the list that are closer to him than minDistance according to dist.
My solution is the following:
var lst = Points.Select(
x => Points.Where(z => dist(x, z) < minDistance)
.ToList() )
.ToList();
So, after noticing that
Using LINQ is probably not the best idea, because you get to calculate every distance twice
The problem doesn't have much practical use
My code, even if bad looking, works
I have the following questions:
Is it possible to translate my code in query expression? and if so, how?
Is there a better way to solve this in dot notation?
The problem definition, that you want "for each point, the set of other points" makes it impossible to solve without the inner query - you could just disguise it in clever manner. If you could change your data storage policy, and don't stick to LINQ then, in general, there are many approaches to Nearest Neighbour Search problem. You could for example hold the points sorted according to their values on one axis, which can speed-up the queries for neighbours by eliminating early some candidates without full distance calculation. Here is the paper with this approach: Flexible Metric Nearest Neighbor Classification.
Because Points is a List you can take advantage of the fact that you can access each item by its index. So you can avoid comparing each item twice with something like this:
var lst =
from i in Enumerable.Range(0, Points.Length)
from j in Enumerable.Range(i + 1, Points.Length - i - 1)
where dist(Points[i], Points[j]) < minDistance
select new
{
x = Points[i], y = Points[j]
};
This will return a set composed of all points within minDistance of each other, but not exactly what the result you wanted. If you want to turn it into some kind of Lookup so you can see which points are close to a given point you can do this:
var lst =
(from i in Enumerable.Range(0, Points.Length)
from j in Enumerable.Range(i + 1, Points.Length - i - 1)
where dist(Points[i], Points[j]) < minDistance
select new { x = Points[i], y = Points[j] })
.SelectMany(pair => new[] { pair, { x = pair.y, y = pair.x })
.ToLookup(pair => pair.x, pair => pair.y);
I think you could add some bool Property to your Point class to mark it's has been browsed to prevent twice calling to dist, something like this:
public class Point {
//....
public bool IsBrowsed {get;set;}
}
var lst = Points.Select(
x => {
var list = Points.Where(z =>!z.IsBrowsed&&dist(x, z) < minDistance).ToList();
x.IsBrowsed = true;
return list;
})
.ToList();

Linq data type comparison with Double

In my Linq query I have a where statement that looks like this
&& vio.Bows.Any(nw => nw.XCoordinate.Equals(currVio.XCoordinate)))
values are
nw.XCoordinate = 4056.48751252685
currVio.XCoordinate = 4056.488
Thus the statement of Equals is not working, what is the easiest way to round?
public double XCoordinate { get; set; }
You can use the usual way of comparing double for proximity by calculating the absolute difference, and comparing it to a small value:
Math.Abs(x - y) < 1E-8 // 1E-8 is 0.00000001
For example, you can use this approach in a LINQ query like this:
&& vio.Bows.Any(nw => Math.Abs(nw.XCoordinate-currVio.XCoordinate) < 0.001)
You could also use Math.Round i.e.:
double x = 4056.48751252685;
var newx = Math.Round(x,3);
So if you knew you always wanted 3 decimal places you could:
&& vio.Bows.Any(nw => Math.Round(nw.XCoordinate,3).Equals(math.Round(currVio.XCoordinate,3))))
You could go further and implement IEquetable, and override the equals function, determine which of the two values has the least number of decimal places, round both to that value and compare.

Convert Sum to an Aggregate product expression

I have this expression:
group i by i.ItemId into g
select new
{
Id = g.Key,
Score = g.Sum(i => i.Score)
}).ToDictionary(o => o.Id, o => o.Score);
and instead of g.Sum I'd like to get the mathematical product using Aggregate.
To make sure it worked the same as .Sum (but as product) I tried make an Aggregate function that would just return the sum...
Score = g.Aggregate(0.0, (sum, nextItem) => sum + nextItem.Score.Value)
However, this does not give the same result as using .Sum. Any idas why?
nextItem.Score is of type double?.
public static class MyExtensions
{
public static double Product(this IEnumerable<double?> enumerable)
{
return enumerable
.Aggregate(1.0, (accumulator, current) => accumulator * current.Value);
}
}
The thing is that in your example you are starting the multiplication with 0.0 - A multiplication with zero yields zero, at the end the result will be zero.
Correct is to use the identity property of multiplication. While adding zero to a number leaves the number of unchanged, the same property holds true for a multiplication with 1. Hence, the correct way to start a product aggregate is to kick off multiplication wit the number 1.0.
If you aren't sure about initial value in your aggregate query and you don't acutally need one (like in this example) I would recommend you not to use it at all.
You can use Aggregate overload which doesn't take the initial value - http://msdn.microsoft.com/en-us/library/bb549218.aspx
Like this
int product = sequence.Aggregate((x, acc) => x * acc);
Which evaluates to item1 * (item2 * (item3 * ... * itemN)).
instead of
int product = sequence.Aggregate(1.0, (x, acc) => x * acc);
Which evaluates to 1.0 * (item1 * (item2 * (item3 * ... * itemN))).
//edit:
There is one important difference though. Former one does throw an InvalidOperationException when the input sequence is empty. Latter one returns seed value, therefore 1.0.

How to sort a list of 2D points using C#?

Sort a list of points in descending order according to X and then Y.
list.Sort((a,b)=>{
int result = a.X.CompareTo(b.X);
if(result==0) result = a.Y.CompareTo(b.Y);
return result;
});
An alternative way to Marc GravellĀ“s answer (which will sort the list itself) where you get an IEnumerable<T> which can be made a list with .ToList() is the LINQ syntax:
var ordered = from v in yourList
orderby v.X, v.Y
select v;
var orderedList = ordered.ToList();
But unless you don't want to actually sort the list itself or you only have, let's say an IEnumerable, List.Sort would be better.
List<Point> sortedList = MyList.Sort(
delegate(Point p1, Point p2)
{
int r = p1.x.CompareTo(p2.x);
if(r.Equals(0)) return p1.y.CompareTo(p2.y);
else return r;
}
);
If the commenters are indeed correct, and you're searching for a solution to a homework problem, I suspect the real assignment is teaching you how to sort integer values. So I'll just help get you started.
Hint: The Point structure has two properties that you may find useful, X and Y, which return the coordinate values for those two axes, respectively.
There are many methods to sort a list.
For example, to sort your list from smallest (x, y) to biggest you can try this algorithm:
Compare the first item in the list with the second
If the second point is smaller than the first (x1 > x2 || (x1 == x2 && y1 > y2)) then swap them around
Compare the second point with the third in the same way, and so on until you get to the end of the list
Go back to the beginning of the list and run the comparisons again until the last but one element
Repeat step 4 but stopping one element earlier each time until you've got no elements left to sort
This is an inefficient algorithm, but it will get the job done.
For better algorithms, have a look at http://www.sorting-algorithms.com/

Categories