-
Notifications
You must be signed in to change notification settings - Fork 417
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IsHomogeneous: test all elements of a sequence for equality #698
Comments
The overloads can be: (bool? isHomogeneous, TSource value) IsHomogeneous<TSource>(this IEnumerable<TSource> source);
(bool? isHomogeneous, TSource value) IsHomogeneous<TSource>(this IEnumerable<TSource> source, IEqualitityComparer<TSource> comparer);
(bool? isHomogeneous, TResult value) IsHomogeneous<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
(bool? isHomogeneous, TResult value) IsHomogeneous<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector, IEqualitityComparer<TResult> comparer); |
An implementation of MoreLINQ/MoreLinq/EquiInterleave.cs Lines 94 to 110 in 365adcc
|
@atifaziz, it is |
Fair point but then thinking |
Second thoughts? |
Yep, distinc + TrySingle doesn't cost that much. I'm ok with any name proposition. |
Here an example of use (an overload with selector predicate may be more adapted here): MoreLINQ/MoreLinq/EquiInterleave.cs Lines 73 to 86 in 365adcc
|
It doesn't cost if it's homogeneous but could otherwise.
First the signature then the name. Instead of asking a question or returning a Boolean, return a sequence of one if source is homogenous otherwise abort early and return an empty sequence (
Sorry, I don't understand. |
If the sequence is empty my proposal return null |
Fair point if you are after reduction without an error for an undefined state but then public static TResult Homogeneity<T, TResult>(
this IEnumerable<T> source,
TResult ambiguous, TResult heterogeneous, Func<T, TResult> homogeneous) |
Indeed it's a 3 state. And this looks like the discussion about TrySingle. The 3 new parameters really add complexity, in my use case example it's obvious: var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous(((bool?)null, default), (false, default), v => (true, v)); And it's source of bug since we can mess up with the two first parameters. That's why I prefer a new class or struct as return type with a 3 state enum and the homogeneous value if any. class HomogeneityResult<T>
{
public Homogeneity Homogeneity { get; } // empty, homogeneous, heterogeneous
public T Value { get; }
} Or with bool properties: class HomogeneityResult<T>
{
public bool HasValue { get; }
public bool IsHomogeneous { get; }
public T Value { get; }
} |
An other option is (optional via overloads) out parameters: bool IsHomogeneous<T>(this IEnumerable<T> source, out bool hasValue, out T value); But with the overloads to add a selector and a comparer it became a mess. |
Yeah and now I am not happy about
The complexity comes from the choice of types.
This is not always avoidable.
This shouldn't be MoreLINQ's problem. var (isHomogeneous, hasNext) = enumerators.Select(e => e.MoveNext()).IsHomogeneous(); This use case is not worth the trouble because it abuses LINQ for side-effects besides causing too many allocations. |
Let me expand on this. Suppose you introduce this in your project: public enum TriBoolean { TriDefault, TriFalse, TriTrue } then you can make it fairly readable: // assume:
// using static TriBoolean;
var moves = from e in enumerators select e.MoveNext();
for (var stop = false; !stop;)
{
switch (moves.Homogeneity(default, (TriFalse, default), moved => (TriTrue, moved)))
{
case (TriFalse, _):
throw new InvalidOperationException("Input sequences are of different length.");
case (TriTrue, true):
foreach (var enumerator in enumerators)
yield return enumerator.Current;
break;
default:
stop = true;
break;
}
} Note that projection of moves can go outside the loop to save allocations.
To expand on this too, if you provide a type, it will use generic names and won't make things necessarily more readable in the context of the actual operation. If you leave the choice up to the user then only the user is to blame or cheer for readability. |
I'm not agree with you, I need a type that can store any case (IsEmpty, IsHeteregoneous, IsHomogeneous with value T). I can't see something smaller than a tuple with a 3 state (here
Again not agree, this use case is pretty straightforward, I have an I abuse linq since the enumeration stop early if an Heteregoneous case appear.
Should I go down to a
I am not happy about TrySingle neither. My analysis: there is a separation in the Linq method. 1 The monad / fluent ones:
For There is already complex object returned in Linq like
For me your proposed solution for An imperative form is: |
With a custom de-constructible
And add a
Example below show that the generic name rarely appear in the code and are pretty readable.
Good catch, I never do that since Resharper will grumble about multiple enumeration. |
Does this PR is similar to this other one? |
For the 'all items equal' part yes, but this proposed method also return the actual value (in the case of an homogeneous enumerable). |
I propose for:
DefaultEqualitityComparer<T>
is used ifcomparer
is null.The return tuple contains:
isHomogeneous
:null
if the source is emptytrue
if the source has one element or if all the elements in the source are equals to the first element relatively tocomparer
false
otherwise.value
: the first value of the source ifisHomogeneous
is true,default(T)
otherwise.I'm open to give this method any other relevant name.
The text was updated successfully, but these errors were encountered: