ordering a list of addresses - c#

I have created a list which contains a number of addresses. I want to sort the list so that they will appears numerically in order i.e.
1 Abbey Road
2 Abbey Road
3 Abbey Road
10 Abbey Road
I've tried list.sort - which does alphabetically but therefore 10 appears before 2.
My linq is limited so I was thinking there might be a way using that or using a regex.
Any ideas?

You can use Linq's OrderBy (http://msdn.microsoft.com/en-us/library/vstudio/bb549422%28v=vs.110%29.aspx) sorting:
list.OrderBy(street => street, comparer);
where street are the strings with the street name and comparer is an IComparer that sorts them the way you want. To sort string numerically you can take a look here:
Sorting a List of Strings numerically (1,2,...,9,10 instead of 1,10,2)

If you don't store the street-number in a separate field but you expect it to be in the first position, you could use this class with meaningful properties to extract all informations:
public class Address
{
public string FullAddress { get; set; }
public int Number { get; set; }
public string Street { get; set; }
}
Now basically you just have to use String.Split and int.Parse, for example in this LINQ query:
List<Address> addresses = strings.Select(s => new {
FullAddress = s.Trim(),
Tokens = s.Trim().Split()
})
.Where(x => x.Tokens.Length > 1 && x.Tokens[0].All(Char.IsDigit))
.Select(x => new Address {
FullAddress = x.FullAddress,
Street = String.Join(" ", x.Tokens.Skip(1)),
Number = int.Parse(x.Tokens[0])
})
.OrderBy(addr => addr.Number)
.ToList();
If you don't want to select this class but just your strings in the correct order, you have to change the end of the query to:
.OrderBy(addr => addr.Number)
.Select(addr => addr.FullAddress)
.ToList();
Note that the Where filters, so "invalid" addresses are skipped. If that's not desired you could use:
int number;
List<Address> addresses = strings.Select(s => new {
FullAddress = s.Trim(),
Tokens = s.Trim().Split()
})
.Select(x => new Address{
FullAddress = x.FullAddress,
Street = String.Join(" ", x.Tokens.Skip(1)),
Number = int.TryParse(x.Tokens[0], out number) ? number : int.MaxValue
})
.OrderBy(addr => addr.Number)
.ToList();

Try this:-
var sortedList = addr.OrderBy(x => x.ID).ToList();
Fiddle.
If you are looking to sort it by Sort method, then you need to implement IComparable interface:-
public class Address : IComparable<Address>
{
public int ID { get; set; }
public string Add { get; set; }
public int CompareTo(Address other)
{
return this.ID.CompareTo(other.ID);
}
}
Then you can do:-
addr.Sort();
SortFiddle.

Related

LINQ, Group by property while keeping other property sort together

So I am not entirely sure how to explain what it is I am trying to do here. I am attempting to take some data (represented by the Excel file screenshot below), and basically sort by Connection2, while keeping similar items in Connection1 together. (Explained a bit in screen shot below)
Here is what I have as of right now:
var wires = RedConductorWires
.OrderBy(x => x.Label)
.ThenBy(x => x.Connection1)
.ThenBy(x => x.Connection2)
.ToList();
Class Object being sorted(Matches Excel Screenshot):
public class CustomExcelFormat
{
public string Label { get; set; }
public string WireSize { get; set; }
public string WireColor { get; set; }
public string WirePartNumber { get; set; }
public string Length { get; set; }
public string Connection1 { get; set; }
public string Connection1Torque { get; set; }
public string Connection1Termination { get; set; }
public string Connection1StripLength { get; set; }
public string Checkbox1 { get; set; }
public string Connection2 { get; set; }
public string Connection2Torque { get; set; }
public string Connection2Termination { get; set; }
public string Connection2StripLength { get; set; }
public string Checkbox2 { get; set; }
}
Screen Shot:
THE PROBLEM:
The issue is if you look at the screen shot the brown "A1:TB7:M1" cells need to be grouped together as well, and the Green "K7:10" need to be grouped together while maintaining their Connection2 sort/group.
In other words, the connection 2 side of those, K8:10 and K8:11 need to stay grouped together.
So obviously my LINQ query is not correct, I believe I need to do some sort of grouping and then sorting but am unsure how to approach it or even ask this question exactly (If someone could put it into words for me). I basically need to group by items in connection 2, while still keeping connection 1 sorted and together.
If someone could point me in the direction of the LINQ expression that could do something like this that would be great!
EDIT
So I used the following query:
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i).ToList();
and got the grouping correct. Now I just need to get it to sort in some alphabetical manner. See picture below.
Imagine this lines
A - B
C - B
C - D
A - D
you can reorder the lines any way you like and either you would have first column grouped or second column grouped. But you can never have both at the same time
I got the grouping to work correctly with the following query. I decided to keep it sorted on label initially.
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i).ToList();
Group By the values according to Label, Connection1, Connection2 then sort by these 3 fields and finally the desired output is generated.
var wires = RedConductorWires
.GroupBy(a => new { a.Label,a.Connection2, a.Connection1})
.Join(RedConductorWires,
left=>new { left.Key.Label,left.Key.Connection1,left.Key.Connection2},
right => new { right.Label, right.Connection1, right.Connection2 },
(left,right)=>new {left=left.Key,right = right })
.OrderBy(x => x.left.Label)
.ThenBy(x => x.left.Connection2)
.ThenBy(x => x.left.Connection1)
.ToList();
foreach(var item in wires)
{
Console.WriteLine(item.left.Label + "----" + item.left.Connection1 + "-----" + item.left.Connection2);
}
or
var wires = RedConductorWires
.OrderBy(x => x.Label)
.GroupBy(x => new { x.Connection2, x.Connection1 })
.Select(grp => grp.ToList()).SelectMany(i => i)
.OrderBy(x => x.Connection2)
.ThenBy(x => x.Connection1)
.ToList();
foreach(var item in wires)
{
Console.WriteLine(item.Label + "----" + item.Connection1 + "-----" + item.Connection2);
}

