Move item to first in array - c#

I have an array of objects
MyObjects[] mos = GetMyObjectsArray();
Now I want to move some element with id 1085 to first, so I write code like this in LINQ, is there more elegant way to do this?
mos.Where(c => c.ID == 1085).Take(1).Concat(mos.Where(c => c.ID != 1085)).ToArray();
Note, I want to save positioning of other items, so swaping with first item is not a solution

It's not LINQ, but it's how I'd do it with arrays.
public static bool MoveToFront<T>(this T[] mos, Predicate<T> match)
{
if (mos.Length == 0)
{
return false;
}
var idx = Array.FindIndex(mos, match);
if (idx == -1)
{
return false;
}
var tmp = mos[idx];
Array.Copy(mos, 0, mos, 1, idx);
mos[0] = tmp;
return true;
}
Usage:
MyObject[] mos = GetArray();
mos.MoveToFront(c => c.ID == 1085);

An array is not the best data structure for the operation you are attempting, it will potentially require copying a lot of items. For what you are doing you should use a List.
First, define a List extension method as follows:
static class ListExtensions
{
public static bool MoveToFront<T>(this List<T> list, Predicate<T> match)
{
int idx = list.FindIndex(match);
if (idx != -1)
{
if (idx != 0) // move only if not already in front
{
T value = list[idx]; // save matching value
list.RemoveAt(idx); // remove it from original location
list.Insert(0, value); // insert in front
}
return true;
}
return false; // matching value not found
}
}
Then you can use the MoveToFront extension method (modified from your example):
List<int> mos = GetMyObjectsList();
mos.MoveToFront(i => i == 1085);

// input array
T[] arr = Get();
// find the item
int index = Array.FindIndex(arr, i => i.ID == 1085);
if (index == -1)
throw new InvalidOperationException();
// get the item
T item = arr[index];
// place the item to the first position
T[] result = new T[arr.Length];
result[0] = item;
// copy items before the index
if (index > 0)
Array.Copy(arr, 0, result, 1, index);
// copy items after the index
if (index < arr.Length)
Array.Copy(arr, index + 1, result, index + 1, arr.Length - index - 1);
return result;

Related

How to iterate a collection between two items?

