Check if a list of integers increments by one - c#

Is there a way, with LINQ, to check if a list of integers are "sequential" - ie 1,2,3,4,5 or 14,15,16,17,18?

You could do this via Enumerable.Zip:
bool sequential = values.Zip(values.Skip(1), (a,b) => (a+1) == b).All(x => x);
This works by taking each pair of values, and checking to see if the second is 1 more than the first, and returning booleans. If all pairs fit the criteria, the values are sequential.
Given that this is a list of integers, you can do this slightly more efficiently using:
bool sequential = values.Skip(1).Select((v,i) => v == (values[i]+1)).All(v => v);
This will only work on sequences which can be accessed by index. Note that we use values[i], not values[i-1], as the Skip call effectively shifts the indices.

bool isSequential = Enumerable.Range(values.Min(), values.Count())
.SequenceEqual(values);

One more option is to use Aggregate to iterate sequence only once.
Note that unlike All suggested by Reed Copsey Aggregate can't stop in the middle when condition fails...
var s = new int[] {3,4,5,6}.ToList();
var isSequential = s.Aggregate
(
new {PrevValue = 0, isFirst = true, Success = true} ,
(acc, current) =>
new {
PrevValue = current,
isFirst = false,
Success = acc.Success && (acc.isFirst || (acc.PrevValue == current - 1))
}
)
.Success;
Fancier version would be to have iterator that carries previous value along or special code that would split iterator on "First and the rest" allowing to implement Reed's solution with single iteration for any enumerable.

If you already know that the numbers you have in your list is unique, and also sorted, then the simplest check for sequential is just
lst[lst.Count - 1] - lst[0] == lst.Count - 1
Assume atleast 1 element in list.

Related

Ordering a list in-place by two properties

I have a class with two properties, Name and Position.
I would like to order the list with this class by Position, and the elements with the same position should be ordered by Name. I am working on a static list, so I would like to work in-place.
So far I managed to order the list by one property:
list.Sort((x, y) => x.Position.CompareTo(y.Position));
this code is working and I have the list ordered by Position, but I don't know how to implement the second part. I found this question, but I don't understand the answer.
Could anyone please help me?
I would use "OrderBy" and "ThenBy":
IEnumerable<Person> orderedPersons = persons.OrderBy(item => item.Position)
.ThenBy(item => item.Name);
The answer you linked to is correct. The key in sorting by multiple values is that the secondary property only matters if the primaries are equal. A psuedocode implementation of your sort comparison might be:
compare x and y position
if they differ, return order
else compare name, return order
On the Sort method, the code following the (x,y)=> must return 0 if the items are equal, a negative number if the first should be before the second, and a positive number if the second should come before the first. The CompareTo method will return -1, 0, or 1 based on these cases and its arguments. Since you need to compare two different properties, you need two calls to CompareTo. If you decided to add them together, you could have a case like this:
x.position < y.position (compare returns -1)
x.name > y.name (compare returns 1)
result 0, items are considered equal, where your rules
clearly say x should come first in this case.
To solve this issue, we need to make sure the Name comparison only matters when the positions are equal. Since CompareTo only returns -1, 0, or 1, if we multiply the position result by 2 (or any larger number) then the Name comparison will only change the result if the positions are equal. (Because -2 + 1 = -1 and 2 - 1 = 1)
So using the method in your original linked answer, your code would be something like:
list.Sort((x, y) =>
2 * x.Position.CompareTo(y.Position)
+ x.Name.CompareTo(y.Name));
list = list.OrderBy(item => item.Name).ToList();
list.Sort((x, y) => x.Position.CompareTo(y.Position));

Check if two lists contain the same numbers using LINQ

