Find the longest sequence in a List<int> - c#

I have the following list:
List<int> days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
I want to get the start and end numbers of the longest sequence. For the above example I should get the (4, 8). If two sequences are available with the same length, I want the first one.
Note: the list will always have numbers in an increasing order.
so far I have tried this:
List<Tuple<int, int>> seqs = new List<Tuple<int, int>>();
int _start = 0;
for (int i = 0; i <= days.Count; i++)
{
if (i == 0)
{
_start = days[i];
continue;
}
if (i < days.Count)
{
if (days[i] == days[i - 1] + 1)
continue;
else
{
seqs.Add(new Tuple<int, int>(_start, days[i - 1]));
_start = days[i];
}
}
else
{
seqs.Add(new Tuple<int, int>(_start, days[i - 1]));
}
}
var largestSeq = seqs
.OrderByDescending(s => s.Item2 - s.Item1)
.FirstOrDefault();

this solution is shorter but uses a side effect, so it cannot be parallelized:
var days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
var groupNumber = 0;
var longestGroup = days
.Select((x, i) => new
{
Item = x,
Index = i
})
.GroupBy(x => x.Index == 0 || x.Item - days[x.Index - 1] == 1
? groupNumber :
++groupNumber)
.OrderByDescending(x => x.Count())
.First()
.Select(x => x.Item)
.ToArray();
Console.WriteLine(longestGroup.First()+", "+longestGroup.Last());
output:
4, 8
this version does not use a side effect:
var days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
var groupEnds = days
.Select((x, i) => new
{
Item = x,
Index = i
})
.Where(x => x.Index > 0)
.Where(x => x.Item - days[x.Index - 1] > 1)
.Select(x => x.Index)
.Concat(new[]{days.Count})
.ToArray();
var groupBounds =
new[]{new{First=0,Last=groupEnds[0]-1}}
.Concat(groupEnds
.Select((x,i) => new{Item=x,Index=i})
.Where(x => x.Index > 0)
.Select(x => new{First=groupEnds[x.Index-1],Last=x.Item-1})
)
.ToArray();
var longestGroup = groupBounds
.OrderByDescending(x => x.Last - x.First)
.First();
Console.WriteLine(days[longestGroup.First] + ", " + days[longestGroup.Last]);
output:
4, 8

My version, which looks pretty similar to to #Gurgadurgen's.
List<int> days = new List<int> { 1, 4, 5, 6, 7, 8, 20, 24, 25, 26, 30 };
int longestSequenceLength = 0;
int startIndexOfLongestSequence = 0;
int currentSequenceLength = 0;
int currentStartSequenceIndex = 0;
for (int i = 0; i < days.Count; i++) {
if (i == 0 || days[i] != days[i - 1] + 1) {
currentSequenceLength = 1;
currentStartSequenceIndex = i;
}
else {
currentSequenceLength++;
}
if (currentSequenceLength > longestSequenceLength) {
longestSequenceLength = currentSequenceLength;
startIndexOfLongestSequence = currentStartSequenceIndex;
}
}
Console.WriteLine(string.Join(",",days.Skip(startIndexOfLongestSequence)
.Take(longestSequenceLength)));

int longestSeqStart = days[0];
int longestSeqEnd = days[0];
int curSeqStart = days[0];
int curSeqEnd = days[0];
int lastVal = days[0];
for(int i = 1; i < days.Count(); i++)
{
if(days[i] == lastVal + 1)
{
curSeqEnd = days[i];
if(curSeqEnd - curSeqStart > longestSeqEnd - longestSeqStart )
{
longestSeqStart = curSeqStart;
longestSeqEnd = curSeqEnd;
}
}
else
{
curSeqStart = curSeqEnd = days[i];
}
lastVal = days[i];
}
Haven't tested it, but I've stared at it for a good five minutes, and it seems pretty sound. I'll go run some tests in ideone and come back to edit them in :P
[EDIT] Tested it, and it indeed works. For a list of integers, "days"
{1,2,3,4,5,12,13,14,15,16,17,18,2,3,4,5}
It spits out 12-18, which is the proper answer.
I'll just post the Ideone link here.
[EDIT 2] Note that this is scarcely optimized, and could (and should) be rendered down further before use in actual code. For example, I realized only after writing this up that "lastVal" isn't actually even needed, as we can just check the value of "days" at the last index (i-1).
This should only serve as a logical basis for which to solve the problem you have, here. Not as a final solution.

Related

Select group from a List<T> matching a condition that cannot be directly specified on a property

