How can I sort a list of users by last name in the formats below?
first.last
first.mi.last
mylist.Sort();
sorts by first name, and that is not what i want to do. Do I somehow need to use a RegEx?
You can use Linq to order your users:
using System.Linq;
mylist.OrderBy(x => x.LastName);
If you have same last names you can order users by middle name. If you have same middle names you can order users by first name:
mylist.OrderBy(x => x.LastName).ThenBy(x => x.MiddleName).ThenBy(x => x.FirstName);
Assuming that your names are simple strings (and not objects with FirstName and LastName properties) like this:
var list = new List<string> { "c.a", "a.c" , "b"};
you can order them like this:
var orderedList = list.OrderBy(item => item.Split('.').Last());
Output:
c.a
b
a.c
If you want to sort in place, try specifing the comparer
mylist.Sort((left, right) => string.Compare(left.LastName, right.LastName));
In case mylist contains string items, you have to extract LastName:
private static String LastName(string value) {
if (string.IsNullOrEmpty(value))
return value;
int p = value.LastIndexOf('.');
return value.SubString(p + 1);
}
...
mylist.Sort((left, right) => string.Compare(LastName(left), LastName(right)));
Related
I try to sort a list that contains filepaths.
And I want them to be sorted by the numbers in them.
With the given code I use I don't get the expected result.
var mylist = mylist.OrderBy(x => int.Parse(Regex.Replace(x, "[^0-9]+", "0"))).ToList<string>();
I expect the result to be:
c:\somedir\1.jpg
c:\somedir\2.jpg
c:\somedir\3.jpg
c:\somedir\7.jpg
c:\somedir\8.jpg
c:\somedir\9.jpg
c:\somedir\10.jpg
c:\somedir\12.jpg
c:\somedir\20.jpg
But the output is random.
There is a simple way of achieving that.
Let's say you have a string list like this:
List<string> allThePaths = new List<string>()
{
"c:\\somedir\\1.jpg",
"c:\\somedir\\2.jpg",
"c:\\somedir\\20.jpg",
"c:\\somedir\\7.jpg",
"c:\\somedir\\12.jpg",
"c:\\somedir\\8.jpg",
"c:\\somedir\\9.jpg",
"c:\\somedir\\3.jpg",
"c:\\somedir\\10.jpg"
};
You can get the desired result with this:
List<string> sortedPaths = allThePaths
.OrderBy(stringItem => stringItem.Length)
.ThenBy(stringItem => stringItem).ToList();
Note: Also make sure you've included LINQ:
using System.Linq;
Here is a demo example just in case it's needed.
More complex solutions can be found there.
A cleaner way of doing this would be to use System.IO.Path:
public IEnumerable<string> OrderFilesByNumberedName(IEnumerable<string> unsortedPathList) =>
unsortedPathList
.Select(path => new { name = Path.GetFileNameWithoutExtension(path), path }) // Get filename
.OrderBy(file => int.Parse(file.name)) // Sort by number
.Select(file => file.path); // Return only path
I Have a model in my Web-Application like this:
class MyClass
{
int ID;
List<AnotherClass> foo {get; set;}
}
class AnotherClass
{
int ID;
string bar;
}
I also have a list of filter-strings given by the user.
List<string> filter = new List<string> { "foo", "bar" } //as example
What I want is to filter MyClass by the given strings like this:
var result = context.MyClass.Include(mc => mc.foo);
result = result.Where(x => filter.Any(f => f == x.foo.Select(d => d.bar)));
The Problem is: Select() returns a List of strings and I can't compare a string with a list of strings.
Anyone an idea how to fix this issue?
If putting into words what you want is: Find whichever item in result, if any of its inner class items is present in the filter list
Try:
result.Where(item => item.Any(innerItem => filter.Contains(innerItem.bar)))
Or in the other syntax:
var output = (from item in result
from innerItem in item.foo
where filter.Contains(innerItem.bar)
select item);
Use Contains
result = result.Where(x => x.foo.Any(i => filter.Contains(i.bar)));
You probably need simple Any and Contains methods, and you could do this.
result.Where(x => x.foo.Any(f=> filter.Contains(f.bar));
I want to sort a C# list by word. Assume I have a C# list (of objects) which contains following words:
[{id:1, name: "ABC"},
{id:2, name: "XXX"},
{id:3, name: "Mille"},
{id:4, name: "YYY"},
{id:5, name: "Mill",
{id:6, name: "Millen"},
{id:7, name: "OOO"},
{id:8, name: "GGGG"},
{id:9, name: null},
{id:10, name: "XXX"},
{id:11, name: "mil"}]
If user pass Mil as a search key, I want to return all the words starting with the search key & then all the words which does not match criteria & have them sort alphabetically.
Easiest way I can think of is to run a for loop over the result set, put all the words starting with search key into one list and put the renaming words into another list. Sort the second list and them combine both the list to return the result.
I wonder if there is a smarter or inbuilt way to get the desired result.
Sure! You will sort by the presence of a match, then by the name, like this:
var results = objects.OrderByDescending(o => o.Name.StartsWith(searchKey))
.ThenBy(o => o.Name);
Note that false comes before true in a sort, so you'll need to use OrderByDescending.
As AlexD points out, the name can be null. You'll have to decide how you want to treat this. The easiest way would be to use o.Name?.StartsWith(searchKey) ?? false, but you'll have to decide based on your needs. Also, not all Linq scenarios support null propagation (Linq To Entities comes to mind).
This should do it, but there's probably a faster way, maybe using GroupBy somehow.
var sorted = collection
.Where(x => x.Name.StartsWith(criteria))
.OrderBy(x => x.Name)
.Concat(collection
.Where(x => !x.Name.StartsWith(criteria))
.OrderBy(x => x.Name))
You can try GroupBy like this:
var sorted = collection
.GroupBy(item => item.Name.StartsWith(criteria))
.OrderByDescending(chunk => chunk.Key)
.SelectMany(chunk => chunk
.OrderBy(item => item.Name));
Separate items into two groups (meets and doesn't meet the criteria)
Order the groups as whole (1st that meets)
Order items within each group
Finally combine the items
There's nothing C#-specific to solve this, but it sounds like you're really looking for algorithm design guidance.
You should sort the list first. If this is a static list you should just keep it sorted all the time. If the list is large, you may consider using a different data structure (Binary Search Tree, Skip List, etc.) which is more optimized for this scenario.
Once it's sorted, finding matching elements becomes a simple binary search. Move the matching elements to the beginning of the result set, then return.
Add an indicator of a match into the select, and then sort on that:
void Main()
{
word[] Words = new word[11]
{new word {id=1, name= "ABC"},
new word {id=2, name= "XXX"},
new word {id=3, name= "Mille"},
new word {id=4, name= "YYY"},
new word {id=5, name= "Mill"},
new word {id=6, name= "Millen"},
new word {id=7, name= "OOO"},
new word {id=8, name= "GGGG"},
new word {id=9, name= null},
new word {id=10, name= "XXX"},
new word {id=11, name= "mil"}};
var target = "mil";
var comparison = StringComparison.InvariantCultureIgnoreCase;
var q = (from w in Words
where w.name != null
select new {
Match = w.name.StartsWith(target, comparison)?1:2,
name = w.name})
.OrderBy(w=>w.Match).ThenBy(w=>w.name);
q.Dump();
}
public struct word
{
public int id;
public string name;
}
It is probably not easier but you could create a class that implements IComparable Interface and have a property Mil that is used by CompareTo.
Then you could just call List.Sort(). And you can pass an IComparer to List.Sort.
It would probably be the most efficient and you can sort in place rather than producing a new List.
On average, this method is an O(n log n) operation, where n is Count;
in the worst case it is an O(n ^ 2) operation.
public int CompareTo(object obj)
{
if (obj == null) return 1;
Temperature otherTemperature = obj as Temperature;
if (otherTemperature != null)
{
if(string.IsNullOrEmpty(Mil)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill) && otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(!this.Name.StartsWith(Mill) && !otherTemperature.Name.StartsWith(Mill)
return this.Name.CompareTo(otherTemperature.Name);
else if(this.Name.StartsWith(Mill))
return 1;
else
return 0;
}
else
throw new ArgumentException("Object is not a Temperature");
}
You will need to add how you want null Name to sort
First create a list of the words that match, sorted.
Then add to that list all of the words that weren't added to the first list, also sorted.
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(words.Where(word => word.Name.StartsWith(keyword))
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}
Some of the comments suggest that it should be case-insensitive. That would be
public IEnumerable<Word> GetSortedByMatches(string keyword, Word[] words)
{
var result = new List<Word>(
words.Where(word => word.Name.StartsWith(keyword, true)) //<-- ignoreCase
.OrderBy(word => word.Name));
result.AddRange(words.Except(result).OrderBy(word => word.Name));
return result;
}
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
I have list i.e. List<Field>. This Field class contains a code and a value properties among other fields and I would like to be able to use linq in order to sum up all the values for the same code.
I know I could loop through my list and add this to a dictionary using the following code, but I'm sure there has to be a cleaner way to do this:
if (totals.ContainsKey(code))
{
totals[code] += value;
}
else
{
totals.Add(code, value);
}
Any ideas?
I found something similar, but this applied to a list> which isn't what I have:
var result = Sales.SelectMany(d => d) // Flatten the list of dictionaries
.GroupBy(kvp => kvp.Key, kvp => kvp.Value) // Group the products
.ToDictionary(g => g.Key, g => g.Sum());
from this article [Sum amount using Linq in <List<Dictionary<string, int>>]Sum amount using Linq in <List<Dictionary<string, int>>
Any ideas? I could always change my code to have a Dictionary<string, Field> but I'm sure there has to be a way to do this with a list and linq.
Thanks.
UPDATE:
Sorry, I think I omitted an important section in regards to the above. The list is contained within another list i.e. List> myitemList; which will contain other irrelevant fields which may require further filtering. I'm not sure???
NOTE: Sorry formatting is messed up once again!
To give a bit of context to this:
Item1 (of type List)
Item Name Value
(Item 1) Type 1
(Item 2) Description Test
(Item 3) Code A
(Item 4) Net 100.00
Item2 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test1
(Item 3) Code B
(Item 4) Net 95.55
Item3 (of type List)
Item Name Value
(Item 1) Type 2
(Item 2) Description Test2
(Item 3) Code A
(Item 4) Net 35.95
As you can see each list of type List contains 4 Field entries where my Field is defined with Name (String) and Value (Object)
Each of these list is then added to a main list. So I need to loop through the main list and in turn I want to end up with a dictionary what will contain the "Code" and sum of "Net" for each list. So at the end, I should just end up with
A, 135.95
B, 95.55
I don't know if the above make sense. I hope it does!
UPDATE
The fact that I'm dealing with a list> actually didn't make a different as I actually wanted to sum up one list at the time, so provide answer is correct! Thank you!
The code that you posted is almost what you need - for using it on the list you need to simplify it slightly:
var result = myList // No flattening
.GroupBy(x => x.Code) // Group the items by the Code
.ToDictionary(g => g.Key, g => g.Sum(v => v.Value)); // Total up the values
var list = new List<Field>
{
new Field { Code = "A", Value = 10 },
new Field { Code = "A", Value = 20 },
new Field { Code = "B", Value = 30 },
};
var dic = list
.GroupBy(z => z.Code)
.ToDictionary(z => z.Key, z => z.Sum(f => f.Value));
You can do:
Dictionary<string, int> dictionary =
list.GroupBy(r => r.ID)
.ToDictionary(grp => grp.Key, grp => grp.Sum(r => r.Value));
Considering you have class like:
public class MyClass
{
public string ID { get; set; }
public int Value { get; set; }
}
I have now 2 lists:
list<string> names;
list<int> numbers;
and I need to sort my names based on the values in numbers.
I've been searching, and most use something like x.ID, but i don't really know what that value is. So that didn't work.
Does anyone know, what to do, or can help me out in the ID part?
So i assume that the elements in both lists are related through the index.
names.Select((n, index) => new { Name = n, Index = index })
.OrderBy(x => numbers.ElementAtOrDefault(x.Index))
.Select(x => x.Name)
.ToList();
But i would use another collection type like Dictionary<int,string> instead if both lists are related insomuch.
Maybe this is a task for the Zip method. Something like
names.Zip(numbers, (name, number) => new { name, number, })
will "zip" the two sequences into one. From there you can either order the sequence immediately, like
.OrderBy(a => a.number)
or you can instead create a Dictionary<,>, like
.ToDictionary(a => a.number, a => a.name)
But it sounds like what you really want is a SortedDictionary<,>, not a Dictionary<,> which is organized by hash codes. There's no LINQ method for creating a sorted dictionary, but just say
var sorted = new SortedDictionary<int, string>();
foreach (var a in zipResultSequence)
sorted.Add(a.number, a.name);
Or alternatively, with a SortedDictionary<,>, skip Linq entirely, an go like:
var sorted = new SortedDictionary<int, string>();
for (int idx = 0; idx < numbers.Count; ++idx) // supposing the two list have same Count
sorted.Add(numbers[idx], names[idx]);
To complement Tims answer, you can also use a custom data structure to associate one name with a number.
public class Person
{
public int Number { get; set; } // in this case you could also name it ID
public string Name { get; set; }
}
Then you would have a List<Person> persons; and you can sort this List by whatever Attribute you like:
List<Person> persons = new List<Person>();
persons.Add(new Person(){Number = 10, Name = "John Doe"});
persons.Add(new Person(){Number = 3, Name = "Max Muster"});
// sort by number
persons = persons.OrderBy(p=>p.Number).ToList();
// alternative sorting method
persons.Sort((a,b) => a.Number-b.Number);
I fixed it by doing it with an dictionary, this was the result:
dictionary.OrderBy(kv => kv.Value).Reverse().Select(kv => kv.Key).ToList();