I was wondering, is there an effective way to implement branching logic in a collection using LINQ, without iterating the collection more than once.
For example
foreach (string file in Files){
if (file== "file1"){
\\do something
}
else if (file== "file2")
\\do something
}
else if (file== "file3")
\\do something
}
}
I have found a solution using lookups but only works for if-else cases
var group = Files.ToLookup(f => f=="file1");
var file1Group= group[true].ToList();
Thanks in advance.
Does this help? Basically you project a new anonymous type with extra properties that you want to filter by, order by, group by or whatever.
using System;
using System.Linq;
namespace Test
{
public class Program
{
static void Main(string[] args)
{
var Files = new string[] { "file1", "file2" };
var withMetaData = Files.Select(z =>
new { file = z, IsFile1 = z == "file1", IsFile2 = z == "file2"});
// You can now OrderBy or GroupBy or whatever you fancy here
foreach (var fileWithMetaData in withMetaData)
{
Console.WriteLine(fileWithMetaData);
}
Console.ReadLine();
}
}
}
If I have a large number of reoccurring actions, which are defined before the loop and have to be reusable (otherwise the if...else as you already have is probably preferable), I've got a tendency to put references to those actions in a dictionary (or other collection). For this particular example (files), that would only make sense if multiple folders are processed with the same filenames, or the 'filex' parts are obtained from a substring, but the concept can be applied to any reoccurring branching logic.
The example below uses lambdas, but any existing method with the same signature can be used
var actions = new Dictionary<string,Action>{
{"file1", () => Console.WriteLine("abc")},
{"file2", () => {var foo ="def"; Console.WriteLine(foo);}},
};
//example call, only to show usage
foreach(var file in new[]{"file1","file1","file2","file1"})
actions[file](); //nb, for the example no check was added. For real use 'TryGetValue' can be used
Of course, this is assuming an action has to be performed on each file, otherwise a group by can be used.
Since an action is used for each file the example itself doesn't make much sense, since there is no specific action for the individual files. So in an effort to create a somewhat more sensible example:
var actions = new Dictionary<string,Action<string>>{
{"file1", f => Console.WriteLine("abc: {0}", f)},
{"file2", f => {var foo ="def"; Console.WriteLine(foo);}},
};
foreach(var file in new[]{#"c:\temp\file1",#"c:\someotherfolder\file1",#"c:\temp\file2",#"c:\abc\file1"})
actions[Path.GetFileNameWithoutExtension(file)](file); //nb, for the example no check was added. For real use 'TryGetValue' can be used
Related
I am working with an XML standard called SDMX. It's fairly complicated but I'll make it as short as possible. I am receiving an object called CategoryScheme. This object can contain a number of Category, and each Category can contain more Category, and so on, the chain can be infinite. Every Category has an unique ID.
Usually each Category contains a lot of Categories. Together with this object I am receiving an Array, that contains the list of IDs that indicates where a specific Category is nested, and then I am receiving the ID of that category.
What I need to do is to create an object that maintains the hierarchy of the Category objects, but each Category must have only one child and that child has to be the one of the tree that leads to the specific Category.
So I had an idea, but in order to do this I should generate LINQ queries inside a cycle, and I have no clue how to do this. More information of what I wanted to try is commented inside the code
Let's go to the code:
public void RemoveCategory(ArtefactIdentity ArtIdentity, string CategoryID, string CategoryTree)
{
try
{
WSModel wsModel = new WSModel();
// Prepare Art Identity and Array
ArtIdentity.Version = ArtIdentity.Version.Replace("_", ".");
var CatTree = JArray.Parse(CategoryTree).Reverse();
// Get Category Scheme
ISdmxObjects SdmxObj = wsModel.GetCategoryScheme(ArtIdentity, false, false);
ICategorySchemeMutableObject CatSchemeObj = SdmxObj.CategorySchemes.FirstOrDefault().MutableInstance;
foreach (var Cat in CatTree)
{
// The cycle should work like this.
// At every iteration it must delete all the elements except the correct one
// and on the next iteration it must delete all the elements of the previously selected element
// At the end, I need to have the CatSchemeObj full of the all chains of categories.
// Iteration 1...
//CatSchemeObj.Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 2...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Iteration 3...
//CatSchemeObj.Items.ToList().SingleOrDefault().Items.ToList().SingleOrDefault().Items.ToList().RemoveAll(x => x.Id != Cat.ToString());
// Etc...
}
}
catch (Exception ex)
{
throw ex;
}
}
Thank you for your help.
So, as i already said in my comment, building a recursive function should fix the issue. If you're new to it, you can find some basic information about recursion in C# here.
The method could look something like this:
private void DeleteRecursively(int currentRecursionLevel, string[] catTree, ICategorySchemeMutableObject catSchemeObj)
{
catSchemeObj.Items.ToList().RemoveAll(x => x.Id != catTree[currentRecursionLevel].ToString());
var leftoverObject = catSchemeObj.Items.ToList().SingleOrDefault();
if(leftoverObject != null) DeleteRecursively(++currentRecursionLevel, catTree, leftoverObject);
}
Afterwards you can call this method in your main method, instead of the loop:
DeleteRecursively(0, CatTree, CatSchemeObject);
But as i also said, keep in mind, that calling the method in the loop, seems senseless to me, because you already cleared the tree, besides the one leftover path, so calling the method with the same tree, but another category, will result in an empty tree (in CatSchemeObject).
CAUTION! Another thing to mention i noticed right now: Calling to list on your Items property and afterwards deleting entries, will NOT affect your source object, as ToList is generating a new object. It IS keeping the referenced original objects, but a deletion only affects the list. So you must write back the resulting list to your Items property, or find a way to directly delete in the Items object. (Assuming it's an IEnumerable and not a concrete collection type you should write it back).
Just try it out with this simple example, and you will see that the original list is not modified.
IEnumerable<int> test = new List<int>() { 1, 2, 3, 4 , 1 };
test.ToList().RemoveAll(a => a != 1);
Edited:
So here is another possible way of going after the discussion below.
Not sure what do you really need so just try it out.
int counter = 0;
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
var temp = list.Where(x => CatTree[counter++] == x.Id); // or != ? play with it .
list = temp.Items.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
I just translated you problem to 2 solutions, but I am not sure if you won't lose data because of the SingleOrDefault call. It means 'Grab the first item regardless of everything'. I know you said you have only 1 Item that is ok, but still... :)
Let me know in comment if this worked for you or not.
//solution 1
// inside of this loop check each child list if empty or not
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
while(true)
{
list.RemoveAll(x => x.Id != Cat.ToString());
list = list.ToList().SingleOrDefault();
if(list.Equals(default(list))
{
break;
}
}
}
}
//solution 2
foreach (var Cat in CatTree)
{
var list = CatSchemeObj.Items.ToList();
//check before you call it or you will get an error
if(!list.Equals(default(list)))
{
CleanTheCat(cat, list);
}
}
//use this recursive function outside of loop because it will cat itself
void CleanTheCat(string cat, List<typeof(ICategorySchemeMutableObject.Items) /*Place here whatever type you have*/> CatSchemeObj)
{
CatSchemeObj.RemoveAll(x => x.Id != cat);
var catObj = CatSchemeObj.Items.ToList().SingleOrDefault();
if (!catObj.Equals(default(catObj)){
CleanTheCat(cat, catObj);
}
}
Thank you to whoever tried to help but I solved it by myself in a much easier way.
I just sent the full CategoryScheme object to the method that converted it in the XML format, then just one line did the trick:
XmlDocument.Descendants("Category").Where(x => !CatList.Contains(x.Attribute("id").Value)).RemoveIfExists();
I'm looking for a way to update an Element in a List without enumerating it on my own.
I got the Class MyProjects which hold a List named Projects.
I want to find the MyProjects.Projects-Class, where a member property of Class1 (Name) equals the Value "Overhead".
What works:
foreach (Project prj in MyProjects.Projects) {
if (prj.Name == "Overhead")
prj.IsActive = true;
};
I, however, try to do the same by using Linq, but failed in writing it as one line. Is this even possible? The reason why I don't like to iterate in the way above is that I already iterate the whole list in this codeblock and think, that there might be a more beautiful way :)
You shouldn't try to get everything down to one line - just as brief as is readable. In this case, you can use:
foreach (var project in MyProjects.Projects.Where(p => p.Name == "Overhead"))
{
project.IsActive = true;
}
That's using LINQ for the querying part, which is appropriate as that's what the Q of LINQ stands for. I'd strongly urge you not to mutate items within LINQ calls in the way that Mayank's answer does though. It's error-prone (as evidenced by the original answer not working) and against the spirit of LINQ.
That's about as readable as it gets, IMO. It does exactly the same thing as the original code, mind you - you can't avoid something iterating over every item in the list, if every item might be one you want to update.
EDIT: Just for laughs, if you really, really wanted to do it in pretty minimal code, you could use:
// DON'T USE THIS!
MyProjects.Project.Count(p => p.Name == "Overhead" && (p.IsActive = true));
Here we use the fact that && is short-circuiting to avoid evaluating the assignment (p.IsActive = true) unless the condition is matched. It's handy that we're assigning a bool value to a property, as that means we don't need to do anything else to make it a valid second operand for the && operator. We use Count() to fully evaluate the result without creating any additional lists etc - and we use the version with a predicate to avoid even needing a Where call, which a previous version did. (LastOrDefault would work too.) But it's all a horrible abuse, and should never appear in any real code.
I've come up with a way to get it down to one line, without abusing LINQ, since I'm only using it for the querying part (filter), and using a custom extension method to perform the property setting action. You're still going to enumerate the items (you have to) but you can hide that away in the extension method. I suspect that you didn't really care whether you enumerated the item or not, you just didn't like the amount of visible space a foreach loop would take up in your main code.
Use this extension method:
public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (var item in list)
{
action.Invoke(item);
}
return list;
}
This allows you to get it down to one readable line.
Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true);
Complete test program:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var Projects = new List<Project>() {
new Project() { Name="Overhead", IsActive=false },
new Project() { Name="Nadfadfs", IsActive=false },
new Project() { Name="Overhead", IsActive=false },
new Project() { Name="dasfasdf", IsActive=false }
};
PrintProjectList(Projects);
Console.WriteLine("--Setting property--");
Projects.Where(p => p.Name == "Overhead").SetProperty(p => p.IsActive = true);
PrintProjectList(Projects);
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
static void PrintProjectList(IEnumerable<Project> projects)
{
foreach(var p in projects)
{
Console.WriteLine($"Name: {p.Name} IsActive: {p.IsActive}");
}
}
}
class Project
{
public string Name { get; set; }
public bool IsActive { get; set; }
}
public static class Extensions
{
public static IEnumerable<T> SetProperty<T>(this IEnumerable<T> list, Action<T> action)
{
foreach (var item in list)
{
action.Invoke(item);
}
return list;
}
}
}
Output:
Name: Overhead IsActive: False
Name: Nadfadfs IsActive: False
Name: Overhead IsActive: False
Name: dasfasdf IsActive: False
--Setting Property--
Name: Overhead IsActive: True
Name: Overhead IsActive: False
Name: Overhead IsActive: True
Name: Overhead IsActive: False
It turns out that my SetProperty function is very similar to the ForEach that's already built into the framework. The main difference being that mine can operate on any IEnumerable<T>. That syntax is loved by some, and hated by others, for reasons that Eric Lippert pointed out on his blog (Thanks to Jon Skeet for pointing this out). Also, see this discussion by the Microsoft team. I'll leave it to you to draw your own conclusion.
On a side note, calling it SetProperty is kind of inaccurate, because you could do any action on the items in the collection. You could call it ForEach, but that clashes with the framework. Not positive what I'd call it, but perhaps PerformAction.
Following Linq statement should work
MyProjects.Projects.Where(p => p.Name == "Overhead")
.Select(x => {x.IsActive = true; return x;})
.ToList();
As per comments from #JonSkeet, #TimSchmelter and #LeandroSoares, above code is really bad idea. Here are some reasons
Calling .ToList() causes whole lazy collection to be executed and loaded in the memory.
Code above isn't very readable and hard to maintain.
Code above is abusing the API as it is forcing the API to do things which it is not designed for.
Upon request, I have simplified this question. When trying to take two generic List and blend them, I get unexpected results.
private List<ConditionGroup> GetConditionGroupParents()
{
return (from Conditions in dataContext.Conditions
orderby Conditions.Name
select new ConditionGroup
{
GroupID = Conditions.ID,
GroupName = Conditions.Name,
/* PROBLEM */ MemberConditions = new List<Condition>()
}).ToList();
}
private List<ConditionGroup> BuildConditionGroups()
{
var results = GetConditionGroupParents();
// contents of ConditionMaps is irrelevant to this matter
List<ConditionMap> ConditionMaps = GenerateGroupMappings();
// now pair entries from the map into their appropriate group,
// adding them to the proper List<MemberConditions> as appropriate
foreach (var map in ConditionMaps)
{
results.Find(groupId => groupId.GroupID == map.GroupID)
.MemberConditions.Add(new ConditionOrphan(map));
}
return results;
}
I would expect each map in ConditionMaps to be mapped to a single ConditionGroup's MemberConditions in the "results.Find...." statement.
Instead, each map is being added to the list of every group, and that happens simultaneously/concurrently.
[edit] I've since proven that there is only a single instance of
List<Memberconditions>, being referenced by each group.
I unrolled the creation of the groups like so:
.
.
.
/* PROBLEM */ MemberConditions = null }).ToList();
foreach (var result in results)
{
List<Condition> memberConditions = new List<Condition>();
results.MemberConditions = memberConditions;
}
return results;
In that case I was able to watch each instantiation stepping
through the loop, and then it worked as expected. My question
remains, though, why the original code only created a single
instance. Thanks!
.
Why doesn't the LINQ query in GetConditionGroupParents "new up" a unique MemberConditions list for each Group, as indicated in the /* PROBLEM */ comment above?
Any insight is appreciated. Thanks!
Jeff Woods of
Reading, PA
This is a bug. As a workaround you can create a factory function
static List<T> CreateList<T>(int dummy) { ... }
And pass it any dummy value depending on the current row such as Conditions.ID.
This trick works because L2S, unlike EF, is capable of calling non-translatable functions in the last Select of the query. You will not have fun migrating to EF since they have not implemented this (yet).
I have the following code:
foreach (var b in userNames.Select(a => new User()))
{
...
}
This works quite well, since it gives me all "fresh" user objects, however Code Analysis complains that I shouldn't create unused locals, so my question is, is there a way of ignoring the arguments (similar to the "_" in Haskell).
PS: prehaps my example is not the best. I am sorry for this.
Thanks!
Update 1
I got the following code analysis error:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "a"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1804:RemoveUnusedLocals", MessageId = "b")]
_ is a perfectly valid variable name in C#. So writing
foreach(var b in userNames.Select(_ => new User()))
{
}
is perfectly valid code. It depends on your analysis rules whether it accepts such cases or not.
However, your code is indeed quite suspicious: you're mapping a collection of user names to a collection of users but you're not specifying a direct relation between the two: maybe you wanted to write something like this:
foreach(var b in userNames.Select(username => new User(username)))
To create a collection of objects of a given size, just use the length from the original collection.
var newColletion = Enumerable.Repeat(false, input.Length)
.Select(_ => new User());
but perhaps better would be your own helper method
static class MyEnumerable {
IEnumberable<T> Repeat<T>(Func<T> generator, int count) {
for (var i = 0; i < count; ++i) {
yield return generator();
}
}
}
and then use
var newCollection = MyEnumerable.Repeat(() => new User(), oldCollection.Length);
If quantity is your concern, and need linq, rewrite it as
foreach(var user in Enumerable.Repeat(()=>new User(),Usernames.Count).Select(x=>x()))
{
}
But, it may look ugly based on how you see it.
I'm sure there is a valid case where you would want to ignore arguments like this, but this doesn't seem like one of them. If you are creating N User objects for N userNames, surely you want to couple those together?
foreach (var b in userNames.Select(a => new { name = a, user = new User() }))
{
...
}
Then you won't have any unused arguments.
But the question remains why you aren't just doing this:
foreach (var name in userNames)
{
var user = new User();
// ...
}
As far as I can see, your use of .Select() makes no sense here.
Select performs a projection on the collection on which it is called, performing a specified operation on each element and returning the transformed results in another collection. If you do not need to perform any operation on the lambda element, you'd better simply create an array of User objects directly.
Answering your edit, as I said above, you can simply do this:
var NewColl = new User[userNames.Length];
As for initialization, you could have done this:
Enumerable.Repeat<User>(new User(), userNames.Length);
I have the following method that takes an extremely long time to run and would love some help to make it run faster and or be more efficient.
The main responsibility of the method is to take a list of data points created from a CSV file, map the Name property of the file datapoints to the to the HistorianTagname property in a list of tagnames by the DataLoggerTagname property and create a resulting list from the mapping. If the mapping does not exist, the file datapoint is ignored.
I know it that was long-winded, but I hope it makes sense. It may be easier just to look at the method:
private IEnumerable<DataPoint> GetHistorianDatapoints(IEnumerable<DataPoint> fileDatapoints, IEnumerable<Tagname> historianTagnames)
{
/**
** REFACTOR THIS
**/
foreach (var fileDatapoint in fileDatapoints)
{
var historianTagname = historianTagnames.FirstOrDefault(x => x.DataLoggerTagname.Equals(fileDatapoint.Name, StringComparison.OrdinalIgnoreCase));
if (historianTagname != null)
{
var historianDatapoint = new DataPoint();
historianDatapoint.Name = historianTagname.HistorianTagname;
historianDatapoint.Date = fileDatapoint.Date;
historianDatapoint.Value = fileDatapoint.Value;
yield return historianDatapoint;
}
}
}
Notes:
I have complete control of classes and methods of mapping, so if I am doing something fundamentally wrong. I would love to know!
Thanks!
I would start by fixing up:
var historianTagname = historianTagnames.FirstOrDefault(x => x.DataLoggerTagname.Equals(fileDatapoint.Name, StringComparison.OrdinalIgnoreCase))
That's a pretty expensive operation to run every iteration through this loop.
Below is my proposition:
private IEnumerable<DataPoint> GetHistorianDatapoints(IEnumerable<DataPoint> fileDatapoints, IEnumerable<Tagname> historianTagnames)
{
var tagNameDictionary = historianTagnames.ToDictionary(t => t.DataLoggerTagname, StringComparer.OrdinalIgnoreCase);
foreach (var fileDatapoint in fileDatapoints)
{
if (tagNameDictionary.ContainsKey(fileDatapoint.Name))
{
var historianTagname = tagNameDictionary[fileDatapoint.Name];
var historianDatapoint = new DataPoint();
historianDatapoint.Name = historianTagname.HistorianTagname;
historianDatapoint.Date = fileDatapoint.Date;
historianDatapoint.Value = fileDatapoint.Value;
yield return historianDatapoint;
}
}
}
Like #Sheldon Warkentin said FirstOrDefault is probably bottle neck of your function, i s better to create historianTagnames a Dictionary where Name is key, then in your function you can get value by key.
Something like bellow:
// this is passed to method
IDictionary<string, Tagname> historianTagnames;
// .. method body
var historianTagname = historianTagnames[fileDatapoint.Name];
ofcourse you need to add proper if's.
As others have said, a Dictionary<string, Tagname> might perform better.
var historianDict = new Dictionary<string, Tagname>();
foreach (var tagName in historianTagnames) {
historianDict[tagName.DataLoggerTagname.ToLowerInvariant()] = tagName;
}
foreach (var fileDatapoint in fileDatapoints) {
if (historianDict.ContainsKey(fileDatapoint.Name.ToLowerInvariant()) {
// ...
}
}