Select interval linq - c#

Is there some way with LINQ to select certain numbers with shortcut criteria.
Like this:
I have numbers from 1 to 10000.
My criteria is (4012..4190|4229), meaning take numbers between 4012 to 4190 and number 4229:
static int[] test(string criteria)
{
// criteria is 4012..4190|4229
// select numbers from lab where criteria is met
int[] lab = Enumerable.Range(0, 10000).ToArray();
return lab;
}

This should be enough for your case:
return lab.Where((int1) => (int1 >= 4012 && int1 <= 4190) || int1 == 4229).ToArray();
Also a quick way of parsing your criteria would be to use RegEx:
Regex r = new Regex(#"\d+");
MatchCollection m = r.Matches(criteria);
int start = int.Parse(m[0].Value);
int end = int.Parse(m[1].Value);
int specific = int.Parse(m[2].Value);
return lab.Where((int1) => (int1 >= start && int1 <= end) || int1 == specific).ToArray();

If your criteria is always a string, you need some way to parse it, to Func<int, bool, but it's not LINQ specific. In the end you'll need something like this:
Func<int, bool> predicate = Parse(criteria);
return lab.Where(predicate).ToArray();
where very basic implementation of Parse might look as follows:
public static Func<int, bool> Parse(string criteria)
{
var alternatives = criteria
.Split('|')
.Select<string, Func<int, bool>>(
token =>
{
if (token.Contains(".."))
{
var between = token.Split(new[] {".."}, StringSplitOptions.RemoveEmptyEntries);
int lo = int.Parse(between[0]);
int hi = int.Parse(between[1]);
return x => lo <= x && x <= hi;
}
else
{
int exact = int.Parse(token);
return x => x == exact;
}
})
.ToArray();
return x => alternatives.Any(alt => alt(x));
}

You can concatenate two sequenses
int[] lab = Enumerable.Range(4012, 4190-4012).Concat(Enumerable.Range(4229,1)).ToArray();
Update:
you need to parse incoming criteria first
static int[] test(string criteria)
{
// criteria is 4012..4190|4229
// select numbers from lab where criteria is met
// assume you parsed your criteria to 2 dimentional array
// I used count for second part for convience
int[][] criteriaArray = { new int[]{ 4012, 50 }, new int[]{ 4229, 1 } };
var seq = Enumerable.Range(criteriaArray[0][0], criteriaArray[0][1]);
for (int i = 1; i < criteriaArray.Length; i++)
{
int start = criteriaArray[i][0];
int count = criteriaArray[i][1];
seq = seq.Concat(Enumerable.Range(start, count));
}
return seq.ToArray();
}

You could :
Flatten[{Range[4012, 4190], 4229}]
And in some way this would work as well 4012..4190|4229, but answer is exactly that - list of items from 4012 to 4190 and item 4229.
Lambda just imitates pure functions. However unless you have free wolfram kernel, using this approach might no be most cost effective. However, you do not need to write boilerplate code.

Related

Check if a string is sorted

I have a string, simplified "12345" which is sorted. The string couild contain Digits (0-9) or letters (a-z). In case of a mixed use the natural sort order. I need a method to verify if this is true.
Attempt with linq technique:
string items1 = "2349"; //sorted
string items2 = "2476"; //not sorted, 6<>7
bool sorted1 = Enumerable.SequenceEqual(items1.OrderBy(x => x), items1); //true
bool sorted2 = Enumerable.SequenceEqual(items2.OrderBy(x => x), items2); //false
but there could be also a descending sort order.
Is there a better way then
string items3 = "4321";
bool sorted3 = Enumerable.SequenceEqual(items3.OrderBy(x => x), items3) || Enumerable.SequenceEqual(items3.OrderByDescending(x => x), items3);
to check if a string is sorted? Maybe some built in solution?
Your solution in fine and very readable. One problem with it is that it requires ordering the string which is O(n * log(n)), this can be solved by iterating the string without sorting it.
For example:
var firstDifs = items1.Zip(items1.Skip(1), (x, y) => y - x);
This Linq projects every 2 items in the first string to a number which indicates their difference, So if you have items1 = "1245" the output will be:
firstDifs: {1, 2, 1}
Now all you need to do is to validate that firstDifs is either ascending or descending:
bool firstSorted = firstDifs.All(x => x > 0) || firstDifs.All(x => x < 0); //true
Now:
Skip is O(1) since the amount of actions required to skip 1 cell is
constant.
Zip is O(n).
All is O(n).
So the whole solution is O(n).
Note that it will be more efficient with a simple loop, also if the first All has returned false because the 3487th item changes its direction (for example: 1234567891), the second All will run for no reason with the Zip running twice as well (Until where All require) - since there are two iterations of All and Linq evaluates them lazily.
It requires a reducer. In C#, it's Enumerable.Aggregate. It's O(n) algorithm.
var query = "123abc".Aggregate(new { asceding = true, descending = true, prev = (char?)null },
(result, currentChar) =>
new
{
asceding = result.prev == null || result.asceding && currentChar >= result.prev,
descending = result.prev == null || result.descending && currentChar <= result.prev,
prev = (char?)currentChar
}
);
Console.WriteLine(query.asceding || query.descending );
I once had to check something similar to your case but with huge data streams, so performance was important. I came up with this small extension class which performs very well:
public static bool IsOrdered<T>(this IEnumerable<T> enumerable) where T: IComparable<T>
{
using (var enumerator = enumerable.GetEnumerator())
{
if (!enumerator.MoveNext())
return true; //empty enumeration is ordered
var left = enumerator.Current;
int previousUnequalComparison = 0;
while (enumerator.MoveNext())
{
var right = enumerator.Current;
var currentComparison = left.CompareTo(right);
if (currentComparison != 0)
{
if (previousUnequalComparison != 0
&& currentComparison != previousUnequalComparison)
return false;
previousUnequalComparison = currentComparison;
left = right;
}
}
}
return true;
}
Using it is obviously very simple:
var items1 = "2349";
var items2 = "2476"; //not sorted, 6<>7
items1.IsOrdered(); //true
items2.IsOrdered(); //false
You can do much better than the accepted answer by not having to compare all of the elements:
var s = "2349";
var r = Enumerable.Range(1, s.Length - 1);
//var isAscending = r.All(i => s[i - 1] <= s[i]);
//var isDescending = r.All(i => s[i - 1] >= s[i]);
var isOrdered = r.All(i => s[i - 1] <= s[i]) || r.All(i => s[i - 1] >= s[i]);
var items = "4321";
var sortedItems = items.OrderBy(i => i); // Process the order once only
var sorted = sortedItems.SequenceEqual(items) || sortedItems.SequenceEqual(items.Reverse()); // Reverse using yield return
I would go for simple iteration over all elements:
string str = "whatever123";
Func<char, char, bool> pred;
bool? asc = str.TakeWhile((q, i) => i < str.Length - 1)
.Select((q, i) => str[i] == str[i+1] ? (bool?)null : str[i] < str[i+1])
.FirstOrDefault(q => q.HasValue);
if (!asc.HasValue)
return true; //all chars are the same
if (asc.Value)
pred = (c1, c2) => c1 <= c2;
else
pred = (c1, c2) => c1 >= c2;
for (int i = 0; i < str.Length - 1; ++i)
{
if (!pred(str[i], str[i + 1]))
return false;
}
return true;

C# array for finding all the 2's

Basically what I have to do is find a certain number, which in this case is 2, and see how many times I have that number in my program, I assumed that I would have to use a .GetValue(42) but it's not doing it right, the code I am using is
static int count2(int[] input)
{
return input.GetValue(2);
}
input is from a separate method, but it contains the values that I'm working with which is
int [] input = {1,2,3,4,5};
Not sure if you count specifically the number 2, or any number that contains the number 2.
For the later here's the easy way:
public int count2(int[] input) {
int counter = 0;
foreach(var i in input) {
if (i.ToString().Contains("2"))
{
++counter;
}
}
return counter;
}
You can do it with LINQ
input.Count(x=>x==2);
Array.GetValue() "gets the value at the specified position in the one-dimensional Array" which is not what you want. (in your example it will return 3 because that's the value at index 2 of your array).
You want to count the number of times a specific item is in the array. That's a matter of looping and checking each item:
var counter = 0;
foreach(var item in input)
{
if(item == 2)
{
counter++;
}
}
return counter;
to get a count do this
int [] inputDupes = {1,2,3,4,5,2};
var duplicates = inputDupes
.Select(w => inputDupes.Contains(2))
.GroupBy(q => q)
.Where(gb => gb.Count() > 1)
.Select(gb => gb.Key).Count();//returns an Int32 value
to see if there are duplicates of the number 2 then do the following
int [] inputDupes = {1,2,3,4,5,2};
var duplicates = inputDupes
.Select(w => inputDupes.Contains(2))
.GroupBy(q => q)
.Where(gb => gb.Count() > 1)
.Select(gb => gb.Key)
.ToList(); //returns true | false
if you want to do this based on any number then create a method and pass a param in where .Contains() extension method is being called
if you want to capture user input from Console you can do it this way as well
int [] inputDupes = {1,2,3,4,5,2};
Console.WriteLine("Enter a number to check for duplicates: ");
string input = Console.ReadLine();
int number;
Int32.TryParse(input, out number);
var dupeCount = inputDupes.Count(x => x == number);
Console.WriteLine(dupeCount);
Console.Read();
Yields 2 for the duplicate Count
static int count2(int[] input)
{
return input.Count(i => i == 2);
}
You could use a Func like this:
public Func<int[], int, int> GetNumberCount =
(numbers, numberToSearchFor) =>
numbers.Count(num => num.Equals(numberToSearchFor));
...
var count = GetNumberCount(input, 2);
Gotta' love a Func :)

How to perform word search using LINQ?

I have a list which contains the name of suppliers. Say
SuppId Supplier Name
----------------------------------
1 Aardema & Whitelaw
2 Aafedt Forde Gray
3 Whitelaw & Sears-Ewald
using following LINQ query
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey));
I can return records correctly in the following conditions,
1) If i am using search string as "Whitelaw & Sears-Ewald" it will return 3rd record.
2) If i am using "Whitelaw" or "Sears-Ewald" it will return 3rd record.
But how can i return 3rd record if i am giving search string as "Whitelaw Sears-Ewald". It always returns 0 records.
Can i use ALL to get this result, but i dont know how to use it for this particular need.
What I usually do in this situation is split the words into a collection, then perform the following:
var searchopts = SearchKey.Split(' ').ToList();
supplierListQuery = supplierListQuery
.Where(x => searchopts.Any(y=> x.SupplierName.Contains(y)));
This works for me:
IEnumerable<string> keyWords = SearchKey.Split('');
supplierListQuery = supplierListQuery
.AsParallel()
.Where
(
x => keyWords.All
(
keyword => x.SupplierName.ContainsIgnoreCase(keyword)
)
);
Thank you all for your quick responses. But the one which worked or a easy fix to handle this was timothyclifford's note on this. Like he said i alterd my answer to this
string[] filters = SearchKey.ToLower().Split(new[] { ' ' });
objSuppliersList = (from x in objSuppliersList
where filters.All(f => x.SupplierName.ToLower().Contains(f))
select x).ToList();
Now it returns the result for all my serach conditions.
Because "Whitelaw" appears in both you will get both records. Otherwise there is no dynamic way to determine you only want the last one. If you know you only have these 3 then append .Last() to get the final record.
supplierListQuery = supplierListQuery.Where(x => x.SupplierName.Contains(SearchKey.Split(' ')[0]));
You need to use some sort of string comparer to create your own simple Search Engine and then you can find strings that are most likely to be included in your result :
public static class SearchEngine
{
public static double CompareStrings(string val1, string val2)
{
if ((val1.Length == 0) || (val2.Length == 0)) return 0;
if (val1 == val2) return 100;
double maxLength = Math.Max(val1.Length, val2.Length);
double minLength = Math.Min(val1.Length, val2.Length);
int charIndex = 0;
for (int i = 0; i < minLength; i++) { if (val1.Contains(val2[i])) charIndex++; }
return Math.Round(charIndex / maxLength * 100);
}
public static List<string> Search(this string[] values, string searchKey, double threshold)
{
List<string> result = new List<string>();
for (int i = 0; i < values.Length; i++) if (CompareStrings(values[i], searchKey) > threshold) result.Add(values[i]);
return result;
}
}
Example of usage :
string[] array = { "Aardema & Whitelaw", "Aafedt Forde Gray", "Whitelaw & Sears-Ewald" };
var result = array.Search("WhitelawSears-Ewald", 80);
// Results that matches this string with 80% or more
foreach (var item in result)
{
Console.WriteLine(item);
}
Output: Whitelaw & Sears-Ewald
If you want an easy (not very handy) solution,
var result = supplierListQuery
.Select(x => normalize(x.SupplierName))
.Where(x => x.Contains(normalize(SearchKey)));
string normalize(string inputStr)
{
string retVal = inputStr.Replace("&", "");
while (retVal.IndexOf(" ") >= 0)
{
retVal = retVal.Replace(" ", " ");
}
return retVal;
}