I'd like to know if there is a better way (still using LINQ) of achieving the following, which checks that both this have the same numbers in them:
var list1 = new int[] { 1, 2, 3, 4 };
var list2 = new int[] { 2, 1, 3, 4 };
return list1.Intersect(list2).Count() == list2.Count();
The above example would return true
I would use two HashSet<int> and the SetEquals method:
var l1Lookup = new HashSet<int>(list1);
var l2Lookup = new HashSet<int>(list2);
bool containsSame = l1Lookup.SetEquals(l2Lookup); // true
The SetEquals method ignores duplicate entries and the order of
elements in the other parameter. If the collection represented by
other is a HashSet collection with the same equality comparer as
the current HashSet object, this method is an O(n) operation.
Otherwise, this method is an O(n + m) operation, where n is the number
of elements in other and m is Count.
Your Count() approach can be inefficient if the sequences are large or/and they are not a collection but an expensive query. It can also be incorrect since the count of all items is not necessarily the count of intersecting items since Intersect removes duplicates.
I think this works but not sure it's efficient enough:
bool isEqual = list1.OrderBy(x=>x).SequenceEqual(list2.OrderBy(x=>x));

Transform sequence in Linq while being aware of each Select/SelectMany result

Here's my problem. I have one specific list, which I'll present as a int[] for simplicity's sake.
int[] a = {1,2,3,4,5};
Suppose I need to transform each item on this list, but depending on the situation, I may return an int or an array of ints.
As an example, suppose I need to return {v} if the value is odd, and {v,v+1} if the value is even. I've done this:
int[] b = a.SelectMany(v => v % 2 == 0 ? new int[] { v, v+1 } : new int[] { v })
.ToArray();
So if I run this, I'll get the expected response:
{1,2,3,3,4,5,5}
See that I have repeating numbers, right? 3 and 5. I don't want those repeating numbers. Now, you may tell me that I can just call .Distinct() after processing the array.
This is the problem. The SelectMany clause is fairly complex (I just made up a simpler example), and I definitely don't want to process 3 if it's already present in the list.
I could check if 3 is present in the original list. But if I got 3 in the SelectMany clause, I don't want to get it again. For instance, if I had this list:
int[] a = {1,2,3,4,5,2};
I would get this:
{1,2,3,3,4,5,5,2,3}
Thus returning v (my original value) and v+1 again at the end. Just so you can understand it better v+1 represents some processing I want to avoid.
Summarizing, this is what I want:
I have a list of objects. (Check)
I need to filter them, and depending on the result, I may need to return more than one object. (Check, used SelectMany)
I need them to be distinct, but I can't do that at the end of the process. I should be able to return just {v} if {v+1} already exists. (Clueless...)
One thing I thought about is writing a custom SelectMany which may suit my needs, but I want to be sure there's no built-in way to do this.
EDIT: I believe I may have mislead you guys with my example. I know how to figure out if v+1 is in a list. To be clear, I have one object which has 2 int properties, Id and IdParent. I need to "yield return" all the objects and their parents. But I just have the ParentId, which comes from the objects themselves. I'm able to know if v+1 is in the list because I can check if any object there has the same Id as the ParentId I'm checking.
ANSWER: I ended up using Aggregate, which can be used to do exactly what I'm looking for.
Does this simple loop with the HashSet<int> help?
int[] a = {1,2,3,4,5,2};
var aLookupList = new HashSet<int>();
foreach (int i in a)
{
bool isEven = i % 2 == 0;
if (isEven)
{
aLookupList.Add(i);
aLookupList.Add(i + 1);
}
else
{
aLookupList.Add(i);
}
}
var result = aLookupList.ToArray();
What about this using Aggregate method. You won't be processing numbers that are already in the list, wheather they were in the original list or as a result of applying (v + 1)
int[] v = { 1, 2, 3, 4, 5, 2 };
var result = v.Aggregate(new List<int>(),
(acc, next) =>
{
if (!acc.Contains(next))
return (next % 2 == 0) ? acc.Concat(new int[] { next, next + 1 }).ToList()
: acc.Concat(new int[] { next }).ToList();
else
return acc;
}).ToArray();
var existing = new HashSet<int>(a);
var result = existing
.Where(v => v % 2 == 0 && !existing.Contains(v + 1))
.Select(v => v + 1)
.Concat(existing)
.ToArray();
As I understand you have this input:
int[] a = {1,2,3,4,5};
And the output should also be {1,2,3,4,5} because you don't want duplicated numbers as you describe.
Because you use an array as input, you can try this code:
var output = a.SelectMany((x,i)=> x % 2 == 0 ? new []{x,x+1} :
i > 0 && a[i-1]==x-1 ? new int[]{} : new []{x});
//if the input is {1,2,4,5}
//The output is also {1,2,3,4,5}

