Select last wrong item from the list - c#

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

Related

Summing up double lists in a Linq GroupBy

I need to group a large number of records which were recorded every minute into daily and bind them to a chart. These records have two fields the datetime value and the double list. I've tried something like this:
var result = Alldatas
.AsEnumerable()
.GroupBy(r => r.TimeStamp.Day)
.Select(x => new {
Day = x.Key,
Value = x.Sum(r => r.Value.Sum())
})
.OrderBy(x => x.Day)
.ToList();
The problem is that the list items in the double list is being summed up each other into a single double value. The correct result is that double lists should be adding up each other into a single double list for each day. Is there a way to achieve this? Thanks.
If I understood you correctly, here is the code:
var result = Alldatas
.AsEnumerable()
.GroupBy(r => r.TimeStamp.Day)
.Select(x => new {
Day = x.Key,
// Using Aggregate method
Value = x
.Select(y => y.Value)
.Aggregate(new List<double>(), (acc, list) =>
{
for (int i = 0; i < list.Count; ++i)
{
if (acc.Count == i) acc.Add(0);
acc[i] += list[i];
}
return acc;
}),
// Pure LINQ, using GroupBy
Value2 = x
// Create tuple (index, value) for each double
.SelectMany(y => y.Value.Select((z, i) => Tuple.Create(i, z)))
// Group by index
.GroupBy(y => y.Item1)
// Sum values within groups
.Select(y => y.Select(z => z.Item2).Sum())
// Make list
.ToList()
})
.OrderBy(x => x.Day)
.ToList();
For input:
var Alldatas = new []
{
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } },
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } },
new { TimeStamp = DateTime.Now, Value = new List<double> { 1, 2, 3 } }
};
This will produce following result:
new[] {
new { Day = 20, Value = new[] {3,6,9}, Value2 = new[] {3,6,9} }
}

How to append more rows to a Linq list after the condition is met