Consider for example a List of objects:
List<MyClass> myList;
I have a method which passes two references to items within the list. I want to iterate all items within the given ones (note: I don't know which of the two items comes first within the list):
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
foreach(var item in ????)
{
// do something
}
}
Currently I have solved this usecase as follows:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
if(listItemIdx < anotherListItemIdx )
{
for(int i = listItemIdx ; i <= anotherListItemIdx ; i++)
{
// do stuff
}
}
else
{
for (int i = anotherListItemIdx ; i < listItemIdx ; i++)
{
// do stuff
}
}
I was wondering if there is a more elegant, efficient or built-in solution to this problem?
If you are looking for performance (IndexOf twice can be a bit slow) and generalization
(when myList is not necessary List<T> but IEnumerable<T> only) you can put it as
bool proceed = false;
MyClass lastItem = default;
foreach (var item in myList) {
if (!proceed) {
if (proceed = item == listItem)
lastItem = anotherListItem;
else if (proceed = item == anotherListItem)
lastItem = listItem;
}
if (proceed) {
//TODO: do staff here
if (item == lastItem)
break;
}
}
You iterate three times over the list: Two times in IndexOf, and then once again in your loop. You can make your code more efficient with this code, which iterates only once over the list.
privat void MyFunction(MyClass listItem, MyClass anotherListItem)
{
bool betweenTwoItems = false;
foreach(var item in myList)
{
if(item == listItem || item == anotherListItem)
{
betweenTwoItems = !betweenTwoItems;
if(!betweenTwoItems)
{
break;
}
}
if(betweenTwoItems )
{
// do stuff
}
}
}
We set a bool variable if we are between the two items. In the beginning, it is false. The we iterate over the list and check whether the current item is one of the two method parameters. If this is the case, we invert the value of the bool. If after the inversion of the bool the value is false, we can leave the list. After that, we check whether the bool is true. If so, we can do stuff.
Online demo: https://dotnetfiddle.net/xYcr7V
More generic version of the same idea. So this can be created as extension method for IEnumerable<,>
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
foreach (T el in elements)
{
if (!hasStarted)
{
hasStarted = comparer.Equals(el, el1) || comparer.Equals(el, el2);
end = comparer.Equals(el, el1) ? el2 : el1;
}
if (hasStarted)
yield return el;
if (comparer.Equals(el, end))
yield break;
}
}
and version with the while loop supporting ranges from el to el. For example for [5, 0, 1, 2, 0, 6] the range [0, 0] will be [0, 1, 2, 0]:
public static IEnumerable<T> RangeOf<T>(this IEnumerable<T> elements, T el1, T el2,
IEqualityComparer<T> comparer = null)
{
comparer ??= EqualityComparer<T>.Default;
var hasStarted = false;
var end = default;
var it = elements.GetEnumerator();
while (!hasStarted && it.MoveNext())
{
T el = it.Current;
hasStarted = comparer.Equals(el , el1) || comparer.Equals(el , el2);
end = comparer.Equals(it.Current, el1) ? el2 : el1;
}
if (hasStarted)
yield return it.Current;
while (it.MoveNext())
{
yield return it.Current;
if (comparer.Equals(it.Current, end))
yield break;
}
}
both can be used like this
foreach (var el in list.RangeOf(listItem, anotherListItem))
// Do with el whatever you want to do
Is the list sorted? The you can use that fact to realize which item must be first. Nevertheless you can do with one for-loop, if you prefer:
private static void MyFunction(string item1, string item2)
{
List<string> input = new() {"A", "B", "C", "D", "E"};
int index1 = input.IndexOf(item1);
int index2 = input.IndexOf(item2);
int beginIndex = Math.Min(index1, index2);
int count = Math.Abs(index1 - index2) + 1;
foreach (string item in input.GetRange(beginIndex, count))
{
Console.Write(item);
}
}
Your existing solution can be improved:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
for(int i = startIdx ; i <= endIdx ; i++)
{
// do stuff
}
Thus, the code duplication disappears and only a minor refactoring is required.
To create a range-loop version, you can create a subset using GetRange(), something like:
int listItemIdx = myList.IndexOf(listItem);
int anotherListItemIdx = myList.IndexOf(anotherListItem);
int startIdx = Math.Min(listItemIdx, anotherListItemIdx);
int endIdx = Math.Max(listItemIdx, anotherListItemIdx);
var subset = myList.GetRange(startIdx, endIdx - startIdx);
foreach(var item in subset)
{
// do stuff
}
Thus, filtering the list and processing the list can now be separated.

Split a list of objects into sub-lists of contiguous elements using LINQ?