Detecting sequence of at least 3 sequential numbers from a given list

I have a list of numbers e.g. 21,4,7,9,12,22,17,8,2,20,23
I want to be able to pick out sequences of sequential numbers (minimum 3 items in length), so from the example above it would be 7,8,9 and 20,21,22,23.
I have played around with a few ugly sprawling functions but I am wondering if there is a neat LINQ-ish way to do it.
Any suggestions?
UPDATE:
Many thanks for all the responses, much appriciated. Im am currently having a play with them all to see which would best integrate into our project.
It strikes me that the first thing you should do is order the list. Then it's just a matter of walking through it, remembering the length of your current sequence and detecting when it's ended. To be honest, I suspect that a simple foreach loop is going to be the simplest way of doing that - I can't immediately think of any wonderfully neat LINQ-like ways of doing it. You could certainly do it in an iterator block if you really wanted to, but bear in mind that ordering the list to start with means you've got a reasonably "up-front" cost anyway. So my solution would look something like this:
var ordered = list.OrderBy(x => x);
int count = 0;
int firstItem = 0; // Irrelevant to start with
foreach (int x in ordered)
{
// First value in the ordered list: start of a sequence
if (count == 0)
{
firstItem = x;
count = 1;
}
// Skip duplicate values
else if (x == firstItem + count - 1)
{
// No need to do anything
}
// New value contributes to sequence
else if (x == firstItem + count)
{
count++;
}
// End of one sequence, start of another
else
{
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
count = 1;
firstItem = x;
}
}
if (count >= 3)
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
count, firstItem);
}
EDIT: Okay, I've just thought of a rather more LINQ-ish way of doing things. I don't have the time to fully implement it now, but:
Order the sequence
Use something like SelectWithPrevious (probably better named SelectConsecutive) to get consecutive pairs of elements
Use the overload of Select which includes the index to get tuples of (index, current, previous)
Filter out any items where (current = previous + 1) to get anywhere that counts as the start of a sequence (special-case index=0)
Use SelectWithPrevious on the result to get the length of the sequence between two starting points (subtract one index from the previous)
Filter out any sequence with length less than 3
I suspect you need to concat int.MinValue on the ordered sequence, to guarantee the final item is used properly.
EDIT: Okay, I've implemented this. It's about the LINQiest way I can think of to do this... I used null values as "sentinel" values to force start and end sequences - see comments for more details.
Overall, I wouldn't recommend this solution. It's hard to get your head round, and although I'm reasonably confident it's correct, it took me a while thinking of possible off-by-one errors etc. It's an interesting voyage into what you can do with LINQ... and also what you probably shouldn't.
Oh, and note that I've pushed the "minimum length of 3" part up to the caller - when you have a sequence of tuples like this, it's cleaner to filter it out separately, IMO.
using System;
using System.Collections.Generic;
using System.Linq;
static class Extensions
{
public static IEnumerable<TResult> SelectConsecutive<TSource, TResult>
(this IEnumerable<TSource> source,
Func<TSource, TSource, TResult> selector)
{
using (IEnumerator<TSource> iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
{
yield break;
}
TSource prev = iterator.Current;
while (iterator.MoveNext())
{
TSource current = iterator.Current;
yield return selector(prev, current);
prev = current;
}
}
}
}
class Test
{
static void Main()
{
var list = new List<int> { 21,4,7,9,12,22,17,8,2,20,23 };
foreach (var sequence in FindSequences(list).Where(x => x.Item1 >= 3))
{
Console.WriteLine("Found sequence of length {0} starting at {1}",
sequence.Item1, sequence.Item2);
}
}
private static readonly int?[] End = { null };
// Each tuple in the returned sequence is (length, first element)
public static IEnumerable<Tuple<int, int>> FindSequences
(IEnumerable<int> input)
{
// Use null values at the start and end of the ordered sequence
// so that the first pair always starts a new sequence starting
// with the lowest actual element, and the final pair always
// starts a new one starting with null. That "sequence at the end"
// is used to compute the length of the *real* final element.
return End.Concat(input.OrderBy(x => x)
.Select(x => (int?) x))
.Concat(End)
// Work out consecutive pairs of items
.SelectConsecutive((x, y) => Tuple.Create(x, y))
// Remove duplicates
.Where(z => z.Item1 != z.Item2)
// Keep the index so we can tell sequence length
.Select((z, index) => new { z, index })
// Find sequence starting points
.Where(both => both.z.Item2 != both.z.Item1 + 1)
.SelectConsecutive((start1, start2) =>
Tuple.Create(start2.index - start1.index,
start1.z.Item2.Value));
}
}
Jon Skeet's / Timwi's solutions are the way to go.
For fun, here's a LINQ query that does the job (very inefficiently):
var sequences = input.Distinct()
.GroupBy(num => Enumerable.Range(num, int.MaxValue - num + 1)
.TakeWhile(input.Contains)
.Last()) //use the last member of the consecutive sequence as the key
.Where(seq => seq.Count() >= 3)
.Select(seq => seq.OrderBy(num => num)); // not necessary unless ordering is desirable inside each sequence.
The query's performance can be improved slightly by loading the input into a HashSet (to improve Contains), but that will still not produce a solution that is anywhere close to efficient.
The only bug I am aware of is the possibility of an arithmetic overflow if the sequence contains negative numbers of large magnitude (we cannot represent the count parameter for Range). This would be easy to fix with a custom static IEnumerable<int> To(this int start, int end) extension-method. If anyone can think of any other simple technique of dodging the overflow, please let me know.
EDIT:
Here's a slightly more verbose (but equally inefficient) variant without the overflow issue.
var sequences = input.GroupBy(num => input.Where(candidate => candidate >= num)
.OrderBy(candidate => candidate)
.TakeWhile((candidate, index) => candidate == num + index)
.Last())
.Where(seq => seq.Count() >= 3)
.Select(seq => seq.OrderBy(num => num));
I think my solution is more elegant and simple, and therefore easier to verify as correct:
/// <summary>Returns a collection containing all consecutive sequences of
/// integers in the input collection.</summary>
/// <param name="input">The collection of integers in which to find
/// consecutive sequences.</param>
/// <param name="minLength">Minimum length that a sequence should have
/// to be returned.</param>
static IEnumerable<IEnumerable<int>> ConsecutiveSequences(
IEnumerable<int> input, int minLength = 1)
{
var results = new List<List<int>>();
foreach (var i in input.OrderBy(x => x))
{
var existing = results.FirstOrDefault(lst => lst.Last() + 1 == i);
if (existing == null)
results.Add(new List<int> { i });
else
existing.Add(i);
}
return minLength <= 1 ? results :
results.Where(lst => lst.Count >= minLength);
}
Benefits over the other solutions:
It can find sequences that overlap.
It’s properly reusable and documented.
I have not found any bugs ;-)
Here is how to solve the problem in a "LINQish" way:
int[] arr = new int[]{ 21, 4, 7, 9, 12, 22, 17, 8, 2, 20, 23 };
IOrderedEnumerable<int> sorted = arr.OrderBy(x => x);
int cnt = sorted.Count();
int[] sortedArr = sorted.ToArray();
IEnumerable<int> selected = sortedArr.Where((x, idx) =>
idx <= cnt - 3 && sortedArr[idx + 1] == x + 1 && sortedArr[idx + 2] == x + 2);
IEnumerable<int> result = selected.SelectMany(x => new int[] { x, x + 1, x + 2 }).Distinct();
Console.WriteLine(string.Join(",", result.Select(x=>x.ToString()).ToArray()));
Due to the array copying and reconstruction, this solution - of course - is not as efficient as the traditional solution with loops.
Not 100% Linq but here's a generic variant:
static IEnumerable<IEnumerable<TItem>> GetSequences<TItem>(
int minSequenceLength,
Func<TItem, TItem, bool> areSequential,
IEnumerable<TItem> items)
where TItem : IComparable<TItem>
{
items = items
.OrderBy(n => n)
.Distinct().ToArray();
var lastSelected = default(TItem);
var sequences =
from startItem in items
where startItem.Equals(items.First())
|| startItem.CompareTo(lastSelected) > 0
let sequence =
from item in items
where item.Equals(startItem) || areSequential(lastSelected, item)
select (lastSelected = item)
where sequence.Count() >= minSequenceLength
select sequence;
return sequences;
}
static void UsageInt()
{
var sequences = GetSequences(
3,
(a, b) => a + 1 == b,
new[] { 21, 4, 7, 9, 12, 22, 17, 8, 2, 20, 23 });
foreach (var sequence in sequences)
Console.WriteLine(string.Join(", ", sequence.ToArray()));
}
static void UsageChar()
{
var list = new List<char>(
"abcdefghijklmnopqrstuvwxyz".ToCharArray());
var sequences = GetSequences(
3,
(a, b) => (list.IndexOf(a) + 1 == list.IndexOf(b)),
"PleaseBeGentleWithMe".ToLower().ToCharArray());
foreach (var sequence in sequences)
Console.WriteLine(string.Join(", ", sequence.ToArray()));
}
Here's my shot at it:
public static class SequenceDetector
{
public static IEnumerable<IEnumerable<T>> DetectSequenceWhere<T>(this IEnumerable<T> sequence, Func<T, T, bool> inSequenceSelector)
{
List<T> subsequence = null;
// We can only have a sequence with 2 or more items
T last = sequence.FirstOrDefault();
foreach (var item in sequence.Skip(1))
{
if (inSequenceSelector(last, item))
{
// These form part of a sequence
if (subsequence == null)
{
subsequence = new List<T>();
subsequence.Add(last);
}
subsequence.Add(item);
}
else if (subsequence != null)
{
// We have a previous seq to return
yield return subsequence;
subsequence = null;
}
last = item;
}
if (subsequence != null)
{
// Return any trailing seq
yield return subsequence;
}
}
}
public class test
{
public static void run()
{
var list = new List<int> { 21, 4, 7, 9, 12, 22, 17, 8, 2, 20, 23 };
foreach (var subsequence in list
.OrderBy(i => i)
.Distinct()
.DetectSequenceWhere((first, second) => first + 1 == second)
.Where(seq => seq.Count() >= 3))
{
Console.WriteLine("Found subsequence {0}",
string.Join(", ", subsequence.Select(i => i.ToString()).ToArray()));
}
}
}
This returns the specific items that form the sub-sequences and permits any type of item and any definition of criteria so long as it can be determined by comparing adjacent items.
What about sorting the array then create another array that is the difference between each element the previous one
sortedArray = 8, 9, 10, 21, 22, 23, 24, 27, 30, 31, 32
diffArray = 1, 1, 11, 1, 1, 1, 3, 3, 1, 1
Now iterate through the difference array; if the difference equlas 1, increase the count of a variable, sequenceLength, by 1. If the difference is > 1, check the sequenceLength if it is >=2 then you have a sequence of at at least 3 consecutive elements. Then reset sequenceLenght to 0 and continue your loop on the difference array.
Here is a solution I knocked up in F#, it should be fairly easy to translate this into a C# LINQ query since fold is pretty much equivalent to the LINQ aggregate operator.
#light
let nums = [21;4;7;9;12;22;17;8;2;20;23]
let scanFunc (mainSeqLength, mainCounter, lastNum:int, subSequenceCounter:int, subSequence:'a list, foundSequences:'a list list) (num:'a) =
(mainSeqLength, mainCounter + 1,
num,
(if num <> lastNum + 1 then 1 else subSequenceCounter+1),
(if num <> lastNum + 1 then [num] else subSequence#[num]),
if subSequenceCounter >= 3 then
if mainSeqLength = mainCounter+1
then foundSequences # [subSequence#[num]]
elif num <> lastNum + 1
then foundSequences # [subSequence]
else foundSequences
else foundSequences)
let subSequences = nums |> Seq.sort |> Seq.fold scanFunc (nums |> Seq.length, 0, 0, 0, [], []) |> fun (_,_,_,_,_,results) -> results
Linq isn't the solution for everything, sometimes you're better of with a simple loop. Here's a solution, with just a bit of Linq to order the original sequences and filter the results
void Main()
{
var numbers = new[] { 21,4,7,9,12,22,17,8,2,20,23 };
var sequences =
GetSequences(numbers, (prev, curr) => curr == prev + 1);
.Where(s => s.Count() >= 3);
sequences.Dump();
}
public static IEnumerable<IEnumerable<T>> GetSequences<T>(
IEnumerable<T> source,
Func<T, T, bool> areConsecutive)
{
bool first = true;
T prev = default(T);
List<T> seq = new List<T>();
foreach (var i in source.OrderBy(i => i))
{
if (!first && !areConsecutive(prev, i))
{
yield return seq.ToArray();
seq.Clear();
}
first = false;
seq.Add(i);
prev = i;
}
if (seq.Any())
yield return seq.ToArray();
}
I thought of the same thing as Jon: to represent a range of consecutive integers all you really need are two measly integers! So I'd start there:
struct Range : IEnumerable<int>
{
readonly int _start;
readonly int _count;
public Range(int start, int count)
{
_start = start;
_count = count;
}
public int Start
{
get { return _start; }
}
public int Count
{
get { return _count; }
}
public int End
{
get { return _start + _count - 1; }
}
public IEnumerator<int> GetEnumerator()
{
for (int i = 0; i < _count; ++i)
{
yield return _start + i;
}
}
// Heck, why not?
public static Range operator +(Range x, int y)
{
return new Range(x.Start, x.Count + y);
}
// skipping the explicit IEnumerable.GetEnumerator implementation
}
From there, you can write a static method to return a bunch of these Range values corresponding to the consecutive numbers of your sequence.
static IEnumerable<Range> FindRanges(IEnumerable<int> source, int minCount)
{
// throw exceptions on invalid arguments, maybe...
var ordered = source.OrderBy(x => x);
Range r = default(Range);
foreach (int value in ordered)
{
// In "real" code I would've overridden the Equals method
// and overloaded the == operator to write something like
// if (r == Range.Empty) here... but this works well enough
// for now, since the only time r.Count will be 0 is on the
// first item.
if (r.Count == 0)
{
r = new Range(value, 1);
continue;
}
if (value == r.End)
{
// skip duplicates
continue;
}
else if (value == r.End + 1)
{
// "append" consecutive values to the range
r += 1;
}
else
{
// return what we've got so far
if (r.Count >= minCount)
{
yield return r;
}
// start over
r = new Range(value, 1);
}
}
// return whatever we ended up with
if (r.Count >= minCount)
{
yield return r;
}
}
Demo:
int[] numbers = new[] { 21, 4, 7, 9, 12, 22, 17, 8, 2, 20, 23 };
foreach (Range r in FindConsecutiveRanges(numbers, 3))
{
// Using .NET 3.5 here, don't have the much nicer string.Join overloads.
Console.WriteLine(string.Join(", ", r.Select(x => x.ToString()).ToArray()));
}
Output:
7, 8, 9
20, 21, 22, 23
Here's my LINQ-y take on the problem:
static IEnumerable<IEnumerable<int>>
ConsecutiveSequences(this IEnumerable<int> input, int minLength = 3)
{
int order = 0;
var inorder = new SortedSet<int>(input);
return from item in new[] { new { order = 0, val = inorder.First() } }
.Concat(
inorder.Zip(inorder.Skip(1), (x, val) =>
new { order = x + 1 == val ? order : ++order, val }))
group item.val by item.order into list
where list.Count() >= minLength
select list;
}
uses no explicit loops, but should still be O(n lg n)
uses SortedSet instead of .OrderBy().Distinct()
combines consecutive element with list.Zip(list.Skip(1))
Here's a solution using a Dictionary instead of a sort...
It adds the items to a Dictionary, and then for each value increments above and below to find the longest sequence.
It is not strictly LINQ, though it does make use of some LINQ functions, and I think it is more readable than a pure LINQ solution..
static void Main(string[] args)
{
var items = new[] { -1, 0, 1, 21, -2, 4, 7, 9, 12, 22, 17, 8, 2, 20, 23 };
IEnumerable<IEnumerable<int>> sequences = FindSequences(items, 3);
foreach (var sequence in sequences)
{ //print results to consol
Console.Out.WriteLine(sequence.Select(num => num.ToString()).Aggregate((a, b) => a + "," + b));
}
Console.ReadLine();
}
private static IEnumerable<IEnumerable<int>> FindSequences(IEnumerable<int> items, int minSequenceLength)
{
//Convert item list to dictionary
var itemDict = new Dictionary<int, int>();
foreach (int val in items)
{
itemDict[val] = val;
}
var allSequences = new List<List<int>>();
//for each val in items, find longest sequence including that value
foreach (var item in items)
{
var sequence = FindLongestSequenceIncludingValue(itemDict, item);
allSequences.Add(sequence);
//remove items from dict to prevent duplicate sequences
sequence.ForEach(i => itemDict.Remove(i));
}
//return only sequences longer than 3
return allSequences.Where(sequence => sequence.Count >= minSequenceLength).ToList();
}
//Find sequence around start param value
private static List<int> FindLongestSequenceIncludingValue(Dictionary<int, int> itemDict, int value)
{
var result = new List<int>();
//check if num exists in dictionary
if (!itemDict.ContainsKey(value))
return result;
//initialize sequence list
result.Add(value);
//find values greater than starting value
//and add to end of sequence
var indexUp = value + 1;
while (itemDict.ContainsKey(indexUp))
{
result.Add(itemDict[indexUp]);
indexUp++;
}
//find values lower than starting value
//and add to start of sequence
var indexDown = value - 1;
while (itemDict.ContainsKey(indexDown))
{
result.Insert(0, itemDict[indexDown]);
indexDown--;
}
return result;
}

How do I get the index of the highest value in an array using LINQ?

I have an array of doubles and I want the index of the highest value. These are the solutions that I've come up with so far but I think that there must be a more elegant solution. Ideas?
double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
int topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderByDescending(x => x.Item).Select(x => x.Index).First();
topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).OrderBy(x => x.Item).Select(x => x.Index).Last();
double maxVal = score.Max();
topScoreIndex = score.Select((item, indx) => new {Item = item, Index = indx}).Where(x => x.Item == maxVal).Select(x => x.Index).Single();
Meh, why make it overcomplicated? This is the simplest way.
var indexAtMax = scores.ToList().IndexOf(scores.Max());
Yeah, you could make an extension method to use less memory, but unless you're dealing with huge arrays, you will never notice the difference.
I suggest writing your own extension method (edited to be generic with an IComparable<T> constraint.)
public static int MaxIndex<T>(this IEnumerable<T> sequence)
where T : IComparable<T>
{
int maxIndex = -1;
T maxValue = default(T); // Immediately overwritten anyway
int index = 0;
foreach (T value in sequence)
{
if (value.CompareTo(maxValue) > 0 || maxIndex == -1)
{
maxIndex = index;
maxValue = value;
}
index++;
}
return maxIndex;
}
Note that this returns -1 if the sequence is empty.
A word on the characteristics:
This works with a sequence which can only be enumerated once - this can sometimes be very important, and is generally a desirable feature IMO.
The memory complexity is O(1) (as opposed to O(n) for sorting)
The runtime complexity is O(n) (as opposed to O(n log n) for sorting)
As for whether this "is LINQ" or not: if it had been included as one of the standard LINQ query operators, would you count it as LINQ? Does it feel particularly alien or unlike other LINQ operators? If MS were to include it in .NET 4.0 as a new operator, would it be LINQ?
EDIT: If you're really, really hell-bent on using LINQ (rather than just getting an elegant solution) then here's one which is still O(n) and only evaluates the sequence once:
int maxIndex = -1;
int index=0;
double maxValue = 0;
int urgh = sequence.Select(value => {
if (maxIndex == -1 || value > maxValue)
{
maxIndex = index;
maxValue = value;
}
index++;
return maxIndex;
}).Last();
It's hideous, and I don't suggest you use it at all - but it will work.
var scoreList = score.ToList();
int topIndex =
(
from x
in score
orderby x
select scoreList.IndexOf(x)
).Last();
If score wasn't an array this wouldn't be half bad...
Try this one which is completely LINQ and has the best performance:
var indexAtMax = scores.Select((x, i) => new { x, i })
.Aggregate((a, a1) => a.x > a1.x ? a : a1).i;
This isn't the only Aggregate based solution, but this is really just a single line solution.
double[] score = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 };
var max = score.Select((val,ix)=>new{val,ix})
.Aggregate(new{val=Double.MinValue,ix=-1},(z,last)=>z.val>=last.val?z:last);
Console.WriteLine ("maximum value is {0}", max.val );
Console.WriteLine ("index of maximum value is {0}", max.ix );
I had this problem today (to get the index in a users array who had highest age), and I did on this way:
var position = users.TakeWhile(u => u.Age != users.Max(x=>x.Age)).Count();
It was on C# class, so its noob solution, I´am sure your ones are better :)
System.Linq.Enumerable.Select with index and System.Linq.Enumerable.Aggregate would do it in one line
public static int IndexOfMax<TSource>(this IEnumerable<TSource> source)
where TSource : IComparable<TSource> => source.Select((value, idx) => (value, idx))
.Aggregate((aggr, next) => next.value.CompareTo(aggr.value) > 0 ? next : aggr).idx;
The worst possible complexity of this is O(2N) ~= O(N), but it needs to enumerate the collection two times.
void Main()
{
IEnumerable<int> numbers = new int[] { 1, 2, 3, 4, 5 };
int max = numbers.Max ();
int index = -1;
numbers.Any (number => { index++; return number == max; });
if(index != 4) {
throw new Exception("The result should have been 4, but " + index + " was found.");
}
"Simple test successful.".Dump();
}
If you want something that looks LINQy, in that it's purely functional, then Jon Skeets' answer above can be recast as:
public static int MaxIndex<T>(this IEnumerable<T> sequence) where T : IComparable<T>
{
return sequence.Aggregate(
new { maxIndex = -1, maxValue = default(T), thisIndex = 0 },
((agg, value) => (value.CompareTo(agg.maxValue) > 0 || agg.maxIndex == -1) ?
new {maxIndex = agg.thisIndex, maxValue = value, thisIndex = agg.thisIndex + 1} :
new {maxIndex = agg.maxIndex, maxValue = agg.maxValue, thisIndex = agg.thisIndex + 1 })).
maxIndex;
}
This has the same computational complexity as the other answer, but is more profligate with memory, creating an intermediate answer for each element of the enumerable.
Using other answers, I came up with this one; writing an extension:
public static int MaxIndex<T, R>(this IEnumerable<T> sequence, Func<T, R> evaluate) where R : IComparable<R>
{
var maxIndex = -1;
var maxValue = default(R);
var index = 0;
foreach (var value in sequence)
{
if (evaluate(value).CompareTo(maxValue) > 0 || maxIndex == -1)
{
maxIndex = index;
maxValue = evaluate(value);
}
index++;
}
return maxIndex;
}

Categories