This Lambda OrderBy seems superfluous in my case - is it?

I don't want to have any unnecessary code, but I want to be "safe," too. Empirical observations have shown that the OrderBy below does nothing - the List is already ordered correctly. Can I rely on that being the case, and remove that OrderBy line?
HashSet<int> hashSet = new HashSet<int>();
List<int> listInts = new List<int>();
using (var file = new System.IO.StreamReader(selectedFile)) {
string line;
int lineNum = 0;
int Offset = (int)numericUpDownLinesOfContext.Value;
while ((line = file.ReadLine()) != null) {
lineNum++;
if (line.Contains(PlatypusToSearchFor)) {
// This adds the lines before and after that will provide the desired context
// (N lines from the log file before and after the searched for value)
hashSet.UnionWith(Enumerable.Range(lineNum - Offset, Offset * 2 + 1));
}
}
// Remove any negative numbers, as well as 0, that might have been added
// (0, -1, -2, or -3 are all possibilities, but the first line is #1)
listInts = hashSet.Where(i => i >= 1).ToList();
// They seem to be ordered correctly already, but this is just in case:
listInts = listInts.OrderBy(i => i).ToList();
}
No, you shouldn't remove the OrderBy. HashSet does not guarantee any particular ordering. You may get lucky in testing, but you can't guarantee it will order things the way you expect it to.
From the MSDN documentation on HashSet (http://msdn.microsoft.com/en-us/library/bb359438.aspx):
A set is a collection that contains no duplicate elements, and whose
elements are in no particular order.
(emphasis added)
As already stated HashSet does not have any particular order. You could use a SortedSet instead if you need that behavior, and you would then not need the OrderBy.
A UnionWith operation will not preserve ordering. However you don't have to use the OrderBy line either, because .NET provides a SortedSet<T> class that exposes set operations and automatic sorting behavior.

Find Item With Max Value Less Than Another Value

I have an object with two doubles:
class SurveyData(){
double md;
double tvd;
}
I have a list of these values that is already sorted ascending. I would like to find and return the index of the object in the list with the maximum tvd value that is less than or equal to a double. How can I efficiently accomplish this task?
Assuming you've got LINQ and are happy to use TakeUntil from MoreLINQ, I suspect you want:
var maxCappedValue = values.TakeUntil(data => data.Tvd >= limit)
.LastOrDefault();
That will get you the first actual value rather than the index, but you could always do:
var maxCappedPair = values.Select((value, index) => new { value, index })
.TakeUntil(pair => pair.value.Tvd >= limit)
.LastOrDefault();
for the index/value pair. In both cases the result would be null if all values were above the limit.
Of course, it would be more efficient to use a binary search - but also slightly more complicated. You could create a "dummy" value with the limit TVD, then use List<T>.BinarySearch(dummy, comparer) where comparer would be an implementation of IComparer<SurveyData> which compared by TVD. You'd then need to check whether the return value was non-negative (exact match found) or negative (exact match not found, return value is complement of where it would be inserted).
The difference in complexity is between O(n) for the simple scan, or O(log n) for the binary search. Without knowing how big your list is (or how important performance is), it's hard to advise whether the extra implementation complexity of the binary search would be worth it.
First filter by the objects that are less than or equal to the filter value (Where), and then select the maximum of those objects' values.
Since it's already in ascending order, just iterate through the set until you find a value greater than the filter value, then return the previous index.
Here's a way to do it with Linq:
int indexOfMax =
data.Select((d, i) => new { Data = d, Index = i }) // associate an index with each item
.Where(item => item.Data.tvd <= maxValue) // filter values greater than maxValue
.Aggregate( // Compute the max
new { MaxValue = double.MinValue, Index = -1 },
(acc, item) => item.Data.tvd <= acc.MaxValue ? acc : new { MaxValue = item.Data.tvd, Index = item.Index },
acc => acc.Index);
But in a case like this, Linq is probably not the best option... a simple loop would be much clearer.

Categories

Resources