Using LINQ to count value frequency - c#

I have a table
ID|VALUE
VALUE is an integer field with possible values between 0 and 4. How can I query the count of each value?
Ideally the result should be an array with 6 elements, one for the count of each value and the last one is the total number of rows.

This simple program does just that:
class Record
{
public int Id { get; set; }
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Record> records = new List<Record>()
{
new Record() { Id = 1, Value = 0},
new Record() { Id = 2, Value = 1 },
new Record() { Id = 3, Value = 2 },
new Record() { Id = 4, Value = 3 },
new Record() { Id = 5, Value = 4 },
new Record() { Id = 6, Value = 2 },
new Record() { Id = 7, Value = 3 },
new Record() { Id = 8, Value = 1 },
new Record() { Id = 9, Value = 0 },
new Record() { Id = 10, Value = 4 }
};
var query = from r in records
group r by r.Value into g
select new {Count = g.Count(), Value = g.Key};
foreach (var v in query)
{
Console.WriteLine("Value = {0}, Count = {1}", v.Value, v.Count);
}
}
}
Output:
Value = 0, Count = 2
Value = 1, Count = 2
Value = 2, Count = 2
Value = 3, Count = 2
Value = 4, Count = 2
Slightly modified version to return an Array with only the count of values:
int[] valuesCounted = (from r in records
group r by r.Value
into g
select g.Count()).ToArray();
Adding the rows count in the end:
valuesCounted = valuesCounted.Concat(new[] { records.Count()}).ToArray();

Here is how you would get the number of rows for each value of VALUE, in order:
var counts =
from row in db.Table
group row by row.VALUE into rowsByValue
orderby rowsByValue.Key
select rowsByValue.Count();
To get the total number of rows in the table, you can add all of the counts together. You don't want the original sequence to be iterated twice, though; that would cause the query to be executed twice. Instead, you should make an intermediate list first:
var countsList = counts.ToList();
var countsWithTotal = countsList.Concat(new[] { countsList.Sum() });

Related

Linq query - List within List [duplicate]

This question already has answers here:
Group by in LINQ
(11 answers)
Closed 5 years ago.
I'm trying to select a list that contains Fund.Name and List<Investment>.
var funds = new List<Fund>
{
new Fund { Id = 1 , Name = "good" },
new Fund { Id = 2, Name = "bad" }
};
var investments = new List<Investment>
{
new Investment { Fund = funds[0], Value = 100 },
new Investment { Fund = funds[0], Value = 200 },
new Investment { Fund = funds[1], Value = 300 }
};
Then I'm trying to create the query with this:
var query = from f in funds
join i in investments
on f.Id equals i.Fund.Id
select new { f.Name, i };
I wanted something like this:
{ Name = good, {{ Id = 1, Value = 100 }, { Id = 1, Value = 200 }}},
{ Name = bad, { Id = 2, Value = 300 }}
But I'm getting something like this:
{ Name = good, { Id = 1, Value = 100 }},
{ Name = good, { Id = 1, Value = 200 }},
{ Name = bad, { Id = 2, Value = 300 }}
Try using GroupJoin.
var query = funds.GroupJoin(investments, f => f.Id, i => i.Fund.Id, (f, result) => new { f.Name, result });

Getting inappropriate rank while merging and arranging 2 list

