I'm writing a tool, and the first part of that tool is to collect all the header files in our public API. The problem is, two of the header files have duplicate file names (but they reside in different folders). This will cause a problem when creating a dictionary.
Originally I wrote a foreach loop to collect FileInfo instances into a dictionary. However lately I'm learning LINQ, and I wanted to convert the foreach loop into a LINQ statement.
The problem is when it executed, it complained about the duplicate file name.
Here is the original code:
public Dictionary<String, FileDependency> GetSDKFiles(DirectoryInfo dir)
{
Dictionary<String, FileDependency> list = new Dictionary<String, FileDependency>();
foreach (FileInfo info in dir.EnumerateFiles("*.h", SearchOption.AllDirectories))
{
String key = info.Name.ToLower();
if (list.ContainsKey(key) == false)
{
list.Add(key, new FileDependency(info.FullName));
}
else
{
Debug.Print("Duplicate key: {0}", info.Name);
Debug.Print(" File: {0}", info.FullName);
Debug.Print(" Have: {0}", list[key].FullFileName);
}
}
return list;
}
Which I tried turning into LINQ like so:
public Dictionary<String, FileDependency> GetSDKFilesLINQ(DirectoryInfo dir)
{
var files = from info in dir.EnumerateFiles("*.h", SearchOption.AllDirectories)
let key = info.Name.ToLower()
let dep = new FileDependency(info.FullName)
select new { key, dep };
return files.ToDictionary(v => v.key, v => v.dep);
}
However at runtime I get this:
An item with the same key has already been added.
In the foreach loop it was easy to avoid that, since I called the ContainsKey method to make sure I had no duplicates. But what is the LINQ equivalent?
Do I use where? - How?
Do I use group? - How?
Thanks.
var files = dir.EnumerateFiles("*.h", SearchOption.AllDirectories)
.GroupBy(file => file.Name.ToLower())
.Select(group => new {Key = group.Key, Value = group.First()})
.ToDictionary(a => a.Key, a => new FileDependency (a.Value.FullName));
If you have MoreLinq, you can do:
var files = dir.EnumerateFiles("*.h", SearchOption.AllDirectories)
.DistinctBy(file => file.Name.ToLower())
.ToDictionary(file => new FileDependency (a.Value.FullName));
Alternatively, you can write your own IEqualityComparer implementation for the files and use the standard Distinct method. The whole problem here is that Distinct (at least as of .NET 3.5) doesn't come with an overload that allows for inserting your own definition of "distinctness" as a lambda expression.
You could group by key and take the first value from the group for dep:
public Dictionary<String, FileDependency> GetSDKFilesLINQ(DirectoryInfo dir)
{
var files = from info in dir.EnumerateFiles(
"*.h", SearchOption.AllDirectories)
let key = info.Name.ToLower()
let dep = new FileDependency(info.FullName)
group dep by key into g
select new { key = g.Key, dep = g.First() };
return files.ToDictionary(v => v.key, v => v.dep);
}
That will silently ignore duplicates. Alternately, you could use a Lookup instead of a Dictionary:
public ILookup<String, FileDependency> GetSDKFilesLINQ2(DirectoryInfo dir)
{
var files = from info in dir.EnumerateFiles(
"*.h", SearchOption.AllDirectories)
let key = info.Name.ToLower()
let dep = new FileDependency(info.FullName)
select new { key, dep };
return files.ToLookup(v => v.key, v => v.dep);
}
The indexer on the lookup will return an IEnumerable<FileDependency>, so you can see all the values.
Related
I have a list of my class
List<Example> exampleList
Which already has all the data inside of it. I need to create a dictionary
Dictionary<string, List<Example>> exampleDictionary
The Key needs to be Example.Name and the value needs to Example
Here is my code below. The problem is Example.Name can be the same. I need to group by Name. I need to loop through my list and if the Name does not exist add new Key and Value otherwise add the Value to the Key. I know I am setting this up wrong but I can't seem to figure out the correct way of doing this.
foreach(var x in exampleList)
{
if(!exampleDictionary.ContainsKey(x.Name)
exampleDictionary.Add(x.Name, x)
else
exampleDictionary[x.Name] = x;
}
I know this code wouldn't build. I am not sure how to set this up.
You can use LookUp() extension method:
var lookup = exampleList.ToLookUp(e => e.Name);
This method returns a Lookup<string, Example>, a one-to-many dictionary that maps keys to collections of values.
But your code can be fixed grouping by Name and adding each group to exampleDictionary:
foreach (var g in exampleList.GroupBy(e => e.Name))
exampleDictionary.Add(g.Key, g.ToList());
Or
var exampleDictionary = exampleList.GroupBy(e => e.Name).ToDictionary(g => g.Key, g => g.ToList());
This should work
Dictionary<string, List<Example>> exampleDictionary = new Dictionary<string, List<Example>>();
foreach(var x in exampleList)
{
if(!exampleDictionary.ContainsKey(x.Name)) {
exampleDictionary[x.Name] = new List<Example>();
}
exampleDictionary[x.Name].Add(x);
}
You can also use ToDictionary extension method to achieve what you want:
Dictionary<string, List<Example>> exampleDictionary=exampleList.GroupBy(e => e.Name)
.ToDictionary(g => g.Key,g.ToList());
Basically the same as user469104 (+1)
List<Example> le = new List<Example>() { new Example("one"), new Example("one"), new Example("two") };
Dictionary<string, List<Example>> de = new Dictionary<string,List<Example>>();
foreach (Example e in le)
{
if (de.ContainsKey(e.Name))
de[e.Name].Add(e);
else
de.Add(e.Name, new List<Example>() { e });
}
I have below code in c# 4.0.
//Dictionary object with Key as string and Value as List of Component type object
Dictionary<String, List<Component>> dic = new Dictionary<String, List<Component>>();
//Here I am trying to do the loping for List<Component>
foreach (List<Component> lstComp in dic.Values.ToList())
{
// Below I am trying to get first component from the lstComp object.
// Can we achieve same thing using LINQ?
// Which one will give more performance as well as good object handling?
Component depCountry = lstComp[0].ComponentValue("Dep");
}
Try:
var firstElement = lstComp.First();
You can also use FirstOrDefault() just in case lstComp does not contain any items.
http://msdn.microsoft.com/en-gb/library/bb340482(v=vs.100).aspx
Edit:
To get the Component Value:
var firstElement = lstComp.First().ComponentValue("Dep");
This would assume there is an element in lstComp. An alternative and safer way would be...
var firstOrDefault = lstComp.FirstOrDefault();
if (firstOrDefault != null)
{
var firstComponentValue = firstOrDefault.ComponentValue("Dep");
}
[0] or .First() will give you the same performance whatever happens.
But your Dictionary could contains IEnumerable<Component> instead of List<Component>, and then you cant use the [] operator. That is where the difference is huge.
So for your example, it doesn't really matters, but for this code, you have no choice to use First():
var dic = new Dictionary<String, IEnumerable<Component>>();
foreach (var components in dic.Values)
{
// you can't use [0] because components is an IEnumerable<Component>
var firstComponent = components.First(); // be aware that it will throw an exception if components is empty.
var depCountry = firstComponent.ComponentValue("Dep");
}
You also can use this:
var firstOrDefault = lstComp.FirstOrDefault();
if(firstOrDefault != null)
{
//doSmth
}
for the linq expression you can use like this :
List<int> list = new List<int>() {1,2,3 };
var result = (from l in list
select l).FirstOrDefault();
for the lambda expression you can use like this
List list = new List() { 1, 2, 3 };
int x = list.FirstOrDefault();
You can do
Component depCountry = lstComp
.Select(x => x.ComponentValue("Dep"))
.FirstOrDefault();
Alternatively if you are wanting this for the entire dictionary of values, you can even tie it back to the key
var newDictionary = dic.Select(x => new
{
Key = x.Key,
Value = x.Value.Select( y =>
{
depCountry = y.ComponentValue("Dep")
}).FirstOrDefault()
}
.Where(x => x.Value != null)
.ToDictionary(x => x.Key, x => x.Value());
This will give you a new dictionary. You can access the values
var myTest = newDictionary[key1].depCountry
Try this to get all the list at first, then your desired element (say the First in your case):
var desiredElementCompoundValueList = new List<YourType>();
dic.Values.ToList().ForEach( elem =>
{
desiredElementCompoundValue.Add(elem.ComponentValue("Dep"));
});
var x = desiredElementCompoundValueList.FirstOrDefault();
To get directly the first element value without a lot of foreach iteration and variable assignment:
var desiredCompoundValue = dic.Values.ToList().Select( elem => elem.CompoundValue("Dep")).FirstOrDefault();
See the difference between the two approaches: in the first one you get the list through a ForEach, then your element. In the second you can get your value in a straight way.
Same result, different computation ;)
There are a bunch of such methods:
.First .FirstOrDefault .Single .SingleOrDefault
Choose which suits you best.
var firstObjectsOfValues = (from d in dic select d.Value[0].ComponentValue("Dep"));
I would to it like this:
//Dictionary object with Key as string and Value as List of Component type object
Dictionary<String, List<Component>> dic = new Dictionary<String, List<Component>>();
//from each element of the dictionary select first component if any
IEnumerable<Component> components = dic.Where(kvp => kvp.Value.Any()).Select(kvp => (kvp.Value.First() as Component).ComponentValue("Dep"));
but only if it is sure that list contains only objects of Component class or children
I have a list of details about a large number of files. This list contains the file ID, last modified date and the file path. The problem is there are duplicates of the files which are older versions and sometimes have different file paths. I want to only store the newest version of a file regardless of file path. So I created a loop that iterates through the ordered list, checks to see if the ID is unique and if it is, it gets stored in a new unique list.
var ordered = list.OrderBy(x => x.ID).ThenByDescending(x => x.LastModifiedDate);
List<Item> unique = new List<Item>();
string curAssetId = null;
foreach (Item result in ordered)
{
if (!result.ID.Equals(curAssetId))
{
unique.Add(result);
curAssetId = result.ID;
}
}
However this is still allowing duplicates into the DB and I can't figure out why this code isn't working as expected. By duplicates I mean, the files have the same ID but different file paths, which like I said before shouldn't be an issue. I just want the latest version regardless of pathway. Can anyone else see what the issue is? Thanks
var ordered = listOfItems.OrderBy(x => x.AssetID).ThenByDescending(x => x.LastModifiedDate);
List<Item> uniqueItems = new List<Item>();
foreach (Item result in ordered)
{
if (!uniqueItems.Any(x => x.AssetID.Equals(result.AssetID)))
{
uniqueItems.Add(result);
}
}
this is what I have now and it is still allowing duplicates
This is because , you are not searching entire list to check whether the id is unique or not
List<Item> unique = new List<Item>();
string curAssetId = null; // here is the problem
foreach (Item result in ordered)
{
if (!result.ID.Equals(curAssetId)) // here you only compare the last value.
{
unique.Add(result);
curAssetId = result.ID; // You are only assign the current ID value and
}
}
to solve this , change the following
if (!result.ID.Equals(curAssetId)) // here you only compare the last value.
{
unique.Add(result);
curAssetId = result.ID; // You are only assign the current ID value and
}
to
if (!unique.Any(x=>x.ID.Equals(result.ID)))
{
unique.Add(result);
}
I don't know if this code is just simplified, but have you considered grouping on ID, sorting on LastModifiedDate, then just taking the first from each group?
Something like:
var unique = list.GroupBy(i => i.ID).Select(x => x.OrderByDescending(y => y.LastModifiedDate).First());
var ordered = list.OrderBy(x => x.ID).ThenByDescending(x => x.LastModifiedDate).Distinct() ??
For this purpose you have to create your own EquityComparer and after that you could use linq's Distinct method. Enumerable.Distinct at msdn
Also I think you could stay with your current code but you have to modify it in such a way (as a sample):
var ordered = list.OrderByDescending(x => x.LastModifiedDate);
var unique = new List<Item>();
foreach (Item result in ordered)
{
if (unique.Any(x => x.ID == result.ID))
continue;
unique.Add(result);
}
List<Item> p = new List<Item>();
var x = p.Select(c => new Item
{
AssetID = c.AssetID,
LastModifiedDate = c.LastModifiedDate.Date
}).OrderBy(y => y.id).ThenByDescending(c => c.LastModifiedDate).Distinct();
Dictionary<int, string> lstSrc = new Dictionary<int, string>();
Dictionary<int, string> lstDest = new Dictionary<int, string>();
lstSrc.Add(1, "All");
lstSrc.Add(2, "Weekday");
lstSrc.Add(3, "WeekEnd");
lstDest.Add(1, "All");
lstDest.Add(2, "X1");
lstDest.Add(3, "X2");
lstDest.Add(4, "Weekday");
lstDest.Add(5, "WeekEnd");
Compare only when name matches in Source and Destination
var matchingItems = lstDest
.Where(l2 => lstSrc.Any(l1 => l1.Value.Equals(l2.Value))).ToList();
matchingItems.AddRange(lstDest.Except(matchingItems));
This query gives result as see in attached image how to get that result without using LINQ ?
How i can achieve this ?
[1]: http://i.stack.imgur.com/FLicZ.png
To get the matching items you could use a query like this:
var matchingItems = List2
.Where(l2 => List1.Any(l1 => l1.TimeGroupName.Equals(l2.TimeGroupName));
matchingItems.AddRange(List2.Except(matchingItems)));
Edited: equivalent without using Linq: (It's easy to forget how much boiler plate Linq saves you from writing!)
// Get the matching items
List<TIMEGROUPINFO> matchingItems = new List<TIMEGROUPINFO>();
foreach (TIMEGROUPINFO l1 in List1)
{
foreach (TIMEGROUPINFO l2 in List2)
{
if (l1.TimeGroupName.Equals(l2.TimeGroupName))
{
matchingItems.Add(l1);
continue;
}
}
}
// Append the items from List2 which aren't already in the list:
foreach (TIMEGROUPINFO l2 in List2)
{
bool exists = false;
foreach (TIMEGROUPINFO match in matchingItems)
{
if (match.TimeGroupName.Equals(l2.TimeGroupName))
{
// This item is already in the list.
exists = true;
break;
}
}
if (exists = false)
matchingItems.Add(l2);
}
I understand that you want to perform a query on list 2 based on list 1. Linq is very good for that.
so, if you wrote something like
//List1Element is a single element in the first list.
List1Element = List1[i];
List2.Where(l2 => l2.TimeGroupName == List1Element.TimeGroupName).ToList();
That might accomplish what I think you're trying to accomplish.
If you're trying to match the entire List1 at once, you can either iterate through all the list1 elements, or you can look into Linq Join operations
I have a Dictionary<string, string>.
I need to look within that dictionary to see if a value exists based on input from somewhere else and if it exists remove it.
ContainsValue just says true/false and not the index or key of that item.
Help!
Thanks
EDIT: Just found this - what do you think?
var key = (from k in dic where string.Compare(k.Value, "two", true) ==
0 select k.Key).FirstOrDefault();
EDIT 2: I also just knocked this up which might work
foreach (KeyValuePair<string, string> kvp in myDic)
{
if (myList.Any(x => x.Id == kvp.Value))
myDic.Remove(kvp.Key);
}
Are you trying to remove a single value or all matching values?
If you are trying to remove a single value, how do you define the value you wish to remove?
The reason you don't get a key back when querying on values is because the dictionary could contain multiple keys paired with the specified value.
If you wish to remove all matching instances of the same value, you can do this:
foreach(var item in dic.Where(kvp => kvp.Value == value).ToList())
{
dic.Remove(item.Key);
}
And if you wish to remove the first matching instance, you can query to find the first item and just remove that:
var item = dic.First(kvp => kvp.Value == value);
dic.Remove(item.Key);
Note: The ToList() call is necessary to copy the values to a new collection. If the call is not made, the loop will be modifying the collection it is iterating over, causing an exception to be thrown on the next attempt to iterate after the first value is removed.
Dictionary<string, string> source
//
//functional programming - do not modify state - only create new state
Dictionary<string, string> result = source
.Where(kvp => string.Compare(kvp.Value, "two", true) != 0)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
//
// or you could modify state
List<string> keys = source
.Where(kvp => string.Compare(kvp.Value, "two", true) == 0)
.Select(kvp => kvp.Key)
.ToList();
foreach(string theKey in keys)
{
source.Remove(theKey);
}
Loop through the dictionary to find the index and then remove it.
Here is a method you can use:
public static void RemoveAllByValue<K, V>(this Dictionary<K, V> dictionary, V value)
{
foreach (var key in dictionary.Where(
kvp => EqualityComparer<V>.Default.Equals(kvp.Value, value)).
Select(x => x.Key).ToArray())
dictionary.Remove(key);
}
You can use the following as extension method
public static void RemoveByValue<T,T1>(this Dictionary<T,T1> src , T1 Value)
{
foreach (var item in src.Where(kvp => kvp.Value.Equals( Value)).ToList())
{
src.Remove(item.Key);
}
}
In my case I use this
var key=dict.FirstOrDefault(m => m.Value == s).Key;
dict.Remove(key);