The "simple" idiomatic C# approach (in my opinion)
Instead of restricting your method to work on exactly 3 elements you could think of the 3 elements as a sequence of values, to start with an array of ints.
int[] values = new int[] {1, 2, 3};
Then you have to compare each element to the next one:
public static bool InOrderEqual(int[] values, bool equalOk) { //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { if (equalOk) { /* * value <= nextValue implies that if * value > nextValue the condition is not met. */ if (values[i].CompareTo(values[i + 1]) > 0) { return false; } } else { /* * value < nextValue implies that if * value >= nextValue the condition is not met. */ if (values[i].CompareTo(values[i + 1]) >= 0) { return false; } } } //The condition was met for all elements. return true; }
With the revised specification it would be:
public static bool InOrderEqual(int[] values, bool equalOk) { //All calculations are performed in long to avoid //under and overflows. long firstDifference = 0; //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { long prev = values[i]; long next = values[i + 1]; long difference = next - prev; if (equalOk && difference == 0) { continue; } if (firstDifference == 0) { firstDifference = difference; } if (firstDifference <= 0 || firstDifference != difference) { return false; } }
Now the equalOk looks out of place and makes the method much more complicated so let's remove that and make it two separate methods instead. The revised method isn't that bad but it should still be separated.
public static bool IsAlwaysIncreasing(this int[] values) { //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { /* * value < nextValue implies that if * value >= nextValue the condition is not met. */ if (values[i].CompareTo(values[i + 1]) >= 0) { return false; } } //The condition was met for all elements. return true; } public static bool IsNeverDecreasing(this int[] values) { //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { /* * value < nextValue implies that if * value >= nextValue the condition is not met. */ if (values[i].CompareTo(values[i + 1]) >= 0) { return false; } } //The condition was met for all elements. return true; } public static bool IsAlwaysIncreasingByFixedInterval(this int[] values) { //All calculations are performed in long to avoid //under and overflows. long firstDifference = 0; //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { long prev = values[i]; long next = values[i + 1]; long difference = next - prev; if (firstDifference == 0) { firstDifference = difference; } if (firstDifference <= 0 || firstDifference != difference) { return false; } } return true; } public static bool IsAlwaysIncreasingByFixedIntervalOrEqual(this int[] values) { //All calculations are performed in long to avoid //under and overflows. long firstDifference = 0; //We use i + 1 < values.Length so accessing i + 1 is always in range. for (int i = 0; i + 1 < values.Length; i++) { long prev = values[i]; long next = values[i + 1]; long difference = next - prev; if (difference == 0) { continue; } if (firstDifference == 0) { firstDifference = difference; } if (firstDifference <= 0 || firstDifference != difference) { return false; } } return true; }
I also added the this keyword to the values array so now it can be used as if it was part of the array class (if it's put in a static class):
int[] increasing = new int[] { 1, 2, 3 }; increasing.IsAlwaysIncreasing(); // true increasing.IsNeverDecreasing(); // true increasing.IsAlwaysIncreasingByFixedInterval(); // true increasing.IsAlwaysIncreasingByFixedIntervalOrEqual(); // true int[] increasingOrEqual = new int[] { 1, 2, 2, 3 }; increasingOrEqual.IsAlwaysIncreasing(); // false increasingOrEqual.IsNeverDecreasing(); // true increasingOrEqual.IsAlwaysIncreasingByFixedInterval(); // false increasingOrEqual.IsAlwaysIncreasingByFixedIntervalOrEqual(); // true
Now this seems an awful lot like LINQ so let's turn up the pace and make this in to a true generic LINQ function:
public static class MyIEnumerableExtensions { public static bool IsAlwaysIncreasingByFixedInterval(this IEnumerable<int> values) { if (values == null) throw new ArgumentNullException("values"); //All calculations are performed in long to avoid //under and overflows. long firstDifference = 0; return values.All((prev, next) => { long difference = (long)next - prev; if (firstDifference == 0) { firstDifference = difference; } return firstDifference > 0 && firstDifference == difference; }); } public static bool IsAlwaysIncreasingByFixedIntervalOrEqual(this IEnumerable<int> values) { if (values == null) throw new ArgumentNullException("values"); //All calculations are performed in long to avoid //under and overflows. long firstDifference = 0; return values.All((prev, next) => { long difference = (long)next - prev; if (difference == 0) { return true; } if (firstDifference == 0) { firstDifference = difference; } return firstDifference > 0 && firstDifference == difference; }); } public static bool IsAlwaysIncreasing<TSource>(this IEnumerable<TSource> source) { if (source == null) throw new ArgumentNullException("source"); var comparer = Comparer<TSource>.Default; return IsAlwaysIncreasing(source, comparer); } public static bool IsAlwaysIncreasing<TSource>(this IEnumerable<TSource> source, IComparer<TSource> comparer) { if (source == null) throw new ArgumentNullException("source"); if (comparer == null) throw new ArgumentNullException("comparer"); return source.All((prev, next) => comparer.Compare(prev, next) < 0); } public static bool IsNeverDecreasing<TSource>(this IEnumerable<TSource> source) { if (source == null) throw new ArgumentNullException("source"); var comparer = Comparer<TSource>.Default; return IsNeverDecreasing(source, comparer); } public static bool IsNeverDecreasing<TSource>(this IEnumerable<TSource> source, IComparer<TSource> comparer) { if (source == null) throw new ArgumentNullException("source"); if (comparer == null) throw new ArgumentNullException("comparer"); return source.All((prev, next) => comparer.Compare(prev, next) <= 0); } /// <summary> /// Applies the <paramref name="sequencePairPredicate"/> to each pair of value and nextValue in <paramref name="source"/>. /// </summary> /// <typeparam name="TSource">The type of the elements of <paramref name="source"/></typeparam> /// <param name="source">An <see cref="IEnumerable<T>"/> that contains the elements to apply the predicate to.</param> /// <param name="sequencePairPredicate">A function to test each sequential pair for a condition.</param> /// <exception cref="ArgumentNullException"><paramref name="source"/> or <paramref name="sequencePairPredicate"/> is null.</exception> /// <returns>true if every element pair of the source sequence passes the test in the specified predicate, or if the sequence contains less than two elements; otherwise, false.</returns> public static bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, TSource, bool> sequencePairPredicate) { if (source == null) throw new ArgumentNullException("source"); if (sequencePairPredicate == null) throw new ArgumentNullException("sequencePairPredicate"); var isFirst = true; var previousValue = default(TSource); foreach (var value in source) { if (isFirst) { previousValue = value; isFirst = false; continue; } if (!sequencePairPredicate(previousValue, value)) { return false; } previousValue = value; } return true; } }
The compare version can then be used with integers and strings and everything else that is IComparable and the revised version can be used on any sequence of integers:
var isIncreasing = new[] { 1, 2, 4, 8, 16 }; isIncreasing.IsAlwaysIncreasing(); // True isIncreasing.IsNeverDecreasing(); // True isIncreasing.IsAlwaysIncreasingByFixedInterval(); // False isIncreasing.IsAlwaysIncreasingByFixedIntervalOrEqual(); // False var isNeverDecreasing = new[] { 1, 1, 2, 3, 5 }; isNeverDecreasing.IsAlwaysIncreasing(); // False isNeverDecreasing.IsNeverDecreasing(); // True isNeverDecreasing.IsAlwaysIncreasingByFixedInterval(); // False isNeverDecreasing.IsAlwaysIncreasingByFixedIntervalOrEqual(); // False var isIncreasingByFixedInterval = new[] { 1, 2, 3 }; isIncreasingByFixedInterval.IsAlwaysIncreasingByFixedInterval()); // True isIncreasingByFixedInterval.IsAlwaysIncreasingByFixedIntervalOrEqual()); // True var isIncreasingByFixedIntervalOrEqual = new[] { 1, 2, 2, 3 }; isIncreasingByFixedIntervalOrEqual.IsAlwaysIncreasingByFixedInterval()); // False isIncreasingByFixedIntervalOrEqual.IsAlwaysIncreasingByFixedIntervalOrEqual()); // True var andWithStrings = new[] { "a", "b", "c", "D", "e" }; // false becayse D > e and Ordinal is case sensitive andWithStrings.IsAlwaysIncreasing(StringComparer.Ordinal); // true because we ignore case andWithStrings.IsAlwaysIncreasing(StringComparer.CurrentCultureIgnoreCase); // true, same as above, StringComparer.CurrentCultureIgnoreCase is default for strings andWithStrings.IsAlwaysIncreasing();
2,5,11should befalse. \$\endgroup\$