I have a simple class Item:
public class Item
{
public int Start { get; set;}
public int Stop { get; set;}
}
Given a List<Item> I want to split this into multiple sublists of contiguous elements. e.g. a method
List<Item[]> GetContiguousSequences(Item[] items)
Each element of the returned list should be an array of Item such that list[i].Stop == list[i+1].Start for each element
e.g.
{[1,10], [10,11], [11,20], [25,30], [31,40], [40,45], [45,100]}
=>
{{[1,10], [10,11], [11,20]}, {[25,30]}, {[31,40],[40,45],[45,100]}}
Here is a simple (and not guaranteed bug-free) implementation that simply walks the input data looking for discontinuities:
List<Item[]> GetContiguousSequences(Item []items)
{
var ret = new List<Item[]>();
var i1 = 0;
for(var i2=1;i2<items.Length;++i2)
{
//discontinuity
if(items[i2-1].Stop != items[i2].Start)
{
var num = i2 - i1;
ret.Add(items.Skip(i1).Take(num).ToArray());
i1 = i2;
}
}
//end of array
ret.Add(items.Skip(i1).Take(items.Length-i1).ToArray());
return ret;
}
It's not the most intuitive implementation and I wonder if there is a way to have a neater LINQ-based approach. I was looking at Take and TakeWhile thinking to find the indices where discontinuities occur but couldn't see an easy way to do this.
Is there a simple way to use IEnumerable LINQ algorithms to do this in a more descriptive (not necessarily performant) way?
I set of a simple test-case here: https://dotnetfiddle.net/wrIa2J
I'm really not sure this is much better than your original, but for the purpose of another solution the general process is
Use Select to project a list working out a grouping
Use GroupBy to group by the above
Use Select again to project the grouped items to an array of Item
Use ToList to project the result to a list
public static List<Item[]> GetContiguousSequences2(Item []items)
{
var currIdx = 1;
return items.Select( (item,index) => new {
item = item,
index = index == 0 || items[index-1].Stop == item.Start ? currIdx : ++currIdx
})
.GroupBy(x => x.index, x => x.item)
.Select(x => x.ToArray())
.ToList();
}
Live example: https://dotnetfiddle.net/mBfHru
Another way is to do an aggregation using Aggregate. This means maintaining a final Result list and a Curr list where you can aggregate your sequences, adding them to the Result list as you find discontinuities. This method looks a little closer to your original
public static List<Item[]> GetContiguousSequences3(Item []items)
{
var res = items.Aggregate(new {Result = new List<Item[]>(), Curr = new List<Item>()}, (agg, item) => {
if(!agg.Curr.Any() || agg.Curr.Last().Stop == item.Start) {
agg.Curr.Add(item);
} else {
agg.Result.Add(agg.Curr.ToArray());
agg.Curr.Clear();
agg.Curr.Add(item);
}
return agg;
});
res.Result.Add(res.Curr.ToArray()); // Remember to add the last group
return res.Result;
}
Live example: https://dotnetfiddle.net/HL0VyJ
You can implement ContiguousSplit as a corutine: let's loop over source and either add item into current range or return it and start a new one.
private static IEnumerable<Item[]> ContiguousSplit(IEnumerable<Item> source) {
List<Item> current = new List<Item>();
foreach (var item in source) {
if (current.Count > 0 && current[current.Count - 1].Stop != item.Start) {
yield return current.ToArray();
current.Clear();
}
current.Add(item);
}
if (current.Count > 0)
yield return current.ToArray();
}
then if you want materialization
List<Item[]> GetContiguousSequences(Item []items) => ContiguousSplit(items).ToList();
Your solution is okay. I don't think that LINQ adds any simplification or clarity in this situation. Here is a fast solution that I find intuitive:
static List<Item[]> GetContiguousSequences(Item[] items)
{
var result = new List<Item[]>();
int start = 0;
while (start < items.Length) {
int end = start + 1;
while (end < items.Length && items[end].Start == items[end - 1].Stop) {
end++;
}
int len = end - start;
var a = new Item[len];
Array.Copy(items, start, a, 0, len);
result.Add(a);
start = end;
}
return result;
}

Linq - Get all items between 2 matching elements

Provided a list, I want to select all items between the 2 given. (including the begin and end params)
My current solution is as follows:
private IEnumerable<string> GetAllBetween(IEnumerable<string> list, string begin, string end)
{
bool isBetween = false;
foreach (string item in list)
{
if (item == begin)
{
isBetween = true;
}
if (item == end)
{
yield return item;
yield break;
}
if (isBetween)
{
yield return item;
}
}
}
But surely there must be a pretty linq query that accomplishes the same thing?
You can nearly use SkipWhile and TakeWhile, but you want the last item as well - you want the functionality of TakeUntil from MoreLINQ. You can then use:
var query = source.SkipWhile(x => x != begin)
.TakeUntil(x => x == end);
static IEnumerable<T> GetAllBetween<T>( this List<T> list, T a, T b )
{
var aOffset = list.IndexOf( a );
var bOffset = list.IndexOf( b );
// what to do if one or all items not found?
if( -1 == aOffset || -1 == bOffset )
{
// for this example I will return an empty array
return new T[] { };
}
// what to do if a comes after b?
if( aOffset > bOffset )
{
// for this example i'll simply swap them
int temp = aOffset;
aOffset = bOffset;
bOffset = temp;
}
return list.GetRange( aOffset, bOffset - aOffset );
}
I think a simple Skip, Take should do it. I normally take it for paging ASP.NET resultsites.
var startIndex = list.IndexOf(begin);
var endIndex = list.IndexOf(end);
var result = list.Skip(startIndex + 1).Take(endIndex - 1 - startIndex);

Get previous and next item in a IEnumerable using LINQ