How to group and count the number of occurence of item in comma delimited string

I have a Student class with the following properties
public class Student
{
public string Name{ get; set; }
public string Subject { get; set; }
}
Assume we have a list of students like below
var students = new List<Student>();
students.Add(new Student { Name = "John", Subject = "Math"});
students.Add(new Student { Name = "Bob", Subject = "English, Math"});
students.Add(new Student { Name = "Jane", Subject = "Math, History, Art"});
students.Add(new Student { Name = "Jim", Subject = "English"});
I want to group the students by subject and count the subject.
So the output would be
Math, 3
English, 1
History 1
Art 1
How can I achieve the outcome using linq?
students.SelectMany(arg => arg.Subject.Split(new []{','})) // split the Subject-property on commas
.Select(arg => arg.Trim()) // get rid of the whitespaces after commas
.GroupBy(arg => arg) // you can inject an equality comparer here, to achieve case insenstive grouping
.Select(arg => new
{
Subject = arg.Key,
Count = arg.Count()
}); // TODO output these objects to your console..
Basically, what you want to do is first get the subjects of all students, and just collect those. To do this, you need to split the Subject by the comma, and then trim the results (to get rid of whitespace around the comma). This gives you a list of all subjects.
Now all you need to do is count how often each subject occurs in the list. To do this, you can use just GroupBy. Afterwards, you have all subjects grouped by the name, so you only need to count how many there are in each group.
You can then collect that result for example in a dictionary like this:
IDictionary<string, int> subjectCount = students
.SelectMany(s => s.Subject.Split(','))
.Select(s => s.Trim())
.GroupBy(s => s)
.ToDictionary(grp => grp.Key, grp => grp.Count());
foreach (var count in subjectCount)
{
Console.WriteLine("{0}: {1}", count.Key, count.Value);
}
Btw. it seems a bit weird that Student.Subject is a comma-separated string of the student’s subjects. You should consider using a list there, so that you have separated values from the beginning. And since the property contains multiple subjects, you should pluralize the name too:
public class Student
{
public string Name { get; set; }
public List<string> Subjects { get; set; }
}

Changing GroupBy keys depending on Class Structure