Below is my models:
public class Test
{
public int TestId { get; set; }
public List<VariantsRank> VariantsRanks { get; set; }
}
public class VariantsRank
{
public int VariantId { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
I have an existing Test instance which contains the following values for VariantsRanks
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
I then need to merge the following VariantsRank
VariantId = 12, Name = "V3", Rank = 0
VariantId = 13, Name = "V4", Rank = 1
and increment the Rank to produce the following output
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 12, Name = "V3", Rank = 2
VariantId = 13, Name = "V4", Rank = 3
and I use the following code which works correctly (List1 is the original list, and List2 is the list to be merged)
int highestOrder = (List1.VariantsRanks.Max(cpo => cpo.Rank)) + 1;
foreach (var rank in List2.VariantsRanks)
{
var match = List1.VariantsRanks.FirstOrDefault(x => x.VariantId == rank.VariantId);
if (match != null) // found
{
match.Rank = rank.Rank;
}
else
{
rank.Rank = highestOrder;
highestOrder = highestOrder + 1;
List1.VariantsRanks.Add(rank);
}
}
I then need to merge the following VariantsRank to the new list (note the matching VariantId values, but they are in reverse order)
VariantId = 13, Name = "V4", Rank = 0
VariantId = 12, Name = "V3", Rank = 1
so that the output should be
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 13, Name = "V4", Rank = 2
VariantId = 12, Name = "V3", Rank = 3
however the above code instead outputs
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 12, Name = "V3", Rank = 1
VariantId = 13, Name = "V4", Rank = 0
and the Rank values are not incremented correctly
How do I modify the code to ensure that duplicate VariantId are not added, but increment the Rank?
You issue is that in the 2 merge, your adding items with values that match the VariantId in the existing list. This means that you hit the code in the if block, which resets the values of the existing items to the value of the Rank in the model your posting.
For example, in the first iteration of your loop, match is the existing item with VariantID = 13 and your set its Rank to equal the value of rank.Rank which is 0.
You need to first remove any matches from your existing list, and then iterate through the posted values, update their Rank and add to the collection.
You code should be
// Get the VariantId values of the list to be merged
var ids = List2.VariantsRanks.Select(x => x.VariantId);
// Remove any matches from the existing list
List1.VariantsRanks.RemoveAll(x => ids.Contains(x.VariantId));
// Calculate the current highest rank
int highestOrder = (List1.VariantsRanks.Max(x => x.Rank));
foreach (var rank in List2.VariantsRanks)
{
// Update the rank
rank.Rank = ++highestOrder; // pre-increment
// Add to the existing list
List1.VariantsRanks.Add(rank);
}
Based on comments in chat that the the second list might contain items that need to be inserted in the middle of the first list, then the code would need to be
// Get the VariantId's of the first and last items in the list to be merged
var firstID = List2.VariantsRanks.First().VariantId;
var lastID = List2.VariantsRanks.Last().VariantId;
// Get the indexers of those items in the original list
var firstIndex = List1.VariantsRanks.FindIndex(x => x.VariantId == firstID);
var lastIndex = List1.VariantsRanks.FindIndex(x => x.VariantId == lastID);
if (firstIndex > lastIndex) // in case they are in descending order
{
var temp = lastIndex;
lastIndex = firstIndex;
firstIndex = temp;
}
// Remove matches from the original list
for (int i = firstIndex; i < lastIndex + 1; i++)
{
List1.VariantsRanks.RemoveAt(firstIndex);
}
// Inset the items from the list to be merged
for(int i = 0; i < List2.VariantsRanks.Count; i++)
{
List1.VariantsRanks.Insert(firstIndex + i, List2.VariantsRanks[i]);
}
/ Re-number the Rank
for(int i = 0; i < List1.VariantsRanks.Count; i++)
{
List1.VariantsRanks[i].Rank = i;
}
Note, the above will only work if the values of VariantId in the merged list are consecutive (in either ascending or descending order)

Convert Objects into Hierarchy based on Number Sequence

I have following data Structure and i want to put covert it in a Hierarchy based on RelationId. This is sorted on RelationId.
Id = 2 has relationId =2 and following two rows has realtionId =0 . That represent the Id=3 and Id=4 are child of Id = 2
Id Name RelationId SortOrder
1 A 1 1
2 B 2 2
3 C 0 3
4 D 0 4
5 E 3 5
6 F 0 6
7 G 0 7
8 H 4 8
End Result would be like following
Id = 1
|
Id = 2
|___ Id = 3 , Id = 4
Id = 5
|___ Id= 6 , Id=7
Id = 8
The desired result is as following (for simplicity representing it as List). This would be a List<Something> in C#
Result =
[
{ Id = 1, Name = A, Children = Null },
{ Id = 2, Name = B, Children = [{ Id = 3, Name = C }, {Id = 4, Name = D }] },
{ Id = 5, Name = E, Children = [{ Id = 6, Name = F }, {Id = 7, Name = G }] },
{ Id = 8, Name = H}
]
My unsuccessful attempt is as following
var finalResult = new List<sampleDataClass>();
var sampleData = GetMeSampleData();
var count = sampleData.Count();
foreach (var item in sampleData)
{
var alreadyExist = finalResult.Any(x => x.Id == item.Id);
var newObject = new sampleDataClass();
if (!alreadyExist && item.RelationId!= 0)
{
newObject = item;
}
for (int i = item.SortOrder; i < count; i++)
{
if (sampleData[i].RelationId== 0)
{
newObject.Children.Add(sampleData[i]);
}
}
finalResult.Add(newObject );
}
Since your RelationId decides whether it is a root or nested element, you could form a group based on these relation and do this. I would suggest using Linq
List<SomeData> somedata = ... // your data.
int index=0;
var results = somedata
.Select(x=> new {gid = x.RelationId ==0? index: ++index, item=x})
.GroupBy(x=> x.gid)
.Select(x=> {
var first = x.FirstOrDefault();
return new
{
Id = first.item.Id,
Name = first.item.Name,
Children = x.Skip(1).Select(s=> new {
Id = s.item.Id,
Name = s.item.Name,
})
};
})
.ToList();
Output :
Id=1, Name=A
Id=2, Name=B
Id=3, Name=C
Id=4, Name=D
Id=5, Name=E
Id=6, Name=F
Id=7, Name=G
Id=8, Name=H
Check this Working Code
I have done it like this. Don't know if there can be some more elegant solution
var data = new List<MyDataObject>();
var SampleData = GetMeSampleData;
var count = SampleData.Count();
for (int i=0;i<count;i++)
{
var rootAdded = false;
var relationId = SampleData[i].relationId;
var alreadyExist = data.Any(x => x.Id == SampleData[i].Id);
var mydataObject = new MyDataObject();
if (!alreadyExist && SampleData[i].RelationId != 0)
{
mydataObject = SampleData[i];
rootAdded = true;
}
for(int j=i+1;j<count;j++)
{
if ((SampleData[j].RelationId == 0 && rootAdded))
{
mydataObject.Children.Add(SampleData[j]);
}
if (SampleData[j].SubjectId != 0)
break;
}
if (rootAdded)
{
data.Add(mydataObject);
}

merging 2 lists into 1 with logic

I have 2 lists of the same type.
List 1:
ID
Name
Value
1,"Prod1", 0
2,"Prod2", 50
3,"Prod3", 0
List 2:
ID
Name
Value
1,"Prod1", 25
2,"Prod2", 100
3,"Prod3", 75
I need to combine these 2 lists into 1, but I only want the values from list2 if the corresponding value from list1 == 0
So my new list should look like this:
1,"Prod1", 25
2,"Prod2", 50
3,"Prod3", 75
I've tried many variations of something like this:
var joined = from l1 in List1.Where(x=>x.Value == "0")
join l2 in List2 on l1.ID equals l2.ID into gj
select new { gj };
I've also tried a variation of the concat
What is the best way of doing this?
You just need to select the individual properties and conditionally select either the Value from the first or second list item.
var List1 = new[]
{
new { Name = "Prod1", Id = 1, Value = 0 },
new { Name = "Prod2", Id = 2, Value = 50 },
new { Name = "Prod3", Id = 3, Value = 0 },
new { Name = "NotInList2", Id = 4, Value = 0}
};
var List2 = new[]
{
new { Name = "Prod1", Id = 1, Value = 25 },
new { Name = "Prod2", Id = 2, Value = 100 },
new { Name = "Prod3", Id = 3, Value = 75 }
};
var results = from l1 in List1
join l2temp in List2 on l1.Id equals l2temp.Id into grpj
from l2 in grpj.DefaultIfEmpty()
select new
{
l1.Id,
l1.Name,
Value = l1.Value == 0 && l2 != null ? l2.Value : l1.Value
};
foreach(var item in results)
Console.WriteLine(item);
Will output
{ Id = 1, Name = Prod1, Value = 25 }
{ Id = 2, Name = Prod2, Value = 50 }
{ Id = 3, Name = Prod3, Value = 75 }
{ Id = 4, Name = NotInList2, Value = 0 }
NOTE: This assumes that you only want all the ids that are in List1 (not any that are only in List2) and that the ids are unique and that the Name from List1 is what you want even if it is different in List2.
clone l1 and
foreach (var item in l1Clone)
if (item.value == 0)
item.value == l2.FirstOrDefault(l2item => l2item.ID == item.ID)
Refer to the code below:
IEnumerable<item> join_lists(IEnumerable<item> list1, IEnumerable<item> list2)
{
var map = list2.ToDictionary(i => i.id);
return list1.Select(i => new item()
{
id = i.id,
name = i.name,
value = i.value == 0 ? map[i.id].value : i.value
});
}
You could use Zip:
var combined = list1
.Zip(list2, (product1, product2) => product1.Value == 0 ? product2 : product1);

Can someone help me ordering this list?

I am not the best programmer, so need some help to order this list. I had a few stabs at it, but still getting some cases which are wrong.
Essentially the list is the following:
#, ID, PreceedingID
A, 1 , 0
B, 2 , 3
C, 3 , 1
D, 4 , 2
I want to order it so that the list follows the preceeding id. The first item will always have the preceeding ID of 0.
#, ID, PreceedingID
A, 1 , 0
C, 3 , 1
B, 2 , 3
D, 4 , 2
Do you think you can help?
Thanks!
How about:
var data = new[] {
new Row{ Name = "A", ID = 1, PreceedingID = 0},
new Row{ Name = "B", ID = 2, PreceedingID = 3},
new Row{ Name = "C", ID = 3, PreceedingID = 1},
new Row{ Name = "D", ID = 4, PreceedingID = 2},
};
var byLastId = data.ToDictionary(x => x.PreceedingID);
var newList = new List<Row>(data.Length);
int lastId = 0;
Row next;
while (byLastId.TryGetValue(lastId, out next))
{
byLastId.Remove(lastId); // removal avoids infinite loops
newList.Add(next);
lastId = next.ID;
}
After this, newList has the data in the desired order.
In the above, Row is:
class Row
{
public string Name { get; set; }
public int ID { get; set; }
public int PreceedingID { get; set; }
}
But obviously substitute for your own type.
You can use for example dictionary to sort it:
Dictionary<..> d = new Dictionary<..>()
foreach(var el in list){
d[el.PreceedingID] = el; //put data to dict by PreecedingID
}
List<..> result = new List<..>();
int prec = 0; //get first ID
for(int i = 0; i < list.Length; ++i){
var actEl = d[prec]; //get next element
prec = actEl.ID; //change prec id
result.Add(actEl); //put element into result list
}

Categories