Is it possible to use one LINQ query to do the same?
var ints = new []{1,2,3,4,5};
var odd = from i in ints where i%2==1 select i;
var even = from i in ints where i%2==0 select i;
var q = from s in new[]{""}
select new {oddCount = odd.Count(), evenCount = even.Count()};
Console.Write(q);
Edit: Want to get this
Count() already allows you to specify a predicate. So you can combine the above in one linq like this:
var ints = new[] { 1, 2, 3, 4, 5 };
Console.Write($"Odd={ints.Count(i => i % 2 == 1)}, Even={ints.Count(i => i % 2 == 0)}");
Also note that it will be considerably faster than doing a Where() as counting is easier to perform than actually returning matching elements.
Edit
If all you want is a single linq query, you could do the following clever trick:
var ints = new[] { 1, 2, 3, 4, 5 };
var Odd = ints.Count(i => i % 2 == 1);
Console.Write($"Odd={Odd}, Even={ints.Length - Odd}");
Sounds like a perfect candidate for Aggregate:
var ints = new[] { 1, 2, 3, 4, 5 };
var info = ints.Aggregate(
new { oddCount = 0, evenCount = 0 }, (a, i) =>
new { oddCount = a.oddCount + (i & 1), evenCount = a.evenCount + ((i & 1) ^ 1) });
Console.WriteLine(info);
prints
{ oddCount = 3, evenCount = 2 }
You could do it one query like this:
var q = ints.Select(i => new { Number = i, Type = (i % 2 == 0) ? "Even" : "Odd" }).GroupBy(i => i.Type).Select(g => new { Type = g.Key, Count = g.Count() });
This would return a list though, with 'Type' and 'Count', as shown below.
If you're looking for a simple object as you currently have, you can use something simpler like this:
var q = new { OddCount = ints.Count(i => i % 2 != 0), EvenCount = ints.Count(i => i % 2 == 0) };
This would be a single object with "OddCount" and "EventCount" properties.
Here's another way that does only a single iteration over the original list.
var ints = new []{1,2,3,4,5};
string[] parities = { "even", "odd" };
var result = ints
.GroupBy(i => i % 2)
.Select(g => new { Name = parities[g.Key], Count = g.Count() });
You just move your queries directly into the select
var q = from s in new[] { "" }
select new {
oddCount = (from i in ints where i % 2 == 1 select i).Count(),
evenCount = (from i in ints where i % 2 == 0 select i).Count()};
int odd = 0;
int even = 0;
(from s in ints
let evens = s % 2 == 0 ? even++ : even
let odds = s % 2 != 0 ? odd++ : odd
select true).ToList();
With this you have the values loaded in even and odd.
This approach has the advantage it only iterates the array once
Related
I have my data in the following format..
UserId Property1 Property2 Property3 Testval
1 1 1 10 35
2 1 2 3 45
3 2 5 6 55
and so on..
I have several criterias, a couple of example are as below..
a) Where Property1=1 and Property3=10
b) Where Property1!=1 and Property2=5
What I need is the count of users & testval average who fall within these criterias and also of all the rest who do not.
So, result data structure would be as follows..
User Count
Criteria Users
a 100
b 200
rest 1000
TestVal Average
Criteria avg
a 25
b 45
rest 15
I know how to get the userlist for the specific criterias separately.
data.Where(w=>w.Property1==1).Select(s=>s.UserId).ToList()
But how do I get the usercount and avg val and more importantly the same for the rest of users.
Any help is sincerely appreciated
Thanks
Looks like you are seeking for group by criteria. Something like this:
var result = data.GroupBy(x =>
x.Property1 == 1 && x.Property3 == 10 ? 0 :
x.Property1 != 1 && x.Property2 == 5 ? 1 :
// ...
-1)
.Select(g => new
{
Criteria = g.Key,
Users = g.Count(),
Avg = g.Average(x => x.Testval),
})
.ToList();
To get the count/average for a specific criterion, it's easy
Func<MyUser, boolean> criterion1 = user => user.Property1==1;
var avg = data.Where(criterion1).Average(user => user.Testval);
var count = data.Where(criterion1).Count();
(this will enumerate the data twice, so if that's an issue, you can materialize the data before the calculations)
If you want to evaluate multiple criteria (and don't want to repeat this code as many times as there are criteria), you can put them in a dictionary, and loop over them:
var criteria = new Dictionary<string, Func<MyUser, boolean>>{
{ "criterion1", user => user.Property1==1 },
{ "criterion2", user => user.Property1!=1 && user.Property2=5 },
//...
}
foreach (var criterion in criteria){
var avg = data.Where(criterion.Value).Average(user => user.Testval);
var count = data.Where(criterion).Count();
Console.WriteLine($"{criterion.Key} average: {avg}, count: {count}");
}
You can also put the results in another dictionary, something like
var results = new Dictionary<string, Tuple<string, string>>();
foreach (var criterion in criteria){
var avg = data.Where(criterion.Value).Average(user => user.Testval);
var count = data.Where(criterion).Count();
results.Add(criterion.Key, Tuple.Create(avg, count));
}
and then make a better looking report, or you can even create a specific result class that will be easier to print after.
To get the rest (the count/average of the data that does not fit any predicate) you can loop through all the predicates, negating them;
var query = data;
foreach (var criterion in criteria.Values){
query = query.Where(user => !criterion(user));
}
var restAvg = query.Average(user => user.Testval);
var count = query.Count();
You can do it using select new to return new anonymously typed objects which contains your criteria.
public void Test()
{
var list = new List<User>();
list.Add(new User {UserId = 1, Property1 = 1, Property2 = 1, Property3 = 10, Testval = 35});
list.Add(new User {UserId = 1, Property1 = 2, Property2 = 2, Property3 = 3, Testval = 45});
list.Add(new User {UserId = 1, Property1 = 5, Property2 = 5, Property3 = 6, Testval = 55});
Func<User, bool> crit = u => u.Property1 == 1 & u.Property3==10;
var zz = list.Where(crit)
.GroupBy(t => new {ID = t.UserId})
.Select(w => new
{
average = w.Average(a => a.Testval),
count = w.Count(),
rest = list.Except(list.Where(crit)).Average(a => a.Testval)
}).Single();
}
How to determine a range in a list of integer follow specific pattern.
For example, we have a list like this:
List<int> ints = new List<int>(){4,5,2,6,8,4,5,6,5,6,8,9,9};
Exists and Any could check if an element satisfies specific condition.
But what if I want to know if there is any three items in row that incremental values(plus 1): here they are {4, 5, 6}.
Patrick already answered your question with a good solution, but if you're really looking for a LINQ-only way, you could use Aggregate:
var inputs = new List<IEnumerable<int>>
{
new List<int>{ 4,5,2,6,8,4,5,6,5,6,8,9,9 },
new List<int>{ 1,2,3 },
new List<int>{ 1,2,4 },
};
foreach(var input in inputs)
{
var result = input.Aggregate(Enumerable.Empty<int>(),
(agg, cur) => agg.Count() == 3 ? agg
: agg.Any() && cur == agg.Last() + 1
? agg.Concat(new []{cur})
: new []{cur});
Console.WriteLine(result.Count() >= 3 ? String.Join(", ", result) : "not found");
}
Another way is to take all of the groups of 3 and then see which group(s) meet your n, n+1 and n+2 rule
var results = Enumerable.Range(0, ints.Count - 3)
.Select(n => ints.Skip(n).Take(3).ToArray())
.Where(three => three[0]+1 == three[1] && three[0]+2 == three[2])
.ToArray();
I would drop the LINQ requirement. It is very hard, maybe even impossible. A regular foreach statement is better suited for this:
List<int> sequence = new List<int>();
List<int> longestSequence = null;
int previous = 0;
foreach (int i in ints)
{
if (i != previous + 1 && sequence.Count > 0)
{
if (longestSequence == null || longestSequence.Count < sequence.Count)
{
longestSequence = sequence;
}
sequence = new List<int>();
}
sequence.Add(i);
previous = i;
}
Take this example:
int[] queryValues1 = new int[10] {0,1,2,3,4,5,6,7,8,9};
int[] queryValues2 = new int[100]; // this is 0 to 100
for (int i = 0; i < queryValues2.Length; i++)
{
queryValues2[i] = i;
}
var queryResult =
from qRes1 in queryValues1
from qRes2 in queryValues2
where qRes1 * qRes2 == 12
select new { qRes1, qRes2 };
foreach (var result in queryResult)
{
textBox1.Text += result.qRes1 + " * " + result.qRes2 + " = 12" + Environment.NewLine;
}
Obviously this code will result in:
1 * 12 = 12
2 * 6 = 12
3 * 4 = 12
4 * 3 = 12
6 * 2 = 12
But what I need is only the first 3 lines. That is I do not want if 2*6 = 12 the query checks if 6*2 is also 12. Is there a way to filter this in the LINQ query or I have to do it in the foreach loop afterward?
My question just is a sample to show what I mean. so I want to know the way of doing such thing no matter what is the type of object being linqed to!
In general the simple solution would be more where conditions since the where clauses are what by definition cause LINQ to skip iterations:
var queryResult =
from qRes1 in queryValues1
from qRes2 in queryValues1
where qRes1 * qRes2 == 12
&& qRes1 <= Math.Sqrt(12)
select new { qRes1, qRes2 };
You could use .Distinct() and create your own IEqualityComparer that compares objects based on what 'equals' means in your case.
So, for your original example:
class PairSetEqualityComparer : IEqualityComparer<Tuple<int, int>>
{
public bool Equals(Tuple<int, int> x, Tuple<int, int> y)
{
return (x.Item1 == y.Item1 && x.Item2 == y.Item2) ||
(x.Item1 == y.Item2 && x.Item2 == y.Item1);
}
public int GetHashCode(Tuple<int, int> obj)
{
return obj.Item1*obj.Item2;
}
}
And, you use it like this:
var queryResult =
(from qRes1 in queryValues1
from qRes2 in queryValues2
where qRes1 * qRes2 == 12
select new Tuple<int, int>(qRes1, qRes2)).Distinct(new PairSetEqualityComparer());
TakeWhile(condition):Returns elements from a sequence as long as a specified condition is true, and then skips the remaining elements.
foreach (var result in queryResult.TakeWhile(x => x.qRes1 <= Math.Sqrt(12)))
var nums = new[]{ 1, 2, 3, 4, 5, 6, 7};
var pairs = /* some linq magic here*/ ;
=>
pairs = { {1, 2}, {3, 4}, {5, 6}, {7, 0} }
The elements of pairs should be either two-element lists, or instances of some anonymous class with two fields, something like new {First = 1, Second = 2}.
None of the default linq methods can do this lazily and with a single scan. Zipping the sequence with itself does 2 scans and grouping is not entirely lazy. Your best bet is to implement it directly:
public static IEnumerable<T[]> Partition<T>(this IEnumerable<T> sequence, int partitionSize) {
Contract.Requires(sequence != null)
Contract.Requires(partitionSize > 0)
var buffer = new T[partitionSize];
var n = 0;
foreach (var item in sequence) {
buffer[n] = item;
n += 1;
if (n == partitionSize) {
yield return buffer;
buffer = new T[partitionSize];
n = 0;
}
}
//partial leftovers
if (n > 0) yield return buffer;
}
Try this:
int i = 0;
var pairs =
nums
.Select(n=>{Index = i++, Number=n})
.GroupBy(n=>n.Index/2)
.Select(g=>{First:g.First().Number, Second:g.Last().Number});
int[] numbers = new int[] { 1, 2, 3, 4, 5, 6, 7 };
var result = numbers.Zip(numbers.Skip(1).Concat(new int[] { 0 }), (x, y) => new
{
First = x,
Second = y
}).Where((item, index) => index % 2 == 0);
(warning: looks ugly)
var pairs = x.Where((i, val) => i % 2 == 1)
.Zip(
x.Where((i, val) => i % 2 == 0),
(first, second) =>
new
{
First = first,
Second = second
})
.Concat(x.Count() % 2 == 1 ? new[]{
new
{
First = x.Last(),
Second = default(int)
}} : null);
This might be a bit more general than you require - you can set a custom itemsInGroup:
int itemsInGroup = 2;
var pairs = nums.
Select((n, i) => new { GroupNumber = i / itemsInGroup, Number = n }).
GroupBy(n => n.GroupNumber).
Select(g => g.Select(n => n.Number).ToList()).
ToList();
EDIT:
If you want to append zeros (or some other number) in case the last group is of a different size:
int itemsInGroup = 2;
int valueToAppend = 0;
int numberOfItemsToAppend = itemsInGroup - nums.Count() % itemsInGroup;
var pairs = nums.
Concat(Enumerable.Repeat(valueToAppend, numExtraItems)).
Select((n, i) => new { GroupNumber = i / itemsInGroup, Number = n }).
GroupBy(n => n.GroupNumber).
Select(g => g.Select(n => n.Number).ToList()).
ToList();
public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
{
return InSetsOf(source, max, false, default(T));
}
public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max, bool fill, T fillValue)
{
var toReturn = new List<T>(max);
foreach (var item in source)
{
toReturn.Add(item);
if (toReturn.Count == max)
{
yield return toReturn;
toReturn = new List<T>(max);
}
}
if (toReturn.Any())
{
if (fill)
{
toReturn.AddRange(Enumerable.Repeat(fillValue, max-toReturn.Count));
}
yield return toReturn;
}
}
usage:
var pairs = nums.InSetsOf(2, true, 0).ToArray();
IList<int> numbers = new List<int> {1, 2, 3, 4, 5, 6, 7};
var grouped = numbers.GroupBy(num =>
{
if (numbers.IndexOf(num) % 2 == 0)
{
return numbers.IndexOf(num) + 1;
}
return numbers.IndexOf(num);
});
If you need the last pair filled with zero you could just add it before doing the grouping if the listcount is odd.
if (numbers.Count() % 2 == 1)
{
numbers.Add(0);
}
Another approach could be:
var groupedIt = numbers
.Zip(numbers.Skip(1).Concat(new[]{0}), Tuple.Create)
.Where((x,i) => i % 2 == 0);
Or you use MoreLinq that has a lot of useful extensions:
IList<int> numbers = new List<int> {1, 2, 3, 4, 5, 6, 7};
var batched = numbers.Batch(2);
var w =
from ei in nums.Select((e, i) => new { e, i })
group ei.e by ei.i / 2 into g
select new { f = g.First(), s = g.Skip(1).FirstOrDefault() };
var nums = new float[] { 1, 2, 3, 4, 5, 6, 7 };
var enumerable =
Enumerable
.Range(0, nums.Length)
.Where(i => i % 2 == 0)
.Select(i =>
new { F = nums[i], S = i == nums.Length - 1 ? 0 : nums[i + 1] });
Another option is to use the SelectMany LINQ method. This is more for those who wish to iterate through a list of items and for each item return 2 or more of it's properties. No need to loop through the list again for each property, just once.
var list = new [] {//Some list of objects with multiple properties};
//Select as many properties from each Item as required.
IEnumerable<string> flatList = list.SelectMany(i=> new[]{i.NameA,i.NameB,i.NameC});
Another simple solution using index and index + 1.
var nums = Enumerable.Range(1, 10);
var pairs = nums.Select((item, index) =>
new { First = item, Second = nums.ElementAtOrDefault(index + 1) })
.SkipLastN(1);
pairs.ToList().ForEach(p => Console.WriteLine($"({p.First}, {p.Second}) "));
Last item is invalid and must be removed with SkipLastN().
this gives all possible pairs(vb.net):
Dim nums() = {1, 2, 3, 4, 5, 6, 7}
Dim pairs = From a In nums, b In nums Where a <> b Select a, b
Edit:
Dim allpairs = From a In nums, b In nums Where b - a = 1 Select a, b
Dim uniquePairs = From p In allpairs Where p.a Mod 2 <> 0 Select p
note: the last pair is missing, working on it
Edit:
union uniquePairs with the pair {nums.Last,0}
I'm using 2 similar LINQ queries to return a result, the only difference is the where clause (&& s.OptIn == "Yes"). Is there a way to execute this with only one query?
Instead of having a result of
A 2
B 3
and another result of
A 1
B 1
I want to have
A 2 1
B 3 1
Here's the LINQ:
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
group 1 by new { ce.EventID } into d
select new {
EventID = d.Key.EventID,
Count = d.Count()
};
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
&& s.OptIn == "Yes"
group 1 by new { ce.EventID } into d
select new {
EventID = d.Key.EventID,
Count = d.Count()
};
You can supply a predicate in the Count method. An example is below:
List<int> list = new List<int> { 1, 2, 3, 4, 5 };
var counts = new { CountAll = list.Count(), CountEven = list.Count(i => i % 2 == 0) };
Console.WriteLine(counts.CountEven);
A similar query written for Linq-To-Entities also worked and produced working SQL.
I haven't fully reconstructed your sample, but you should be able to rework it to something like this.
var result = from s in pdc.ScanLogs
from e in pdc.Exhibits
from ce in pdc.ClientEvents
where s.ExhibitID == e.ExhibitID
&& e.ClientEventID == ce.ClientEventID
group new { s, e, ce } by new { ce.EventID } into d
select new
{
EventID = d.Key.EventID,
Count = d.Count(),
CountOptIn = d.Count(item => item.s.OptIn == "Yes")
};
IQueryable<ScanLog> scanlogs = pdc.ScanLogs;
if (filter) scanlogs = scanlogs.Where(...);
var result = from s in scanlogs
...