C# - Populate a list using lambda expressions or LINQ - c#

It's been a while since I've used lambda expressions or LINQ and am wondering how I would do the following (I know I can use a foreach loop, this is just out of curiosity) using both methods.
I have an array of string paths (does it make a difference if it's an array or list here?) from which I want to return a new list of just the filenames.
i.e. using a foreach loop it would be:
string[] paths = getPaths();
List<string> listToReturn = new List<string>();
foreach (string path in paths)
{
listToReturn.add(Path.GetFileName(path));
}
return listToReturn;
How would I do the same thing with both lambda and LINQ?
EDIT: In my case, I'm using the returned list as an ItemsSource for a ListBox (WPF) so I'm assuming it's going to need to be a list as opposed to an IEnumerable?

Your main tool would be the .Select() method.
string[] paths = getPaths();
var fileNames = paths.Select(p => Path.GetFileName(p));
does it make a difference if it's an array or list here?
No, an array also implements IEnumerable<T>
Note that this minimal approach involves deferred execution, meaning that fileNames is an IEnumerable<string> and only starts iterating over the source array when you get elements from it.
If you want a List (to be safe), use
string[] paths = getPaths();
var fileNames = paths.Select(p => Path.GetFileName(p)).ToList();
But when there are many files you might want to go the opposite direction (get the results interleaved, faster) by also using a deferred execution source:
var filePaths = Directory.EnumerateFiles(...); // requires Fx4
var fileNames = filePaths.Select(p => Path.GetFileName(p));
It depends on what you want to do next with fileNames.

I think by "LINQ" you really mean "a query expression" but:
// Query expression
var listToReturn = (from path in paths
select Path.GetFileName(path)).ToList();
// Extension methods and a lambda
var listToReturn = paths.Select(path => Path.GetFileName(path))
.ToList();
// Extension methods and a method group conversion
var listToReturn = paths.Select(Path.GetFileName)
.ToList();
Note how the last one works by constructing the projection delegate from a method group, like this:
Func<string, string> projection = Path.GetFileName;
var listToReturn = paths.Select(projection).ToList();
(Just in case that wasn't clear.)
Note that if you don't need to use this as a list - if you just want to iterate over it, in other words - you can drop the ToList() call from each of these approaches.

It's just:
var listToReturn = getPaths().Select(x => Path.GetFileName(x)).ToList();
As already stated in other answers, if you don't actually need a List<string> you can omit the ToList() and simply return IEnumerable<string> (for example if you just need to iterate it, IEnumerable<> is better because avoids the creation of an other list of strings)
Also, given that Select() method takes a delegate, and there's an implicit conversion between method groups and delegates having the same signature, you can skip the lambda and just do:
getPaths().Select(Path.GetFileName)

You could do it like this:
return getPaths().Select(Path.GetFileName);

listToReturn = paths.ToList().Select(p => Path.GetFileName(p));

Related

Sort list items [full path] by file name in C#

I have a list of type string. Each string is a full path to a file. Now I would like to sort this list by the name of the file.
When I use the Sort() method, the list would be sorted by the full path. But the path can be different.
Is there a simple way to sort the list by whats left after the last \ of each item?
You can use System.IO.Path.GetFileName and LINQ:
files = files.OrderBy(System.IO.Path.GetFileName).ToList();
If you want to compare in a case-insensitive manner you can pass a StringComparer:
files = files.OrderBy(System.IO.Path.GetFileName, StringComparer.InvariantCultureIgnoreCase).ToList();
Note that OrderBy (and OrderByDescending) uses a stable sort algorithm as opposed to List.Sort. That means that all elements which are equal stay in the original order.
You're looking for Path.GetFileName:
https://msdn.microsoft.com/en-us/library/system.io.path.getfilename(v=vs.110).aspx
List<string> files = ...
// when sorting via Sort we should compare file names
files.Sort((left, right) =>
string.Compare(Path.GetFileName(left), Path.GetFileName(right)));
The advantages of Sort over Linq OrderBy are
Sort performs in place sorting when Linq creates an additional collection via .ToList()
You can put a complex sorting algorithm just within the lambda function; in case of Linq one has to implement IComparer<T> interface
The disadvantages are
Linq wants just IEnumerable<T> when Sort requires List<T>.
Sort() uses unstable sorting algorithms (if two items considered being equal their actual order in the sorted list is not guaranteed)
current simplest implementtation string.Compare(Path.GetFileName(left), Path.GetFileName(right)) calls Path.GetFileName too often (about twice as many as OrderBy)
this example code,
var getFileName = System.IO.Path.GetFileName("C:\temp\name.txt"); > temp.txt
var getfileNameWithoutExtention = System.IO.Path.GetFileName("C:\temp\name.txt"); > name
var getextention = System.IO.Path.GetFileName("C:\temp\name.txt"); > .txt
List<string> patsh = new List<string>
{
#"C:\temp\name.txt",
#"C:\music\ranbow.mp3",
#"C:\net\core.cs",
#"C:\java\fr.jar"
};
patsh = patsh.OrderBy(System.IO.Path.GetFileNameWithoutExtension).ToList();
if you want to do it with file extension
patsh = patsh.OrderBy(System.IO.Path.GetFileNameWithoutExtension).ThenBy(System.IO.Path.GetExtension).ToList();
This might do the trick for you
List<string> fullfilepath = new List<string>() { "C:\\text1.txt", "C:\\text2.txt", "D:\\text4.txt", "C:\\text3.txt", "C:\\text6.txt", "D:\\text5.txt" };
List<string> sortedfilename = fullfilepath.OrderBy(x => Path.GetFileName(x)).ToList();
You can also sort it by fileName like this:
fileName = path.Split('\').Last();

Add mutliple Lists to one list

I have multiple functions that returns a List of objects. How can I add them into one list.
var List1 = GetList1().RunFilter1();
var List2 = GetList2();
The AddRange() function gets far to messy.
List1.AddRange(List2.AddRange(List3.AddRange(List4.AddRange(...);
Is there a pattern that I can use that it will be easier. I also have extension methods (Filters) that apply to certain lists. Which I interchange based on requirement.
Something like this:
var CombinedList = GetAllLists(GetList1().RunFilter1(),
GetList2(),
GetList3().RunFilter2(),
GetList4() ...);
Keep in mind that the GetList() functions being fetched might change.
Thanks for any help!
You can use some Linq extensions to help you out with the format a bit
var joined = GetList1()
.Concat(GetList2())
.Concat(GetList3().RunFilter())
...
;
You could first insert all your lists into another list:
var temp = new List<T>
{
GetList1().RunFilter1(),
GetList2(),
GetList3().RunFilter2(),
GetList4()
};
Then using the SelectMany method flatten this list.
var combined = temp.SelectMany(item=>item).ToList();

Convert list of of objects to list of tuple without iterating

I'm trying to add an extra parameter to a list of ef objects to track processing, but I keep running into having to initialize each list item explicitly. What's the correct linq way to do this? Aside from terseness, is there any advantage to a linq syntax in this case?
List<app_subjects> subjectList = AppMySQLQueries.GetAllSubjects();
List<Tuple<app_subjects, bool>> subjectCollection = new List<Tuple<app_subjects, bool>>(subjectList.Count);
foreach (app_subjects subject in subjectList)
{
subjectCollection.Add(Tuple.Create(subject, false));
}
I have searched the site without success.
You just want to use a projection here ( Select ) which applies the transformation in your lambda expression to each element in the source collection.
List<Tuple<app_subjects, bool>> tuples = subjectList.Select(x => new Tuple<app_subjects, bool>(x, false)).ToList();
The ToList() call is not entirely necessary, if you removed it then the method will return an IEnumerable<Tuple<app_subjects, bool>>. If you're just going to iterate the collection of tuples afterwards the ToList call should be removed as it forces execution (enumerates the IEnumberable) and then your next operation (the foreach) would do the same, making the code perform worse.
Like this?
subjectList.Select(s => Tuple.Create(s, false)).ToList();
With C# 10.0 (.NET 6.0) this is even easier and cleaner. Along with named tuples we can also declare a tuple by simply putting the values in round brackets.
List<(string NamedProperty1, int NamedProperty2)> _tuples = new();
_tuples = _objectList.Select(o => (o.SomeProperty1, o.SomeProperty2)).ToList();
try this.
List<Tuple<app_subjects, bool>> subjectCollection = subjectList.CovertAll( subject => new Tuple<app_subjects, bool>(){
subject,
false
}).ToList();

Could not find an implementation of the query pattern Error

Given
var selectedItems = listBoxControl1.SelectedItems;
var selectedItemsList = (from i in selectedItems
select i).ToList();
I receive Error
Could not find an implementation of the query pattern for source type
'DevExpress.XtraEditors.BaseListBoxControl.SelectedItemCollection'.
'Select' not found. Consider explicitly specifying the type of the
range variable 'i'.
using system.LINQ Done
I can use foreach so it must implement IEnumerable. I prefer to use LINQ over foreach to gather each string, if possible.
I want to take the ToString() values for each SelectedItem in the list box control and stick them in a List<string>. How can I do it?
I can use foreach so it must implement IEnumerable.
That's not actually true, but it's irrelevant here. It does implement IEnumerable, but not IEnumerable<T> which is what LINQ works over.
What's actually in the list? If it's already strings, you could use:
var selectedItemsList = selectedItems.Cast<string>().ToList();
Or if it's "any objects" and you want to call ToString you can use:
var selectedItemsList = selectedItems.Cast<object>()
.Select(x => x.ToString())
.ToList();
Note that the call to Cast is why the error message suggested using an explicitly typed range variable - a query expression starting with from Foo foo in bar will be converted to bar.Cast<Foo>()...
For LINQ to work, you need an IEnumerable<T>, straight IEnumerable isn't enough. Try:
var selectedItems = listboxControl1.SelectedItems.Cast<T> //where T is the actual type of the item
Try just
var result = listBoxControl1.SelectedItems.Cast<MyItemType>().ToList();

How to remove x items from collection using LINQ?

Is there a way to remove all items except first one from any type of collection (Control.Items, List ....) using LINQ only ?
No. LINQ is designed for querying collections (no side-effects), not for adding or removing items.
What you can do is write a query that takes the first element of the collection:
var result = source.Take(1);
Note that LINQ doesn't work with all types of collections; you need a LINQ provider to make LINQ work. For instance, source must implement IEnumerable<T> to use the extension methods of the Enumerable Class (LINQ-to-Objects).
How about something using reflection?
static void RemoveButFirst(object o){
Type t = o.GetType();
System.Reflection.MethodInfo rm = t.GetMethod("RemoveAt",
new Type[]{typeof(int)});
System.Reflection.PropertyInfo count = t.GetProperty("Count");
for (int n = (int)(count.GetValue(o,null)) ; n>1; n--)
rm.Invoke(o, new object[]{n-1});
}
This would work any time your collection exposed an int Count property and a RemoveAt(int) method, which I think those collections should.
And a more concise version, using dynamic, if you work with C# 4.0:
public static void RemoveBut(dynamic col, int k){
for (int n = col.Count; n>k; n--)
col.RemoveAt(n-1);
}
You can use .Take(1), but it returns a new collection, and leaves the original intact.
The idea of LINQ came from functional programming where everything is immutable, because of that, they didn't make it possible to modify the collections with LINQ.
Jon Skeet has a comment on the subject: LINQ equivalent of foreach for IEnumerable<T>
How about (in linq):
var result = list.Where(l => l != list.First());
But this would be better:
var result = list.Take(1);
List<string> collection = new List<string>();
collection.RemoveAll(p => p.StartsWith("something"));
listXpto.Where(x=>true /* here goes your query */)
.Select(x=>{listXpto.Remove(x); return null})
But I donĀ“t know the real utility of that.
Remember that the remove method is for ILists, not IQueryable in general.

Categories

Resources