I have a list of items with multiple columns and would like to group them by some fields depending on a boolean:
I have the following class:
public class Item
{
public string Group { get; set; }
public string Person { get; set; }
public string Currency { get; set; }
public string Country { get; set; }
public string County { get; set; }
public string OtherAdd { get; set; }
public string Income { get; set; }
}
which is part of a List:
var results = items.ToList(); //items is IEnumerable<Item>
if int type = 1, then I want to group by more elements:
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency, e.Country, e.County, e.OtherAdd})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Currency = g.Key.Country,
Currency = g.Key.County,
Currency = g.Key.OtherAdd,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
if int type = 2, then I want to group by fewer elements (e.g. because OtherAdd would be an empty String):
results = results
.GroupBy(e => new { e.Group, e.Person, e.Branch, e.Currency})
.Select(g => new Item
{
Group = g.Key.Group,
Person = g.Key.Person,
Currency = g.Key.Currency,
Income = g.Sum(p => double.Parse(p.Income, System.Globalization.CultureInfo.InvariantCulture)).ToString("0.00", System.Globalization.CultureInfo.InvariantCulture)
})
.ToList();
etc.
Is there a way for me to change the GroupBy key depending on my integer type without repeating the code?
Well, you could use the old SQL trick, conditional values:
.GroupBy(e => new { e.Group, Person = (e.Type == 1 ? e.Person : Guid.NewGuid().ToString()), ... }
While this will still include the columns in the group by, all the items will have unique keys, so it doesn't quite matter. Sadly, I don't think there's a way around generating the unique keys, unlike in SQL (where you could just use NULL).
A better way might be to implement your own grouping class, instead of using an anonymous type. You could then use your own equality and hashing semantics, to make sure whether you include all the fields or not. However, that is arguably going to be more work than just having the similar code repeated.
Or, you might want to revise your whole design. It doesn't sound like what you're trying to do makes much sense - it's already quite suspicious that you're using the same type for two different things, and using strings for all the fields doesn't help either. Maybe you could try a different object design?

how to remove objects from list by multiple variables using linq

I want to remove objects from a list using linq,
for example :
public class Item
{
public string number;
public string supplier;
public string place;
}
Now i want to remove all of the items with the same number and supplier that appear more then once.
Thanks
This would be slightly tedious to do in-place, but if you don't mind creating a new list, you can use LINQ.
The easiest way to specify your definition of item-equality is to project to an instance of an anonymous-type - the C# compiler adds a sensible equality-implementation on your behalf:
List<Item> items = ...
// If you mean remove any of the 'duplicates' arbitrarily.
List<Item> filteredItems = items.GroupBy(item => new { item.number, item.supplier })
.Select(group => group.First())
.ToList();
// If you mean remove -all- of the items that have at least one 'duplicate'.
List<Item> filteredItems = items.GroupBy(item => new { item.number, item.supplier })
.Where(group => group.Count() == 1)
.Select(group => group.Single())
.ToList();
If my first guess was correct, you can also consider writing an IEqualityComparer<Item> and then using the Distinct operator:
IEqualityComparer<Item> equalityComparer = new NumberAndSupplierComparer();
List<Item> filteredItems = items.Distinct(equalityComparer).ToList();
Btw, it's not conventional for types to expose public fields (use properties) or for public members to have camel-case names (use pascal-case). This would be more idiomatic:
public class Item
{
public string Number { get; set; }
public string Supplier { get; set; }
public string Place { get; set; }
}

Easiest way to merge two List<T>s

I've got two List<Name>s:
public class Name
{
public string NameText {get;set;}
public Gender Gender { get; set; }
}
public class Gender
{
public decimal MaleFrequency { get; set; }
public decimal MaleCumulativeFrequency { get; set; }
public decimal FemaleCumulativeFrequency { get; set; }
public decimal FemaleFrequency { get; set; }
}
If the NameText property matches, I'd like to take the FemaleFrequency and FemaleCumulativeFrequency from the list of female Names and the MaleFrequency and MaleCumulativeFrequency values from the list of male Names and create one list of Names with all four properties populated.
What's the easiest way to go about this in C# using .Net 3.5?
Are you attempting to sum each of the values when you merge the lists? If so, try something like this:
List<Name> list1 = new List<Name>();
List<Name> list2 = new List<Name>();
List<Name> result = list1.Union(list2).GroupBy(x => x.NameText).Select(x => new
Name
{
NameText = x.Key,
Gender = new Gender
{
FemaleCumulativeFrequency = x.Sum(y => y.Gender.FemaleCumulativeFrequency),
FemaleFrequency = x.Sum(y => y.Gender.FemaleFrequency),
MaleCumulativeFrequency = x.Sum(y => y.Gender.MaleCumulativeFrequency),
MaleFrequency = x.Sum(y => y.Gender.MaleFrequency)
}
}).ToList();
What this does is the following:
Unions the lists, creating an IEnumerable<Name> that contains the contents of both lists.
Groups the lists by the NameText property, so if there are duplicate Names with the same NameText, they'll show up in the same group.
Selects a set of new Name objects, with each grouped Name's properties summed... you can also use Average if that makes more sense.
Converts the entire query to a List<Name> by calling the "ToList()" method.
Edit: Or, as you've said below, you simply want to merge the two lists... do this:
List<Name> allNames = femaleNames.Union(maleNames).ToList();
This looks a lot like the census name frequency data, right? Gender is a bit of a misnomer for the class you have it's more like "FrequencyData".
In effect you want a Dictionary so you can lookup any name and get the four values for it. You could simply take the males and do ToDictionary(...) on it and then iterate over the females, if the name exists in the dictionary, replace the female probabilities on it, if it doesn't exist, create a new dictionary entry.
My own approach to this same data was to create a Table in a database with all four values attached.
Here's some code for your scenario ...
Dictionary<string, Gender> result;
result = males.ToDictionary(x => x.NameText, x => x.Gender);
foreach (var female in females)
{
if (result.ContainsKey(female.NameText))
{
result[female.NameText].FemaleCumulativeFrequency = female.Gender.FemaleCumulativeFrequency;
result[female.NameText].FemaleFrequency = female.Gender.FemaleFrequency;
}
else
result.Add(female.NameText, female.Gender);
}
I think this could be what you want although I'm not sure if it handles the cumulative frequencies as you'd expect:
var mergedNameList = maleNames
.Concat(femaleNames)
.GroupBy(n => n.NameText)
.Select(nameGroup => new Name
{
NameText = nameGroup.Key,
Gender = new Gender
{
MaleFrequency = nameGroup.Sum(n => n.Gender.MaleFrequency),
MaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.MaleCumulativeFrequency),
FemaleFrequency = nameGroup.Sum(n => n.Gender.FemaleFrequency),
FemaleCumulativeFrequency = nameGroup.Sum(n => n.Gender.FemaleCumulativeFrequency)
}
}.ToList();

Categories