I have following class specifying a data record
class DataRecord
{
public double MeasuredValue { get; set; }
public DateTime MeasurementDate { get; set; }
}
I want to select the records that were taken in a range of one hour (the date doesn't matter), where the count of the records taken is maximal.
Example:
MeasurementDate: 2019/01/31 11:50
MeasurementDate: 2019/02/02 17:21
MeasurementDate: 2019/03/01 17:59
MeasurementDate: 2019/03/12 10:54
MeasurementDate: 2019/05/28 11:15
Expected output: 1, 4, 5 (because within the span 10:50 - 11:50 3 measurements were made)
The code I came up with is
List<DataRecord> records = new List<DataRecord>();
var maxRecordsInAnHour = records.GroupBy(x => x.MeasurementDate.Hour)
.Aggregate((g1, g2) => { return g2.Count() > g1.Count() ? g2 : g1; });
This code returns either 2 and 3 or 1 and 5 (depending on the order) as I group by the Hour property and just records with the same value in Hour are grouped.
How can I adjust my code to get the expected output?
I will propose 2 solutions for your problem, depend on the length of your list one will be better.
Initialize :
var myList = new List<DataRecord>
{
new DataRecord
{
MeasurementDate = new DateTime(2019, 1, 31, 11, 50, 0)
},
new DataRecord
{
MeasurementDate = new DateTime(2019, 1, 31, 17, 21, 0)
},
new DataRecord
{
MeasurementDate = new DateTime(2019, 1, 31, 17, 59, 0)
},
new DataRecord
{
MeasurementDate = new DateTime(2019, 1, 31, 10, 54, 0)
},
new DataRecord
{
MeasurementDate = new DateTime(2019, 1, 31, 11, 54, 0)
},
};
List<DataRecord> result = new List<DataRecord>();
Solution 1 :
var minimumMinutes = myList.Min(x => x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute);
var maximumMinutes = myList.Max(x => x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute);
for (int minutes = minimumMinutes; minutes < maximumMinutes; minutes++)
{
var list = myList.Where(x =>
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute <= minutes + 60 &&
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute >= minutes);
if (result.Count < list.Count())
{
result = list.ToList();
}
}
Solution 2 :
foreach (var dataRecord in myList)
{
var minutes = dataRecord.MeasurementDate.Hour * 60 + dataRecord.MeasurementDate.Minute;
var before = myList.Where(x =>
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute >= minutes - 60 &&
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute <= minutes).ToList();
var after = myList.Where(x =>
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute <= minutes + 60 &&
x.MeasurementDate.Hour * 60 + x.MeasurementDate.Minute >= minutes).ToList();
if (before.Count > result.Count ||
after.Count > result.Count)
{
result = before.Count > after.Count ? before.ToList() : after.ToList();
}
}
As I mentioned in comments this code is not performance efficient, but this will do the trick.
// DummyData
List<DateTime> dates = new List<DateTime>
{
DateTime.Parse("2019/01/31 11:50"),
DateTime.Parse("2019/02/02 17:21"),
DateTime.Parse("2019/03/01 17:59"),
DateTime.Parse("2019/03/12 10:54"),
DateTime.Parse("2019/05/28 11:15"),
};
// Storage for final Output
List<DateTime> finalOp = new List<DateTime>();
// Main logic goes here
// foreach Hour in list we will compare that with every other Hour in list
// and it is in 1 hour range we will add it to list
foreach (DateTime dateTime in dates)
{
List<DateTime> temp = new List<DateTime>();
foreach (DateTime innerDateTime in dates)
{
// find the difference between two hours
var timeSpan = dateTime.TimeOfDay - innerDateTime.TimeOfDay;
// add it to same list if we have +/- 1 Hour difference
if (timeSpan.TotalHours <= 1 && timeSpan.TotalHours >= -1)
{
temp.Add(innerDateTime);
}
}
// once we have final group for date we will check if this has maximum number of common dates around
// if so replace it with previous choice
if (finalOp.Count < temp.Count)
{
finalOp = temp;
}
}
// print the results
foreach (var data in finalOp)
{
Console.WriteLine(data.ToShortTimeString());
}
For the 10,000 items of myList collection this solution can be fast:
private static List<DataRecord> Get(List<DataRecord> myList)
{
var ordered = myList.OrderBy(x => x.MeasurementDate.TimeOfDay).ToList();
int i = 0;
int count = myList.Count();
var temp =
(from s in ordered
let index = ++i
let next = ordered.ElementAtOrDefault(index != count ? index : 0)
select new
{
Cur = s.MeasurementDate,
Diff = index != count
? (next.MeasurementDate.TimeOfDay - s.MeasurementDate.TimeOfDay).TotalMinutes
: 24 * 60 - (ordered.ElementAtOrDefault(count - 1).MeasurementDate.TimeOfDay - ordered.ElementAtOrDefault(0).MeasurementDate.TimeOfDay).TotalMinutes
}).ToList();
Dictionary<int, int> dict = new Dictionary<int, int>();
count = 0;
double minutes = 0;
for (int index = 0; index < temp.Count(); index++)
{
for (int j = index; j < temp.Count(); j++)
{
minutes += temp[j].Diff;
if (minutes > 60)
{
dict.Add(index, count);
count = 0;
minutes = 0;
break;
}
else
{
count++;
}
}
}
var max = dict.First(d => d.Value == dict.Values.Max());
var finalResult = ordered.Skip(max.Key).Take(max.Value + 1).ToList();
return finalResult;
}
Please note it returns ordered results.

Select last wrong item from the list

I have a list with items, that have a Time property. If I want to select all items where Time is equal or bigger then some startTime, then I write something like this:
var newList = list.Where(i => (i.Time >= startTime));
But now I also want to get the last item, where the time is smaller than startTime. Is there a better way to implement this?
For example I have list where items have Time from this list:
[5:32, 5:46, 5:51, 6:07, 6:11, 6:36]
We specify a startTime as 6:00.
Now we want to get this times:
[5:51, 6:07, 6:11, 6:36]
Getting the whole List at once:
var newList = list
.OrderByDescending(i => i.Time)
.Take(list.Count(j => j.Time >= startTime) + 1)
.OrderBy(k => k.Time); //Optional
With Cognition's suggestion:
var newList = list
.OrderBy(i => i.Time)
.Skip(list.Count(j => j.Time < startTime - 1));
var result=list
.Where(i=>i.Time<startTime)
.OrderBy(i=>i.Time)
.Last()
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
or
var result=list
.OrderBy(i=>i.Time)
.Last(i=>i.Time<startTime)
.Concat(list
.OrderBy(i=>i.Time)
.Where(i=>i.Time>=startTime)
);
var smallerThan = list
.Where(i => i.Time < startTime)
.OrderByDescending(o => o.Time)
.Take(1)
.Concat(list.Where(i => i.Time => startTime));
As your list is in order of the property you want to find, you can do something along the lines of
List<int> things = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8 };
int threshold = 4;
var newThings = things.Skip(things.FindIndex(x => x >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings));
Which outputs
3, 4, 5, 6, 7, 8
Extending it to use a class with a Time property which happens to be a TimeSpan:
class Z
{
public TimeSpan Time { get; set; }
};
class Program
{
static void Main(string[] args)
{
Random rand = new Random();
List<Z> zs = new List<Z>();
for (int i = 0; i < 10; i++)
{
zs.Add(new Z { Time = new TimeSpan(i, rand.Next(0,61), rand.Next(0,61)) });
}
TimeSpan threshold = new TimeSpan(4,0,0);
var newThings = zs.Skip(zs.FindIndex(x => x.Time >= threshold) - 1);
Console.WriteLine(string.Join(", ", newThings.Select(x => x.Time.ToString("c"))));
Console.ReadLine();
}
}
Sample output:
03:03:57, 04:09:37, 05:14:44, 06:58:55, 07:40:33, 08:37:06, 09:10:06
Many of the answers seem to require a descending orderby. But you can easily avoid this with a clean one liner and good efficiency:
var newList = list.Skip(list.Count(j => j.Time < startTime) - 1);
var newList = list
.Where(i => (i.Time >= startTime))
.ToList()
.Add(list
.Where(i => (i.Time < startTime))
.OrderByDescending(o => o.Time)
.FirstOrDefault()
)
int lastItemIndex = list.OrderBy(D => D.TimeOfDay).ToList()
.FindLastIndex(D => D.TimeOfDay < startTime);
var newList = list.Where(D => list.IndexOf(D) > lastItemIndex);

