How to perform group by on list [duplicate] - c#

This question already has answers here:
Using Linq to group a list of objects into a new grouped list of list of objects
(5 answers)
Closed 9 years ago.
I have a list that contains string and value of that string but list contains multiple string of same name now i want to group name and add value of that name in single entry. for example
Name ..... Value
apple ----- 2
mango ----- 4
banana ---- 8
apple ----- 4
Now i want to add apple as a single entry.

May be this is what you want:
suppose you have a collection like this:
List<Test> fruits = new List<Test>() { new Test() { Name="Apple", value=3 }
, new Test() {Name="Apple",value=5 }
, new Test() {Name="Orange",value=5 }
};
then you can groupBy it and sum similar items like this:
var netFruits= fruits.GroupBy(s => s.Name)
.Select(s => new Test()
{
Name=s.Key,
value = s.Sum(b=>b.value)
});
where netFruits will have two entries
Apple 8
Orage 5
where Test is:
public class Test
{
public string Name { get; set; }
public int value { get; set; }
}

You can do groupby like this
yourlist.GroupBy(x=>x.YourField())

If I think I know what you want is retreiving distint values. Than this is how it can be do
List<String> MyListOfFruit = new List<String>()
{
{"Banana"},
{"Banana"},
{"Apple"}
};
List<String> GroupedListOfFruits = new List<String>();
GroupedListOfFruits.AddRange(MyListOfFruit.Distinct());
//Now GroupedListOfFruits contains two items: Banana and Apple.
Otherwise post a piece of your code with what kind of list you exactly have. Do you have list of KeyValuePair<String, int> maybe?

So they are separated with "-----"?
var groups = list.Select(str => str.Split(new[]{"-----"}, StringSplitOptions.None))
.Select(split => new { Name = split.First(), Value = split.Last() })
.GroupBy(x => x.Name);
foreach (var grp in groups)
{
Console.WriteLine("Name:{0} Values:{1}", grp.Key, string.Join(",", grp.Select(x => x.Value)));
}
However, it is not clear what you mean with "Now i want to add apple as a single entry".

list.Distinct();
make sure you have included System.Linq in yr namespace.

