Linq - Get all items between 2 matching elements - c#

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);

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;
}

Check if string contains characters in certain order in C#r

I have a code that's working right now, but it doesn't check if the characters are in order, it only checks if they're there. How can I modify my code so the the characters 'gaoaf' are checked in that order in the string?
Console.WriteLine("5.feladat");
StreamWriter sw = new StreamWriter("keres.txt");
sw.WriteLine("gaoaf");
string s = "";
for (int i = 0; i < n; i++)
{
s = zadatok[i].nev+zadatok[i].cim;
if (s.Contains("g") && s.Contains("a") && s.Contains("o") && s.Contains("a") && s.Contains("f") )
{
sw.WriteLine(i);
sw.WriteLine(zadatok[i].nev + zadatok[i].cim);
}
}
sw.Close();
You can convert the letters into a pattern and use Regex:
var letters = "gaoaf";
var pattern = String.Join(".*",letters.AsEnumerable());
var hasletters = Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase);
For those that needlessly avoid .*, you can also solve this with LINQ:
var ans = letters.Aggregate(0, (p, c) => p >= 0 ? s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase) : p) != -1;
If it is possible to have repeated adjacent letters, you need to complicate the LINQ solution slightly:
var ans = letters.Aggregate(0, (p, c) => {
if (p >= 0) {
var newp = s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase);
return newp >= 0 ? newp+1 : newp;
}
else
return p;
}) != -1;
Given the (ugly) machinations required to basically terminate Aggregate early, and given the (ugly and inefficient) syntax required to use an inline anonymous expression call to get rid of the temporary newp, I created some extensions to help, an Aggregate that can terminate early:
public static TAccum AggregateWhile<TAccum, T>(this IEnumerable<T> src, TAccum seed, Func<TAccum, T, TAccum> accumFn, Predicate<TAccum> whileFn) {
using (var e = src.GetEnumerator()) {
if (!e.MoveNext())
throw new Exception("At least one element required by AggregateWhile");
var ans = accumFn(seed, e.Current);
while (whileFn(ans) && e.MoveNext())
ans = accumFn(ans, e.Current);
return ans;
}
}
Now you can solve the problem fairly easily:
var ans2 = letters.AggregateWhile(-1,
(p, c) => s.IndexOf(c.ToString(), p+1, StringComparison.InvariantCultureIgnoreCase),
p => p >= 0
) != -1;
Why not something like this?
static bool CheckInOrder(string source, string charsToCheck)
{
int index = -1;
foreach (var c in charsToCheck)
{
index = source.IndexOf(c, index + 1);
if (index == -1)
return false;
}
return true;
}
Then you can use the function like this:
bool result = CheckInOrder("this is my source string", "gaoaf");
This should work because IndexOf returns -1 if a string isn't found, and it only starts scanning AFTER the previous match.

Identifying strings and manipulating the correctly