Unique Values from a list of of items in C#

I am having a list:
list = { 1,1,1,2,3,3,3,4,4,5,6,6,6}
Now I want to extract list of unique values.
Final list contains {2,5} only.
How can I do that through LINQ or any other function.
One way would be to use the GroupBy method and filter only those which have a count of 1:
var unique = list.GroupBy(l => l)
.Where(g => g.Count() == 1)
.Select(g => g.Key);
Try This:
List<int> list = new List<int>(new int[]{ 1, 1, 1, 2, 3, 3, 3, 4, 4, 5, 6, 6, 6});
List<int> unique=new List<int>();
int count=0;
bool dupFlag = false;
for (int i = 0; i < list.Count; i++)
{
count = 0;
dupFlag = false;
for(int j=0;j<list.Count;j++)
{
if (i == j)
continue;
if (list[i].Equals(list[j]))
{
count++;
if (count >= 1)
{
dupFlag = true;
break;
}
}
}
if (!dupFlag)
unique.Add(list[i]);
}
Try this code:
var lstUnique =
from t1 in list
group t1 by t1 into Gr
where Gr.Count() == 1
select Gr.Key;

Using LINQ how can I prioritise an IEnumerable based on ranges?

If my data is a List<Cat> and each Cat has an age of 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
I want to re-order the list according to the ranges 1-3, 4-8, 9-10
such that the resulting list will be
3, 2, 1, 8, 7, 6, 5, 4, 10, 9
How can I do that?
I first considered creating three queries:
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c => 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
but then I don't know how to combine the results using LINQ, as I have three IEnumerable<Cat>.
What's the correct method to use, or do I have to use a foreach?
You can use Concat.
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c => 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
var result = band1.Concat(band2).Concat(band3);
Create a GetRange(Cat cat) method order your list by it:
myCatList.OrderBy(cat=>GetRange(cat));
This is how you would use a single LINQ statement to do what you're asking for.
var ordered = items
.OrderBy(x => {
if(x <= 3) return 0;
if(x <= 8) return 1;
return 2;
})
.ThenByDescending(x => x);
Alternatively (assuming the items are already in descending order):
var ordered = myList
.OrderBy(x => {
if (x <= 3) { return 1; }
if (x <= 8) { return 2; }
return 3;
});
//define your ranges
var ranges = new[] { 3,8,10};
var grouped = listOfCats.GroupBy( x => ranges.FirstOrDefault( r => r >= x.Age ) );
All you need is to concat the IEnumerable<int> with IEnumerable<int>.Concat()
var band1 = myList.Where(c => c <= 3);
var band2 = myList.Where(c => c >= 4 && c <= 8);
var band3 = myList.Where(c => c >= 9);
List<int> finalList = band1.Concat(band2).Concat(band3).ToList();

