Reputation: 7702
I'm creating an application for a manufacturing company. We're implementing WECO (Western Electric Co.) rules for statistical process control. One of the rules states that if any 2 out of 3 consecutive values exceeds some target value, issue an alarm.
So, to keep things simple, say I have the following list of values:
List<double> doubleList = new List<double>()
{
.89,.91,.93,.95,1.25,.76,.77,.78,.77,1.01,.96,.99, .88,.88,.96,.89,1.01
};
From this list, I want to pull out all sequences where any 2 out of 3 consecutive values are greater than .94. The Linq query should return the following six sequences:
.93, .95, 1.25 (3rd, 4th and 5th values)
.95, 1.25, .76 (4th, 5th and 6th values)
.77, 1.01, .96 (9th, 10th and 11th values)
1.01, .96, .99 (10th, 11th and 12th values)
.96, .99, .88 (11th, 12th and 13th values)
.96, .89, 1.01 (15th, 16th and 17th values)
Notice the last sequence. The two values, out of the three are not consecutive. That's ok, they don't need to be. Just any 2 out of 3 consecutive.
I've thought about starting with the first value, taking three and checking for any two out of that three, moving to the second value, doing the same, moving to the third and doing the same etc. in a loop This would work of course, but would be slow. I'm assuming there must be a faster way to do this.
Upvotes: 2
Views: 187
Reputation: 160912
You could write an extension method:
public static IEnumerable<IEnumerable<double>> GetTroubleSequences(this IEnumerable<double> source, double threshold)
{
int elementCount = source.Count();
for (int idx = 0; idx < elementCount - 2; idx++)
{
var subsequence = source.Skip(idx).Take(3);
if (subsequence.Aggregate(0, (count, num) => num > threshold ? count + 1 : count) >= 2)
{
yield return subsequence.ToList();
}
}
}
Now you can just use it on your input list:
var problemSequences = doubleList.GetTroubleSequences(0.94);
Note that above extension method is inefficient, if your input list is very long you should consider just a regular for loop with a sliding window so you only iterate over the sequence once - or rewrite the extension method accordingly (i.e. limit to ICollection
input so you can use the indexer instead of having to use Skip
and Take
).
Update:
Here's a version requiring IList
so we can use the indexer:
public static IEnumerable<IEnumerable<double>> GetTroubleSequences(this IList<double> source, double threshold)
{
for (int idx = 0; idx < source.Count - 2; idx++)
{
int count = 0;
for (int i = idx; i < idx + 3; i++)
if (source[i] > threshold)
count++;
if (count >= 2)
yield return new[] { source[idx], source[idx + 1], source[idx + 2] };
}
}
This version is traversing the list once, and for each item in the list evaluates the next 3 items starting with the current item, so still O(n).
Upvotes: 4
Reputation: 32333
You can do this by using Enumerable.Zip
extension method:
var result = doubleList
.Zip(doubleList.Skip(1), (first, second) => new[] { first, second })
.Zip(doubleList.Skip(2), (temp, third) => new[] { temp[0], temp[1], third })
.Where(i => i.Count(d => d > .94) >= 2);
I want to mention that this method is also inefficient. This is just a try to implement it by using LINQ.
Upvotes: 0