I'm struggling with the following task. Any suggestions would be greatly appreciated!
I have a list of Person objects like below:
public class Person {
private string firstname {get; set}
private string lastname {get; set}
private string zipcode {get; set;}
private string id {get; set;}
private int freq = 1;
public Person(...) {...}
}
List<Person> PersonList = new List<Person>; //Gets populated with Person objects
I want to find all the people who have unique names within their zipcode.
So far, I've tried performing a frequency count on all the distinct combinations of (firstname, lastname, zipcode) and then selecting the combinations that have frequency = 1. However, I then lose all information about these peoples' IDs. I need a way to retain the original Person objects despite the grouping operation.
Below is the frequency count I mentioned above, but it isn't the result I'm looking for:
var QueryFreqAnalysis =
from p in PersonList
group p by new { p.firstName, p.lastName, p.zipcode } into g
select new {
fName = g.Key.firstname,
lName = g.Key.lastname,
zip3 = g.Key.zipcode,
freq = g.Sum(p => p.freq)
};
As I mentioned, even though I can now select groups within g that have freq = 1, I have lost all information about the Person IDs.
I hope I've made the problem clear. Thanks in advance for any suggestions!
from p in PersonList
// Group by name and zip
group p by new { p.firstName, p.lastName, p.zipcode } into g
// Only select those who have unique names within zipcode
where g.Count() == 1
// There is guaranteed to be one result per group: use it
let p = g.FirstOrDefault()
select new {
fName = p.firstname,
lName = p.lastname,
zip3 = p.zipcode,
id = p.id
}
I know you probably only need and want a linq answer :)
But i just had to write a non linq one:
var dict = new Dictionary<string, Person>(PersonList.Count);
var uniqueList = new List<Person>();
foreach (var p in PersonList)
{
var key = p.firstname + p.lastname + p.zipcode;
if (!dict.ContainsKey(key))
dict.Add(key, p);
else
dict[key] = null;
}
foreach (var kval in dict)
{
if (kval.Value != null)
uniqueList.Add(kval.Value);
}
return uniqueList;
Using Hash Codes is also possible.
Related
I'm struggling with linq (left join - group - count). Please help me.
Below is my code and it gives me this result.
Geography 2
Economy 1
Biology 1
I'm expecting this...
Geography 2
Economy 1
Biology 0
How can I fix it?
class Department
{
public int DNO { get; set; }
public string DeptName { get; set; }
}
class Student
{
public string Name { get; set; }
public int DNO { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Department> departments = new List<Department>
{
new Department {DNO=1, DeptName="Geography"},
new Department {DNO=2, DeptName="Economy"},
new Department {DNO=3, DeptName="Biology"}
};
List<Student> students = new List<Student>
{
new Student {Name="Peter", DNO=2},
new Student {Name="Paul", DNO=1},
new Student {Name="Mary", DNO=1},
};
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count()
};
foreach (var st in query)
{
Console.WriteLine("{0} \t{1}", st.DName, st.Count);
}
}
}
var query =
from department in departments
join student in students on department.DNO equals student.DNO into gst
select new
{
DepartmentName = department.DeptName,
Count = gst.Count()
};
I don't think any grouping is required for answering your question.
You only want to know 2 things:
- name of department
- number of students per department
By using the 'join' and 'into' you're putting the results of the join in the temp identifier gst. You only have to count the number of results in the gst.
var query = from dp in departments
from st in students.Where(stud => stud.DNO == dp.DNO).DefaultIfEmpty()
group st by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(x => x!=null)
};
You want to group the students by the department name but you want the count to filter out null students. I did change the join syntax slightly although that really does not matter to much.
Here is a working fiddle
Well, see what #Danny said in his answer, it's the best and cleanest fix for this case. By the way, you could also rewrite it to the lambda syntax:
var query = departments.GroupJoin(students,
dp => dp.DNO, st => st.DNO,
(dept,studs) => new
{
DName = dept.DNO,
Count = studs.Count()
});
I find this syntax much more predictable in results, and often, shorter.
BTW: .GroupJoin is effectively a "left join", and .Join is "inner join". Be careful to not mistake one for another.
And my answer is similar to #Igor
var query = from dp in departments
join st in students on dp.DNO equals st.DNO into gst
from st2 in gst.DefaultIfEmpty()
group st2 by dp.DeptName into g
select new
{
DName = g.Key,
Count = g.Count(std => std != null)
};
g.Count(std => std != null) is only one change you should take.
I am really new to using LINQ and I was wondering what I need to do to the below expression to grab extra fields
public class Foo
{
public string Name {get;set;}
public string Manufacturer {get;set;}
public float Price {get;set;}
}
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
select new Foo
{
Name = g.Key,
Price=g.Min (x =>x.Field<float>("PRICE"))
//Manufacturer = ????
}
).ToList();
I basically need to get the Manufacturer from the MANUFACTURER field and set it's value in the object. I've tried:
row.Field<string>("MANUFACTURER")
//and
g.Field<string>("MANUFACTURER")
But I am having no luck accessing the field in the DataTable. Can anyone advise what I'm doing wrong please?
So you want to group by name. But how do you want to aggregate the manufacturers for each name-group?
Presuming that you just want to take the first manufacturer:
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
select new Foo
{
Name = g.Key,
Price=g.Min (x =>x.Field<float>("PRICE")),
Manufacturer = g.First().Field<string>("MANUFACTURER")
}
).ToList();
Maybe you instead want to concatenate all with a separator:
// ...
Manufacturer = string.Join(",", g.Select(r=> r.Field<string>("MANUFACTURER")))
As your logic stands you may have more than one Manufacturer if you only group by Name.
To illustrate this consider the following data, which is supported by your data structure.
Example
ProductA, ManufacturerA
ProductA, ManufacturerB
If you group by just "ProductA" then Manufacturer is a collection of ["ManufacturerA", "ManufacturerB"]
Potential Solution
You could group by Name and Manufacturer then access both Name and Manufacturer
var result= (
from row in dt.AsEnumerable()
group row by new
{
row.Field<string>("NAME"),
row.Field<string>("MANUFACTURER")
} into g
select new Foo
{
Name = g.Key.Name,
Manufacturer = g.Key.Manufacturer,
Price=g.Min (x =>x.Field<float>("PRICE"))
}
).ToList();
EDIT
Based on comment "I am trying to pull the name with the cheapest price and the manufacturer along with it."
var result= (
from row in dt.AsEnumerable()
group row by row.Field<string>("NAME") into g
let x = new
{
Name = g.Key.Name,
Price=g.Min (x =>x.Field<float>("PRICE"))
}
where (row.Name == x.Name && row.Price == x.Price)
select new Foo
{
Name = row.Name,
Manufacturer = row.Manufacturer,
Price= row.Price
}
).ToList();
In this example class IcdPatient represents a many-to-many relationship between a Patient table (not shown in this example) and a lookup table Icd.
public class IcdPatient
{
public int PatientId { get; set; }
public int ConditionCode { get; set; }
public static List<IcdPatient> GetIcdPatientList()
{
return new List<IcdPatient>()
{
new IcdPatient { PatientId = 100, ConditionCode = 111 },
new IcdPatient { PatientId = 100, ConditionCode = 222 },
new IcdPatient { PatientId = 200, ConditionCode = 111 },
new IcdPatient { PatientId = 200, ConditionCode = 222 },
new IcdPatient { PatientId = 3, ConditionCode = 222 },
};
}
}
public class Icd
{
public int ConditionCode { get; set; }
public string ConditionName { get; set; }
public static List<Icd> GetIcdList()
{
return new List<Icd>()
{
new Icd() { ConditionCode =111, ConditionName ="Condition 1"},
new Icd() { ConditionCode =222, ConditionName ="Condition 2"},
};
}
}
I would like for the user to be able to enter as many conditions as they want, and get a LINQ object back that tells them how many PatientIds satisfy that query. I've come up with:
List<string> stringFilteredList = new List<string> { "Condition 1", "Condition 2" };
List<int> filteringList = new List<int> { 111,222 };
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
group m by m.PatientId into g
where g.Count() == filteringList.Distinct().Count()
select new
{
PatientId = g.Key,
Count = g.Count()
};
/*End*/
foreach (var item in grouped)
{
Console.WriteLine(item.PatientId);
}
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like
let the user table in the ConditionName in type Icd and perform the same operation. I've not used LINQ this way a lot and I've gathered:
List<int> filteringList = new List<int> { 111,222 };
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
filteringList.Distinct();
var manyToMany = IcdPatient.GetIcdPatientList();
var icdList = Icd.GetIcdList();
/*Working method without joining on the lookup table*/
var grouped = from m in manyToMany
join i in icdList on
m.ConditionCode equals i.ConditionCode
//group m by m.PatientId into g
group new {m,i} by new { m.ConditionCode }into g
where g.Count() == filteringList.Distinct().Count()
select new
{
Condition = g.Key.ConditionCode
};
/*End*/
but can't get anything to work. This is essentially a join on top of my first query, but I'm not getting what I need to group on.
You don't need to group anything in this case, just use a join and a contains:
List<string> stringFilteredList= new List<string>{"Condition 1","Condition 2" };
var patients =
from icd in Icd.GetIcdList()
join patient in IcdPatient.GetIcdPatientList() on icd.ConditionCode equals patient.ConditionCode
where stringFilteredList.Contains(icd.ConditionName)
select patient.PatientId;
Let's say that IcdPatient has a composite primary key on both fields, so we know that each row is unique. If we find the distinct number of entries in filteringList and do a count on the number of times a PatientId shows up, that means we've found all the people who have all conditions. Because the codes can be esoteric, I would like to do something like let the user table in the ConditionName in type Icd and perform the same operation.
I believe you're asking:
Given a list of ConditionCodes, return a list of PatientIds where every patient has every condition in the list.
In that case, the easiest thing to do is group your IcdPatients table by Id, so that we can tell every condition that a patient has by looking once. Then we check that every ConditionCode we're looking for is in the group. In code, that looks like:
var result = IcdPatient.GetIcdPatientList()
// group up all the objects with the same PatientId
.GroupBy(patient => patient.PatientId)
// gather the information we care about into a single object of type {int, List<int>}
.Select(patients => new {Id = patients.Key,
Conditions = patients.Select(p => p.ConditionCode)})
// get rid of the patients without every condition
.Where(conditionsByPatient =>
conditionsByPatient.Conditions.All(condition => filteringList.Contains(condition)))
.Select(conditionsByPatient => conditionsByPatient.Id);
In query format, that looks like:
var groupedInfo = from patient in IcdPatient.GetIcdPatientList()
group patient by patient.PatientId
into patients
select new { Id = patients.Key,
Conditions = patients.Select(patient => patient.ConditionCode) };
var resultAlt = from g in groupedInfo
where g.Conditions.All(condition => filteringList.Contains(condition))
select g.Id;
Edit: If you'd also like to let your user specify the ConditionName rather than the ConditionId then simply convert from one to the other, storing the result in filteringList, like so:
var conditionNames = // some list of names from the user
var filteringList = Icd.GetIcdList().Where(icd => conditionNames.Contains(icd.ConditionName))
.Select(icd => icd.ConditionCode);
I created a Web Api in VS 2012.
I am trying to get all the value from one column "Category", that is all the unique value, I don't want the list to be returned with duplicates.
I used this code to get products in a particular category. How do I get a full list of categories (All the unique values in the Category Column)?
public IEnumerable<Product> GetProductsByCategory(string category)
{
return repository.GetAllProducts().Where(
p => string.Equals(p.Category, category, StringComparison.OrdinalIgnoreCase));
}
To have unique Categories:
var uniqueCategories = repository.GetAllProducts()
.Select(p => p.Category)
.Distinct();
var uniq = allvalues.GroupBy(x => x.Id).Select(y=>y.First()).Distinct();
Easy and simple
I have to find distinct rows with the following details
class : Scountry
columns: countryID, countryName,isactive
There is no primary key in this. I have succeeded with the followin queries
public DbSet<SCountry> country { get; set; }
public List<SCountry> DoDistinct()
{
var query = (from m in country group m by new { m.CountryID, m.CountryName, m.isactive } into mygroup select mygroup.FirstOrDefault()).Distinct();
var Countries = query.ToList().Select(m => new SCountry { CountryID = m.CountryID, CountryName = m.CountryName, isactive = m.isactive }).ToList();
return Countries;
}
Interestingly enough I tried both of these in LinqPad and the variant using group from Dmitry Gribkov by appears to be quicker. (also the final distinct is not required as the result is already distinct.
My (somewhat simple) code was:
public class Pair
{
public int id {get;set;}
public string Arb {get;set;}
}
void Main()
{
var theList = new List<Pair>();
var randomiser = new Random();
for (int count = 1; count < 10000; count++)
{
theList.Add(new Pair
{
id = randomiser.Next(1, 50),
Arb = "not used"
});
}
var timer = new Stopwatch();
timer.Start();
var distinct = theList.GroupBy(c => c.id).Select(p => p.First().id);
timer.Stop();
Debug.WriteLine(timer.Elapsed);
timer.Start();
var otherDistinct = theList.Select(p => p.id).Distinct();
timer.Stop();
Debug.WriteLine(timer.Elapsed);
}
I need to build a sorter class for a List.
I would like the sort rule or priority to be:
SFC = 1 SSG = 2 SGT = 3 CPL = 4 SPC = 5 ETC...
So when I sort I will get these in the correct order by rank first then by lastname.
List<Person> person = new List<Person>();
person.Rank
person.LastName
person.FirstName
ETC...
Please lead me to an article or instruction. Thanks
Could you use LINQ?
person.OrderBy(p => p.Rank).ThenBy(p => p.LastName)
or using the query syntax with the orderby clause:
from p in person
orderby p.Rank, p.LastName
select p
public enum Rank
{
SFC = 1, SSG, SGT
}
public class Person
{
public Rank Rank { get; set; }
public string Name { get; set; }
}
static void Main(string[] args)
{
var persons = new List<Person>
{
new Person{ Name = "Aaaa", Rank = Rank.SFC },
new Person{ Name = "Bbbb", Rank = Rank.SFC },
new Person{ Name = "Aaaa", Rank = Rank.SSG }
};
foreach (var person in persons.OrderBy(p => p.Rank).ThenBy(p => p.Name))
{
Console.WriteLine("{0} {1}", person.Rank, person.Name);
}
}
Output:
SFC Aaaa
SFC Bbbb
SSG Aaaa
var ordered = person.OrderBy(p => p.Rank)
.ThenBy(p => p.LastName)
.ThenBy(p => p.FirstName);
Note that this does NOT modify person. It merely produces an iterator that when iterated over yields the results of person returned in the specified ordering. If you want to modify the list you'll have to take a slightly different approach (note that it's not enough to say person = ordered.ToList(); as that actually creates a new instance of List<Person>.
using linq
var list = from xx in person
order by xx.Rank, xx.LastName
select xx
it will sort by rank and last name
try this....
var result = from m in person
orderby m.Rank, m.LastName
select m;
or you can try this....
var result = person.OrderBy(m=> m.Rank)
.ThenBy(m => m.LastName)
.ThenBy(m => m.FirstName);
Your class Person should inherit from the interface Icomparable. Doing so you'll have to implement the compareto() method. Then you can use the sort method of the List.