Finding if a target number is the sum of two numbers in an array via LINQ and get the and Indices

Hello I am new to Linq , I found this thread which explain 90% of what I need
https://stackoverflow.com/questions/2331882?tab=newest#tab-top , thanks "pdr"
but what I need is to get the Indices too , here is my modification I get the index of the first number but I don't know how to get the index of the second number
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var result = from item in numbers.Select((n1, idx) =>
new { n1,idx, shortList = numbers.Take(idx) })
from n2 in item.shortList
where item.n1 + n2 == 7
select new { nx1 = item.n1,index1=item.idx, nx2=n2 };
SelectMany is what you need ...
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int target = 7;
var query = numbers
.SelectMany((num1,j) => numbers.Select((num2,i) => new {n1=num1, n2=num2, i=i, j=j}))
.Where(x => x.n1 + x.n2 == target && x.i < x.j);
foreach (var x in query)
Console.WriteLine(x.n1 + " and " + x.n2 + " occur at " + x.i + "," + x.j );
This will give you a pair of items, each of which containing a value from the array and its index. The pairs are limited to where the sums of the values equal a target. The && on the where clause eliminates duplicates and self-matching (applicable for even targets).
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int target = 7;
var query = from item1 in numbers.Select((number, index) => new { Number = number, Index = index })
from item2 in numbers.Select((number, index) => new { Number = number, Index = index })
where item1.Number + item2.Number == target
&& item1.Index < item2.Index
select new { Item1 = item1, Item2 = item2 };
foreach (var itemPair in query)
{
Console.WriteLine("{0}:{1}\t{2}:{3}",
itemPair.Item1.Index,
itemPair.Item1.Number,
itemPair.Item2.Index,
itemPair.Item2.Number);
}
If it's definitely over an array then you can just do:
var result = from index1 in Enumerable.Range(0, numbers.Length)
from index2 in Enumerable.Range(index1 + 1,
numbers.Length - index - 1)
where numbers[index1] + numbers[index2] == targetNumber
select new { index1, index2,
value1 = numbers[index1], value2 = numbers[index2] };
Otherwise, you can use the Select form that includes the index twice:
var result = from pair1 in numbers.Select((value, index) => new { value, index})
from pair2 in numbers.Skip(pair1.index + 1)
.Select((value, index) =>
new { value, index = index - pair2.index - 1})
where pair1.value + pair2.value == targetNumber
select new { index1 = pair1.index, index2 = pair2.index,
value1 = pair1.value, value2 = pair2.value };
Both of these are really ugly though...

Categories