Since the number of - separating the value from the key seems to be variable, I guess using a Regex to split is more appropriate:
var query = yourlist.Select(x=>{
var arr = Regex.Split(x,#"[-]+");
return new{
Name = arr[0],
Value = Int32.Parse(arr[1])
};
})
.GroupBy(x=>x.Name)
.Select(x=> new{Name = x.Key, Value=x.Sum(y=>y.Value)});
In this way you'll have a new anonymous object, with property Name equal to your string, and property Value equal to the sum of the Values of the elements with such Name.
If, instead you want as a return a string with the sum of the values as value, you can replace the last select with:
.Select(x=> x.Key + " ----- " + x.Sum(y=>y.Value).ToString());
And if, you already have a list with a name and value field, just remove the first Select.

Related

How to use linq to split a list of objects by half and then group them [duplicate]

This question already has answers here:
Split List into Sublists with LINQ
(34 answers)
Closed 3 years ago.
I have a list of n objects (17 for now) and I wanted to know if it is possible to take said list and split it into (potentially) 2 groups. That way the end result would be
NewList
-"GroupA"
-List1 = {"john", "mary", "sam"}
-"GroupB"
-List2 = {"tony", "aaron"}
The desired result would help me output the first half of the list of students in page 1 and then using paging the user can then view the remaining list on the next page.
Right now I am trying to do something like this:
var groupList = Classroom.GroupBy(o => o).Select(grp=>grp.Take((Classroom.Count + 1) / 2)).ToList();
But when I debug it I'm still getting the full list. Can it be done via linq?
You can create group by some property. For example, we have 50 students, then we can make GroupId property and group them by GroupId property:
var students = new List<Student>();
for (int i = 0; i < 50; i++)
{
students.Add(new Student { Id = i, Name = $"Student { i }" });
}
var sectionedStudents = students.Select(s => new
{
GroudId = s.Id / 10,
s.Id,
s.Name
});
var groupedStudents = sectionedStudents.GroupBy(s => s.GroudId);
and Person class:
class Student
{
public int Id { get; set; }
public string Name { get; set; }
}

Linq to select highest ID where the ID is alphanumeric

I have a list of ID where the ID's start with MB (for members) or NM (for non members).
The ID would be like MB-101 or NM-108 etc...
I am trying to select the Highest ID starting with MB and then Add 1 and then save back to DB. Saving is easy but I am not sure how to query the highest Member or Nonmember ID and add one to it. Any help is much appreciated
You can do something like this:
List<string> list = new List<string>() { "MB-101", "MB-102", "MB-103", "MB-104"};
var ids = list.Select(x =>Convert.ToInt32(x.Replace("MB-", "")));//convert all the number parts to integer
list[list.FindIndex(x => x == "MB-" + ids.Max().ToString())] = "MB-" + (ids.Max() + 1);//set the max number after adding one.
You can do the same with your Nonmember ID. It is tested code, it successfully addresses your problem.
Hope it helps.
You can get the max id by splitting your list like below:
var ids = yourList.Where(x => x.ID.StartsWith("MB-")).Select(x => new { ID = x.ID.Split('-')[1] }).ToList();
var maxIdValue = ids.Select(x => int.Parse(x.ID)).ToList().Max();
If you want max id from both starting with MB- and NB- than you can remove above where condition. By this it will fetch max id from both MB- and NB-. Following will be query than:
var ids = yourList.Select(x => new { ID = x.ID.Split('-')[1] }).ToList();
var maxIdValue = ids.Select(x => int.Parse(x.ID)).ToList().Max();
you can try like this
List<string> lststr = new List<string>() { "MB-101", "MB-103", "MB-102", "NM-108" };
var result = "MB-"+ lststr
.Where(x=>x.Contains("MB"))
.Select(x => Regex.Replace(x, #"[^\d]", ""))
.OrderByDescending(x=>x)
.FirstOrDefault();
it will return MB-103 because it will first check if the string contains MB then it will replace everything with "" other than digit and OrderByDescending it will order by Descending so that the highest value will be on top and at last FirstOrDefault will get the fist value
You have to do OrderByDescending your list by replacing MB or NM with empty and get FirstOrDefault from ordered list. Please check below example.
CODE:
List<string> list = new List<string>() { "MB-101", "MB-102", "MB-109", "MB-105", "NM-110"};
var maxMember = list.OrderByDescending(m=>Convert.ToInt16(m.Replace("MB-","").Replace("NM-",""))).ToList().FirstOrDefault();
Console.WriteLine(maxMember.ToString());

Form a list of distinct words from set of repeating words lists in c#

I have a model:
public class CompanyModel1
{
public string compnName1 { get; set; }
public string compnKeyProcesses1 { get; set; }
}
then I form a list:
List<CompanyModel1> companies1 = new List<CompanyModel1>();
If I access its values:
var newpairs = companies1.Select(x => new { Name = x.compnName1, Processes = x.compnKeyProcesses1 });
foreach (var item in newpairs)
{
string CName = item.Name;
Process = item.Processes;
}
I will get value like:
CName = "name1"
Process = "Casting, Casting, Casting, Welding, brazing & soldering"
and
CName = "name2"
Process = "Casting, Welding, Casting, Forming & Forging, Moulding"
etc.
Now I want to form a list of distinct Process and count number of them, how many time each of them have by different name.
For example with these two above, I have to form a list like following:
"Casting, Welding, brazing & soldering, Forming & Forging, Moulding"
and if I count there will be: 5 distinct Processes; frequency of them by each name:
"Casting" appears in 2 names
"Welding" appears in 2 names
"brazing & soldering" appears in 1 names
"Forming & Forging" appears in 1 names
"Moulding" appears in 1 names
I am thinking of Linq can help with this problem, may be something like this:
var list= Process
.SelectMany(u => u.Split(new string[] { ", " }, StringSplitOptions.None))
.GroupBy(s => s)
.ToDictionary(g => g.Key, g => g.Count());
var numberOfProcess = list.Count;
var numberOfNameWithProcessOne = frequency["Process1"];
But how could I put that in the foreach loop and apply for all the names and processes that I have and get the result I want?
var processes = companies1.SelectMany(
c => c.compnKeyProcesses1.Split(new char[] { ',' }).Select(s => s.Trim()).Distinct())
.GroupBy(s => s).ToDictionary(g => g.Key, g => g.Count());
foreach(var process in processes)
{
Console.WriteLine("\"{0}\" appears in {1} names", process.Key, process.Value);
}
This selects only distinct processes from each individual company, and then creates all master list using SelectMany to store the correct number of unique occurrences for every process. Then we just count the occurrences of each process in the final list, and put them into a dictionary of process=>count.
EDIT:
Here is another solution that groups the data in a dictionary, to allow showing the associated companies with each process. The dictionary is from Process Names -> List of Company Names.
Func<string, IEnumerable<string>> stringToListConverter = s => s.Split(new char[] { ',' }).Select(ss => ss.Trim());
var companiesDict = companies1.ToDictionary(c => c.compnName1, c => stringToListConverter(c.compnKeyProcesses1).Distinct());
var processesAll = companies1.SelectMany(c => stringToListConverter(c.compnKeyProcesses1)).Distinct();
var processesToNames = processesAll.ToDictionary(s => s, s => companiesDict.Where(d => d.Value.Contains(s)).Select(d => d.Key).ToList());
foreach(var processToName in processesToNames)
{
List<string> companyNames = processToName.Value;
Console.WriteLine("\"{0}\" appears in {1} names : {2}", processToName.Key, companyNames.Count, String.Join(", ", companyNames));
}
I've saved the stringToListConverter Func delegate to convert the process string into a list, and used that delegate in two of the queries.
This query would be more readable if the CompanyModel1 class stored the compnKeyProcesses1 field as a List<string> instead of just one big string. That way you could instantly query the list instead of having the split, select, and trim every time.

C# Lambda to filter list based on existence on another list

List A: 3,5,5,5,7,9
List B: 3,5
Both of the list are the same type and those values are from a field ID. My objective is to construct a forloop that will return me 7,9 because 7,9 is not existed in List B.
I've tried the following but no luck:
int counter = 0;
foreach(var item in ListA.Where(x=>ListB.Any(b=>x.ID != b.ID)))
{
counter++;
//Here I should perform operation with item that having ID 7 and 9
}
Updates:
Using a except method in the above case, counter will still return me 4 simply because each of the 5 in ListA are different object eventhou they are sharing the same ID. My ultimate objective is to have the counter as 2 irregardless whether the object is the same or not. As long as the ID of object in ListA is 3 or 5, I would wanna exclude it.
Just use the Except extension mtehod
foreach (var item in ListA.Except(ListB)) {
...
}
it should be "ALL", or "Not Any"
foreach(var item in ListA.Where(x=>ListB.All(b=>x.ID != b.ID)))
{
//Here I should perform operation with item that having ID 7 and 9
}
update:
As you actually want to have distinct result from A except B, so, you can do either:
foreach(var item in ListA.GroupBy(m=>m.ID).Where(x=>ListB.All(b=>b.ID != x.Key)))
{
counter ++;
Debug.writeline(item.Key);
}
or
foreach(var id in ListA.Select(x=>x.ID).Distinct().Except(ListB.Select(y=>y.ID)))
{
counter++;
}
note: all untested - i have no compiler with me for the moment.
Change your query like this:
foreach(var item in ListA.Where(x=> !ListB.Any(b => x.ID == b.ID)))
And it should work fine.
Try This:
List<int> listA=new List<int>(new[]{ 3,5,7,9});
List<int> listB=new List<int>(new[]{ 3,5});
var items=(from a in listA
select a).Except(from b in listB
select b);
foreach(var item in items)
{
Console.WriteLine(ll);
}
Output:
7
9
Except method can be used when both List are of same type.
If Type is different. We can use like this.
var outPut = _employees.Where(i => _employeeExtensions.Any(j => i.EmpId == j.EmpId));
I think you want to get the items in a list where the items' IDs are different:
Example that I put together in LinqPad:
void Main()
{
List<Person> a = new List<Person>()
{
new Person { ID = 1 },
new Person { ID = 2 },
new Person { ID = 3 },
};
List<Person> b = new List<Person>()
{
new Person { ID = 1 },
};
var c = a.Where(x => b.Any(bprime => bprime.ID != x.ID));
foreach(var item in c)
{
Console.WriteLine(item.ID);
}
}
class Person
{
public int ID { get; set; }
}
Output:
2
3
This works similar to the Except method but this will check the elements' properties.

Filter a generic list based on another list

I have a generic list which needs to be filter based on another list (say, List<string>).
public class Model
{
public string ID { get; set;}
public string Make { get; set;}
}
List<Model> lstModel = new List<Model>();
And the lstModel is as follows
ID Make
---- -----------
5 MARUTI
4 BENZ
3 HYUNDAI
2 HONDA
1 TOYOTA
And i have another list which contains only car makers,ie
List<string> lstMakers = new List<string>() {"MARUTI", "HONDA"};
1) I need to filter lstModel which contains only items in lstMakers.
The output would be
ID Make
---- -----------
5 MARUTI
2 HONDA
2) Based on output (1), need another list of ids with 1 increment to each item in descending order,
The output would be List<int> ie,
6
5
3
2
Note: Using lambda expression / linq is more preferable
1 )
var list1 = lst.Where(x=>lstMakers.Contains(x.Make)).ToList();
2)
var list2 = list1.Select(x=>int.Parse(x.ID)+1)
.Concat(list1.Select(x=>int.Parse(x))
.OrderByDescending(x=>x)
.ToList();
Use Enumerable.Join and OrderByDescending:
var models = from maker in lstMakers
join model in lstModel
on maker equals model.Make
select model;
List<int> result = models
.Select(m => int.Parse(m.ID) + 1)
.OrderByDescending(i => i)
.ToList();
However, this selects two ints since only two models match. Your result contains 4 ints. I assume that your result is not related to your sample, is it?
but i need both the item and its incremental value,...
Now it's clear, use Enumerable.SelectMany with an array:
List<int> result = models
.Select(m => int.Parse(m.ID))
.SelectMany(id => new int[]{ id, id + 1 })
.OrderByDescending(id => id)
.Distinct()
.ToList();

Categories

Resources