I have a list which contains items (let assume 1000 items). I want to select data from that list where TimeInSecond matches the criteria.
newList = oldList.Where(x => x.TimeInSecond >= 30 && x.TimeInSecond <= 90).ToList();
// the above query returns 20 items (from 10 to 20)
However, I need to append next N number of rows from the oldList
// This is just an example of what I need, for example 10 next more items
newList = oldList.Where(x => x.TimeInSecond >= 30 && x.TimeInSecond <= 90).GetNextMoreItems(10).ToList() ;
// the above query returns (20 + 10) 30 items (from 1 to 30)
Since you mentioned in the comments, you want 10 additional elements from the last element where condition is true (satisfied), you could do something like this.
// Get the elements where condition is satisfied.
newList = oldList
.Where(x => x.TimeInSecond >= 30 && x.TimeInSecond <= 90)
.ToList() ;
// Find the index of last element and then take 10 elements from then on.
newList = newList.Concat(oldList
.Skip(oldList.ToList().IndexOf(newList.Last() + 1))
.Take(10))
.ToList();
Check sample Demo to see how it works.
You can use Union or AddRange to append your new 10 items to your existing list. Of course, the data-types has to be the same. Here's an example with int type:
List<int> l1 = new List<int>() {1,2,3 };
List<int> l2 = new List<int>() { 1,4,5,6};
l1.AddRange(l2); // l1 now contains {1,2,3,1,4,5,6}
// will remove duplicates using default Equality Comparer
var l3 = l1.Union(l2).ToList(); // contains {1,2,3,4,5,6}
With your example, it may look like this:
var list1 = oldList.Where(x => x.TimeInSecond >= 30 && x.TimeInSecond <= 90);
var list2 = GetNextMoreItems(10).ToList();
var finalList = list1.AddRange(list2);
Edit based on comment
To exclude items that you've already selected, you can use Except, which is opposite of Intersect . So, for the int example above:
var l4 = l3.Except(l1); // contains {4,5,6}
And then, to pick certain number of elements, append .Take(count).
If you want those that match and N of which other items in the collection you can simply do:
Option 1: where condition or counter
int n = 10;
int counter = 0;
var values = oldList.Where(x => (x.TimeInSecond >= 30 && x.TimeInSecond <= 90) ||
counter++ < n).ToList();
Option 2: use a helping method to avoid repeating condition
Func<dynamic, bool> condition = (x) => x.TimeInSecond >= 30 && x.TimeInSecond <= 90;
var result = oldList.Where(x => condition(x))
.Concat(oldList.Where(x => !condition(x)).Take(n));
If order matters and you want to take N items that come only after all those that match:
Option 1:
int n = 10;
var values = oldList.Select((item, index) => new { item, index })
.Where(x => x.item.TimeInSecond >= 30 && x.item.TimeInSecond <= 90);
var lastItem = values.Max(item => item.index);
var result = values.Select(item => item.item)
.Concat(oldList.Skip(lastItem + 1).ToList().Take(n));
Option 2: Maybe looks a bit cleaner.. not sure (and has to have the ToList() just before the concat:
int n = 1;
int lastItem = 0;
var result = oldList.Where((x, i) =>
{
var outcome = x.TimeInSecond >= 30 && x.TimeInSecond <= 90;
if (outcome)
lastItem = i;
return outcome;
}).ToList()
.Concat(oldList.Skip(lastItem + 1).ToList().Take(n)); //Result: 40, 50, 100 (from data below)
Tested with:
List<dynamic> oldList = new List<dynamic>()
{
new {TimeInSecond = 10},
new {TimeInSecond = 40},
new {TimeInSecond = 50},
new {TimeInSecond = 100},
new {TimeInSecond = 120},
};

finding all possible sum of two arrays element

I have two arrays and i am trying to get all possible sum of each element with other element of two array and index of each element
int[] width = new int[2] {10,20 };
int[] height = new int[2] {30,40 };
result should like this (value / indexes)
10 width0
10+20 width0+width1
10+30 width0+height0
10+40 width0+height1
10+20+30 width0+width1+height0
10+20+40 width0+width1+height1
10+20+30+40 width0+width1+height0+height1
And so for each element in two array
I tried using permutation but I get other output
It is more easy to get all combinations from one array than two arrays. And as we see, you need to store indices and array names along with the value of the elements in collections. So, in my opinion the best option is to combine these two arrays in one dictionary, where the key will be the value of the numbers and the value will be [ArrayName + Index of item] (f.e width0, height1 and so on....)
So, let's combine these arrays in one dictionary:
int[] width = new int[2] { 10, 20 };
int[] height = new int[2] { 30, 40 };
var widthDictionary = width.ToList().Select((number, index) => new { index, number })
.ToDictionary(key => key.number, value => string.Format("width{0}", value.index));
var heightDictionary = height.ToList().Select((number, index) => new { index, number })
.ToDictionary(key => key.number, value => string.Format("height{0}", value.index));
// And here is the final dictionary
var totalDictionary = widthDictionary.Union(heightDictionary);
Then add this method to your class: (source)
public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
{
return from m in Enumerable.Range(0, 1 << list.Count)
select
from i in Enumerable.Range(0, list.Count)
where (m & (1 << i)) != 0
select list[i];
}
Then send your dictionary as an argument to this method and project this collection as you want with the help of the Select() method:
var sumOfCombinations = GetPowerSet(totalDictionary.ToList())
.Where(x => x.Count() > 0)
.Select(x => new
{
Numbers = x.Select(pair => pair.Key).ToList(),
DisplayValues = x.Select(pair => pair.Value).ToList()
})
.ToList();
And at the end you can display expected result as this:
sumOfCombinations.ForEach(x =>
{
x.Numbers.ForEach(number => Console.Write("{0} ", number));
x.DisplayValues.ForEach(displayValue => Console.Write("{0} ", displayValue));
Console.WriteLine();
});
And, the result is:
This is a play off of #Farhad Jabiyev's answer.
Declares a class called IndexValuePair. and uses foreach on widthList and heightList. to populate the 'Index' property of item instance.
Note: Index is a string.
Class & Static Function
public class IndexValuePair
{
public string Index {get;set;}
public int Value {get;set;}
}
public static IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
{
return from m in Enumerable.Range(0, 1 << list.Count)
select
from i in Enumerable.Range(0, list.Count)
where (m & (1 << i)) != 0
select list[i];
}
Main (Console)
static void Main(string[] args)
{
int[] width = new int[2] { 10, 20 };
int[] height = new int[2] { 30, 40 };
var wholeList = width.Select(val => new IndexValuePair() { Index = "width", Value = val }).ToList();
var heightList = height.Select(val => new IndexValuePair() { Index = "height", Value = val }).ToList();
var iteration = 0;
wholeList.ForEach(ivp => { ivp.Index = ivp.Index + count; count = iteration + 1; });
iteration = 0;
heightList.ForEach(ipv => { ivp.Index = ivp.Index + count; count = iteration + 1; });
wholeList.AddRange(heightList);
var sumOfCombinations = GetPowerSet(wholeList).Where(x => x.Count() > 0)
.Select(x => new { Combination = x.ToList(), Sum = x.Sum(ivp => ivp.Value) }).ToList();
StringBuilder sb = new StringBuilder();
sumOfCombinations.ForEach(ivp =>
{
ivp.Combination.ForEach(pair => sb.Append(string.Format("{0} ", pair.Value)));
sb.Append(string.Format("= {0} = ", x.Sum));
ivp.Combination.ForEach(pair=> sb.Append(string.Format("{0} + ", pair.Index)));
sb.Length -= 3;
Console.WriteLine(sb);
sb.Clear();
});
var key = Console.ReadKey();
}

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

Find the most occurring number in a List<int>

Is there a quick and nice way using linq?
How about:
var most = list.GroupBy(i=>i).OrderByDescending(grp=>grp.Count())
.Select(grp=>grp.Key).First();
or in query syntax:
var most = (from i in list
group i by i into grp
orderby grp.Count() descending
select grp.Key).First();
Of course, if you will use this repeatedly, you could add an extension method:
public static T MostCommon<T>(this IEnumerable<T> list)
{
return ... // previous code
}
Then you can use:
var most = list.MostCommon();
Not sure about the lambda expressions, but I would
Sort the list [O(n log n)]
Scan the list [O(n)] finding the longest run-length.
Scan it again [O(n)] reporting each number having that run-length.
This is because there could be more than one most-occurring number.
Taken from my answer here:
public static IEnumerable<T> Mode<T>(this IEnumerable<T> input)
{
var dict = input.ToLookup(x => x);
if (dict.Count == 0)
return Enumerable.Empty<T>();
var maxCount = dict.Max(x => x.Count());
return dict.Where(x => x.Count() == maxCount).Select(x => x.Key);
}
var modes = { }.Mode().ToArray(); //returns { }
var modes = { 1, 2, 3 }.Mode().ToArray(); //returns { 1, 2, 3 }
var modes = { 1, 1, 2, 3 }.Mode().ToArray(); //returns { 1 }
var modes = { 1, 2, 3, 1, 2 }.Mode().ToArray(); //returns { 1, 2 }
I went for a performance test between the above approach and David B's TakeWhile.
source = { }, iterations = 1000000
mine - 300 ms, David's - 930 ms
source = { 1 }, iterations = 1000000
mine - 1070 ms, David's - 1560 ms
source = 100+ ints with 2 duplicates, iterations = 10000
mine - 300 ms, David's - 500 ms
source = 10000 random ints with about 100+ duplicates, iterations = 1000
mine - 1280 ms, David's - 1400 ms
Here is another answer, which seems to be fast. I think Nawfal's answer is generally faster but this might shade it on long sequences.
public static IEnumerable<T> Mode<T>(
this IEnumerable<T> source,
IEqualityComparer<T> comparer = null)
{
var counts = source.GroupBy(t => t, comparer)
.Select(g => new { g.Key, Count = g.Count() })
.ToList();
if (counts.Count == 0)
{
return Enumerable.Empty<T>();
}
var maxes = new List<int>(5);
int maxCount = 1;
for (var i = 0; i < counts.Count; i++)
{
if (counts[i].Count < maxCount)
{
continue;
}
if (counts[i].Count > maxCount)
{
maxes.Clear();
maxCount = counts[i].Count;
}
maxes.Add(i);
}
return maxes.Select(i => counts[i].Key);
}
Someone asked for a solution where there's ties. Here's a stab at that:
int indicator = 0
var result =
list.GroupBy(i => i)
.Select(g => new {i = g.Key, count = g.Count()}
.OrderByDescending(x => x.count)
.TakeWhile(x =>
{
if (x.count == indicator || indicator == 0)
{
indicator = x.count;
return true;
}
return false;
})
.Select(x => x.i);
Here's a solution I've written for when there are multiple most common elements.
public static List<T> MostCommonP<T>(this IEnumerable<T> list)
{
return list.GroupBy(element => element)
.GroupBy(group => group.Count())
.MaxBy(groups => groups.Key)
.Select(group => group.Key)
.ToList();
}

Categories