To preface this I am pulling records from a database. The CaseNumber column will have a unique identifier. However, multiple cases related to ONE Event will have very similar case numbers in which the last two digits will be the next following number. Example:
TR42X2330789
TR42X2330790
TR42X2330791
TR51C0613938
TR51C0613939
TR51C0613940
TR51C0613941
TR51C0613942
TR52X4224749
As you can see we would have to group these records into three groups. Currently my function is really messy and I it does not account for the scenario in which a group of case numbers is followed by another group of case numbers. I was wondering if anybody had any suggestions as to how to tackle this. I was thinking about putting all the case numbers in an array.
int i = 1;
string firstCaseNumber = string.Empty;
string previousCaseNumber = string.Empty;
if (i == 1)
{
firstCaseNumber = texasHarrisPublicRecordInfo.CaseNumber;
i++;
}
else if (i == 2)
{
string previousCaseNumberCode = firstCaseNumber.Remove(firstCaseNumber.Length - 3);
int previousCaseNumberTwoCharacters = Int32.Parse(firstCaseNumber.Substring(Math.Max(0, firstCaseNumber.Length - 2)));
string currentCaseNumberCode = texasHarrisPublicRecordInfo.CaseNumber.Remove(texasHarrisPublicRecordInfo.CaseNumber.Length - 3);
int currentCaselastTwoCharacters = Int32.Parse(texasHarrisPublicRecordInfo.CaseNumber.Substring(Math.Max(0, texasHarrisPublicRecordInfo.CaseNumber.Length - 2)));
int numberPlusOne = previousCaseNumberTwoCharacters + 1;
if (previousCaseNumberCode == currentCaseNumberCode && numberPlusOne == currentCaselastTwoCharacters)
{
//Group offense here
i++;
needNewCriminalRecord = false;
}
else
{
//NewGRoup here
}
previousCaseNumber = texasHarrisPublicRecordInfo.CaseNumber;
i++;
}
else
{
string beforeCaseNumberCode = previousCaseNumber.Remove(previousCaseNumber.Length - 3);
int beforeCaselastTwoCharacters = Int32.Parse(previousCaseNumber.Substring(Math.Max(0, previousCaseNumber.Length - 2)));
string currentCaseNumberCode = texasHarrisPublicRecordInfo.CaseNumber.Remove(texasHarrisPublicRecordInfo.CaseNumber.Length - 3);
int currentCaselastTwoCharacters = Int32.Parse(texasHarrisPublicRecordInfo.CaseNumber.Substring(Math.Max(0, texasHarrisPublicRecordInfo.CaseNumber.Length - 2)));
int numberPlusOne = beforeCaselastTwoCharacters + 1;
if (beforeCaseNumberCode == currentCaseNumberCode && numberPlusOne == currentCaselastTwoCharacters)
{
i++;
needNewCriminalRecord = false;
}
else
{
needNewCriminalRecord = true;
}
}
If you do not really care about performance you can use LINQ .GroupBy() and .ToDictionary() methods and create dictionary with lists. Something among the lines of :
string[] values =
{
"TR42X2330789",
"TR42X2330790",
"TR42X2330791",
"TR51C0613938",
"TR51C0613939",
"TR51C0613940",
"TR51C0613941",
"TR51C0613942",
"TR52X4224749"
};
Dictionary<string, List<string>> grouppedValues = values.GroupBy(v =>
new string(v.Take(9).ToArray()), // key - first 9 chars
v => v) // value
.ToDictionary(g => g.Key, g => g.ToList());
foreach (var item in grouppedValues)
{
Console.WriteLine(item.Key + " " + item.Value.Count);
}
Output :
TR42X2330 3
TR51C0613 5
TR52X4224 1
I would create a general puropose extension method:
static IEnumerable<IEnumerable<T>> GroupByConsecutiveKey<T, TKey>(this IEnumerable<T> list, Func<T, TKey> keySelector, Func<TKey, TKey, bool> areConsecutive)
{
using (var enumerator = list.GetEnumerator())
{
TKey previousKey = default(TKey);
var currentGroup = new List<T>();
while (enumerator.MoveNext())
{
if (!areConsecutive(previousKey, keySelector(enumerator.Current)))
{
if (currentGroup.Count > 0)
{
yield return currentGroup;
currentGroup = new List<T>();
}
}
currentGroup.Add(enumerator.Current);
previousKey = keySelector(enumerator.Current);
}
if (currentGroup.Count != 0)
{
yield return currentGroup;
}
}
}
And now you would use it like:
var grouped = data.GroupByConsecutiveKey(item => item, (k1, k2) => areConsecutive(k1, k2));
A quick hack for areConsecutive could be:
public static bool Consecutive(string s1, string s2)
{
if (s1 == null || s2 == null)
return false;
if (s1.Substring(0, s1.Length - 2) != s2.Substring(0, s2.Length - 2))
return false;
var end1 = s1.Substring(s1.Length - 2, 2);
var end2 = s2.Substring(s2.Length - 2, 2);
if (end1[1]!='0' && end2[1]!='0')
return Math.Abs((int)end1[1] - (int)end2[1]) == 1;
return Math.Abs(int.Parse(end1) - int.Parse(end2)) == 1;
}
Note that I am considering that Key can take any shape. If the alphanumeric code has the same pattern always then you can probably make this method a whole lot prettier or just use regular expressions.

Move item to first in array

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;

Categories