I have an IEnumerable of a custom type. (That I've gotten from a SelectMany)
I also have an item (myItem) in that IEnumerable that I desire the previous and next item from the IEnumerable.
Currently, I'm doing the desired like this:
var previousItem = myIEnumerable.Reverse().SkipWhile(
i => i.UniqueObjectID != myItem.UniqueObjectID).Skip(1).FirstOrDefault();
I can get the next item by simply ommitting the .Reverse.
or, I could:
int index = myIEnumerable.ToList().FindIndex(
i => i.UniqueObjectID == myItem.UniqueObjectID)
and then use .ElementAt(index +/- 1) to get the previous or next item.
Which is better between the two options?
Is there an even better option available?
"Better" includes a combination of performance (memory and speed) and readability; with readability being my primary concern.
First off
"Better" includes a combination of performance (memory and speed)
In general you can't have both, the rule of thumb is, if you optimise for speed, it'll cost memory, if you optimise for memory, it'll cost you speed.
There is a better option, that performs well on both memory and speed fronts, and can be used in a readable manner (I'm not delighted with the function name, however, FindItemReturningPreviousItemFoundItemAndNextItem is a bit of a mouthful).
So, it looks like it's time for a custom find extension method, something like . . .
public static IEnumerable<T> FindSandwichedItem<T>(this IEnumerable<T> items, Predicate<T> matchFilling)
{
if (items == null)
throw new ArgumentNullException("items");
if (matchFilling == null)
throw new ArgumentNullException("matchFilling");
return FindSandwichedItemImpl(items, matchFilling);
}
private static IEnumerable<T> FindSandwichedItemImpl<T>(IEnumerable<T> items, Predicate<T> matchFilling)
{
using(var iter = items.GetEnumerator())
{
T previous = default(T);
while(iter.MoveNext())
{
if(matchFilling(iter.Current))
{
yield return previous;
yield return iter.Current;
if (iter.MoveNext())
yield return iter.Current;
else
yield return default(T);
yield break;
}
previous = iter.Current;
}
}
// If we get here nothing has been found so return three default values
yield return default(T); // Previous
yield return default(T); // Current
yield return default(T); // Next
}
You can cache the result of this to a list if you need to refer to the items more than once, but it returns the found item, preceded by the previous item, followed by the following item. e.g.
var sandwichedItems = myIEnumerable.FindSandwichedItem(item => item.objectId == "MyObjectId").ToList();
var previousItem = sandwichedItems[0];
var myItem = sandwichedItems[1];
var nextItem = sandwichedItems[2];
The defaults to return if it's the first or last item may need to change depending on your requirements.
Hope this helps.
For readability, I'd load the IEnumerable into a linked list:
var e = Enumerable.Range(0,100);
var itemIKnow = 50;
var linkedList = new LinkedList<int>(e);
var listNode = linkedList.Find(itemIKnow);
var next = listNode.Next.Value; //probably a good idea to check for null
var prev = listNode.Previous.Value; //ditto
By creating an extension method for establishing context to the current element you can use a Linq query like this:
var result = myIEnumerable.WithContext()
.Single(i => i.Current.UniqueObjectID == myItem.UniqueObjectID);
var previous = result.Previous;
var next = result.Next;
The extension would be something like this:
public class ElementWithContext<T>
{
public T Previous { get; private set; }
public T Next { get; private set; }
public T Current { get; private set; }
public ElementWithContext(T current, T previous, T next)
{
Current = current;
Previous = previous;
Next = next;
}
}
public static class LinqExtensions
{
public static IEnumerable<ElementWithContext<T>>
WithContext<T>(this IEnumerable<T> source)
{
T previous = default(T);
T current = source.FirstOrDefault();
foreach (T next in source.Union(new[] { default(T) }).Skip(1))
{
yield return new ElementWithContext<T>(current, previous, next);
previous = current;
current = next;
}
}
}
You could cache the enumerable in a list
var myList = myIEnumerable.ToList()
iterate over it by index
for (int i = 0; i < myList.Count; i++)
then the current element is myList[i], the previous element is myList[i-1], and the next element is myList[i+1]
(Don't forget about the special cases of the first and last elements in the list.)
You are really over complicating things:
Sometimes just a for loop is going to be better to do something, and I think provide a clearer implementation of what you are trying to do/
var myList = myIEnumerable.ToList();
for(i = 0; i < myList.Length; i++)
{
if(myList[i].UniqueObjectID == myItem.UniqueObjectID)
{
previousItem = myList[(i - 1) % (myList.Length - 1)];
nextItem = myList[(i + 1) % (myList.Length - 1)];
}
}
Here is a LINQ extension method that returns the current item, along with the previous and the next. It yields ValueTuple<T, T, T> values to avoid allocations. The source is enumerated once.
/// <summary>
/// Projects each element of a sequence into a tuple that includes the previous
/// and the next element.
/// </summary>
public static IEnumerable<(T Previous, T Current, T Next)> WithPreviousAndNext<T>(
this IEnumerable<T> source, T firstPrevious = default, T lastNext = default)
{
ArgumentNullException.ThrowIfNull(source);
(T Previous, T Current, bool HasPrevious) queue = (default, firstPrevious, false);
foreach (var item in source)
{
if (queue.HasPrevious)
yield return (queue.Previous, queue.Current, item);
queue = (queue.Current, item, true);
}
if (queue.HasPrevious)
yield return (queue.Previous, queue.Current, lastNext);
}
Usage example:
var source = Enumerable.Range(1, 5);
Console.WriteLine($"Source: {String.Join(", ", source)}");
var result = source.WithPreviousAndNext(firstPrevious: -1, lastNext: -1);
Console.WriteLine($"Result: {String.Join(", ", result)}");
Output:
Source: 1, 2, 3, 4, 5
Result: (-1, 1, 2), (1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, -1)
To get the previous and the next of a specific item, you could use tuple deconstruction:
var (previous, current, next) = myIEnumerable
.WithPreviousAndNext()
.First(e => e.Current.UniqueObjectID == myItem.UniqueObjectID);
CPU
Depends entirely on where the object is in the sequence. If it is located at the end I would expect the second to be faster with more than a factor 2 (but only a constant factor). If it is located in the beginning the first will be faster because you don't traverse the whole list.
Memory
The first is iterating the sequence without saving the sequence so the memory hit will be very small. The second solution will take as much memory as the length of the list * references + objects + overhead.
I thought I would try to answer this using Zip from Linq.
string[] items = {"nought","one","two","three","four"};
var item = items[2];
var sandwiched =
items
.Zip( items.Skip(1), (previous,current) => new { previous, current } )
.Zip( items.Skip(2), (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
This will return a anonymous type {previous,current,next}.
Unfortunately this will only work for indexes 1,2 and 3.
string[] items = {"nought","one","two","three","four"};
var item = items[4];
var pad1 = Enumerable.Repeat( "", 1 );
var pad2 = Enumerable.Repeat( "", 2 );
var padded = pad1.Concat( items );
var next1 = items.Concat( pad1 );
var next2 = items.Skip(1).Concat( pad2 );
var sandwiched =
padded
.Zip( next1, (previous,current) => new { previous, current } )
.Zip( next2, (pair,next) => new { pair.previous, pair.current, next } )
.FirstOrDefault( triplet => triplet.current == item );
This version will work for all indexes.
Both version use lazy evaluation courtesy of Linq.
Here are some extension methods as promised. The names are generic and reusable with any type simple and there are lookup overloads to get at the item needed to get the next or previous items. I would benchmark the solutions and then see where you could squeeze cycles out.
public static class ExtensionMethods
{
public static T Previous<T>(this List<T> list, T item) {
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, T item) {
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T Previous<T>(this List<T> list, Func<T, Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) - 1;
return index > -1 ? list[index] : default(T);
}
public static T Next<T>(this List<T> list, Func<T,Boolean> lookup) {
var item = list.SingleOrDefault(lookup);
var index = list.IndexOf(item) + 1;
return index < list.Count() ? list[index] : default(T);
}
public static T PreviousOrFirst<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(item);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, T item) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(item);
return next == null ? list.Last() : next;
}
public static T PreviousOrFirst<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var previous = list.Previous(lookup);
return previous == null ? list.First() : previous;
}
public static T NextOrLast<T>(this List<T> list, Func<T,Boolean> lookup) {
if(list.Count() < 1)
throw new Exception("No array items!");
var next = list.Next(lookup);
return next == null ? list.Last() : next;
}
}
And you can use them like this.
var previous = list.Previous(obj);
var next = list.Next(obj);
var previousWithLookup = list.Previous((o) => o.LookupProperty == otherObj.LookupProperty);
var nextWithLookup = list.Next((o) => o.LookupProperty == otherObj.LookupProperty);
var previousOrFirst = list.PreviousOrFirst(obj);
var nextOrLast = list.NextOrLast(ob);
var previousOrFirstWithLookup = list.PreviousOrFirst((o) => o.LookupProperty == otherObj.LookupProperty);
var nextOrLastWithLookup = list.NextOrLast((o) => o.LookupProperty == otherObj.LookupProperty);
I use the following technique:
var items = new[] { "Bob", "Jon", "Zac" };
var sandwiches = items
.Sandwich()
.ToList();
Which produces this result:
Notice that there are nulls for the first Previous value, and the last Next value.
It uses the following extension method:
public static IEnumerable<(T Previous, T Current, T Next)> Sandwich<T>(this IEnumerable<T> source, T beforeFirst = default, T afterLast = default)
{
var sourceList = source.ToList();
T previous = beforeFirst;
T current = sourceList.FirstOrDefault();
foreach (var next in sourceList.Skip(1))
{
yield return (previous, current, next);
previous = current;
current = next;
}
yield return (previous, current, afterLast);
}
If you need it for every element in myIEnumerable I’d just iterate through it keeping references to the 2 previous elements. In the body of the loop I'd do the processing for the second previous element and the current would be its descendant and first previous its ancestor.
If you need it for only one element I'd choose your first approach.

Use LINQ to group a sequence of numbers with no gaps

With this array int[]{ 1, 2, 3, 4, 7, 8, 11, 15,16,17,18 };
How can i convert to this string array "1-4","7-8","11","15-18"
Suggestions ? Linq ?
var array = new int[] { 1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18 };
var result = string.Join(",", array
.Distinct()
.OrderBy(x => x)
.GroupAdjacentBy((x, y) => x + 1 == y)
.Select(g => new int[] { g.First(), g.Last() }.Distinct())
.Select(g => string.Join("-", g)));
with
public static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> GroupAdjacentBy<T>(
this IEnumerable<T> source, Func<T, T, bool> predicate)
{
using (var e = source.GetEnumerator())
{
if (e.MoveNext())
{
var list = new List<T> { e.Current };
var pred = e.Current;
while (e.MoveNext())
{
if (predicate(pred, e.Current))
{
list.Add(e.Current);
}
else
{
yield return list;
list = new List<T> { e.Current };
}
pred = e.Current;
}
yield return list;
}
}
}
}
You don't need Linq; in fact, the easiest solution requires knowing about three positions in the array (your starting number, current number and the next number after the current), for which Enumerables are not well-suited.
Try this:
var start = 0;
var end = 0;
var write = false;
var builder = new StringBuilder();
for(var i=0; i<array.Length; i++)
{
//arranged this way to avoid ArrayOutOfBoundException
//if the next index doesn't exist or isn't one greater than the current,
//the current index is the end of our incremental range.
if(i+1 == array.Length || array[i+1] > array[i] + 1)
{
end = i;
write = true;
}
if(write)
{
if(end - start == 0) //one number
builder.Append(String.Format("{0}, ", array[start]);
else //multi-number range
builder.Append(String.Format("{0}-{1}, ", array[start], array[end]);
start = i+1;
end = i+1; //not really necessary but avoids any possible case of counting backwards
write = false;
}
}
You can rearrange this to reduce nesting of code, continue early in the loop logic, and remove a few vars; you'll gain a few millis of execution time. You'll also need to trim the last two characters (a trailing comma and space) off the end of the StringBuilder before getting the String out.
Here is a cut at it:
public static IEnumerable<string> ToRanges(this IEnumerable<int> values)
{
int? start = null, end = null;
foreach (var value in values.OrderBy(vv => vv))
{
if (!start.HasValue)
{
start = value;
}
else if (value == (end ?? start) + 1)
{
end = value;
}
else
{
yield return end.HasValue
? String.Format("{0}-{1}", start, end)
: String.Format("{0}", start);
start = value;
end = null;
}
}
if (start.HasValue)
{
yield return end.HasValue
? String.Format("{0}-{1}", start, end)
: String.Format("{0}", start);
}
}
What's the algorithm you want to implement? Figure out what you want to happen, then see if it could be made clearer with a LINQ translation. Here's something non-LINQ that could give you an idea.
int[] array = { 1, 2, 3, 4, 7, 8, 11, 15, 16, 17, 18};
List<string> ranges = new List<string>();
// code assumes array is not zero-length, is distinct, and is sorted.
// to do: handle scenario as appropriate if assumptions not valid
Action<int, int, List<string>> addToRanges = (first, last, list) =>
{
if (last == first)
list.Add(last.ToString());
else
list.Add(string.Format("{0}-{1}", first, last)); ;
};
int firstItem = array[0];
int lastItem = firstItem;
foreach (int item in array.Skip(1))
{
if (item > lastItem + 1)
{
addToRanges(firstItem, lastItem, ranges);
firstItem = lastItem = item;
}
else
{
lastItem = item;
}
}
addToRanges(firstItem, lastItem, ranges);
// return ranges or ranges.ToArray()

Categories