I have two string lists which have same size.
I want to create a dictionary, the key is from listA, the value is from listB.
What is the fast way?
I used the code:
List<string> ListA;
List<string> ListB;
Dictionary<string,string> dict = new Dictionary<string,string>();
for(int i=0;i<ListA.Count;i++)
{
dict[key] = listA[i];
dict[value]= listB[i];
}
I don't like this way, can I use ToDictionary method?
Starting with .NET 4.0, you can do it using LINQ's Zip method, like this:
var res = ListA.Zip(ListB, (a,b) => new {a, b})
.ToDictionary(p=>p.a, p=>p.b);
[Zip] method merges each element of the first sequence with an element that has the same index in the second sequence.
You could create an anonymous type with the index which you can use to get the B at this index.
Dictionary<string, string> dict = ListA
.Select((a, i) => new { A = a, Index = i })
.ToDictionary(x => x.A, x => ListB.ElementAtOrDefault(x.Index));
Note that the value would be null in case ListB is smaller than ListA.
I would not bother (if it is possible) as your version is readable, easy to debug and quicker than any other LINQ solutions (especially if you are working with big list).
I wouldn't change your version.
The following piece of code is more readable than LINQ stuff in your case, IMHO.
var ListA = new List<string>();
var ListB = new List<string>();
var dict = new Dictionary<string, string>();
for (int i = 0; i < ListA.Count; i++)
{
dict.Add(ListA[i], ListB[i]);
}
Related
List<string> equipment = new List<string>();
equipment.Add("Football");
equipment.Add("Golf Ball");
equipment.Add("Baseball");
List<string> myEquipment = new List<string>();
myEquipment.Add("Football");
myEquipment.Add("Golf Ball");
myEquipment.Add("Basketball");
If i have the above, how would i check if there is an item in myEquipment that isn't in equipment?
You can use Linq to do this pretty easily
myEquipment.Any(c => !equipment.Contains(c));
If you need the items you could do
var notInEquipment = myEquipment.Where(c => !equipment.Contains(c));
The simplest solution is to use Except method
var diff = myEquipment.Except(equipment);
Another solution is to use HashSet and ExceptWith. It has almost the same complexity with previous solution (both O(n)), but HashSet will skip the duplicated values
var set = new HashSet<string>(equipment);
var mySet = new HashSet<string>(myEquipment);
mySet.ExceptWith(set);
Contains method also work, but has some performance overhead
var item = equipment.FirstOrDefault(e => !myEquipment.Contains(e));
You'll get the first item, which isn't present in a second list, or null value
You can use the Linq Except method:
void Main()
{
List<string> equipment = new List<string>();
equipment.Add("Football");
equipment.Add("Golf Ball");
equipment.Add("Baseball");
List<string> myEquipment = new List<string>();
myEquipment.Add("Football");
myEquipment.Add("Golf Ball");
myEquipment.Add("Basketball");
var missingEquipment = myEquipment.Except(equipment);
}
Here's two way look up to get items that don't exist in other from both lists:
var b = equipment.Concat(myEquipment).ToLookup(x => x)
.Select(x => new { x.Key, Count = x.Count()})\
.Where(x => x.Count == 1);
I have a dictionary which has an integer Key that represents a year, and a Value which is a list of object Channel. I need to flatten the data and create a new object from it.
Currently, my code looks like this:
Dictionary<int, List<Channel>> myDictionary;
foreach(var x in myDictionary)
{
var result = (from a in x.Value
from b in anotherList
where a.ChannelId == b.ChannelId
select new NewObject
{
NewObjectYear = x.Key,
NewObjectName = a.First().ChannelName,
}).ToList();
list.AddRange(result);
}
Notice that I am using the Key to be the value of property NewObjectYear.
I want to get rid of foreach since the dictionary contains a lot of data and doing some joins inside the iteration makes it very slow. So I decided to refactor and came up with this:
var flatten = myDictionary.SelectMany(x => x.Value.Select(y =>
new KeyValuePair<int, Channel>(x.Key, y))).ToList();
But with this, I couldn't get the Key directly. Using something like flatten.Select(x => x.Key) is definitely not the correct way. So I tried finding other ways to flatten that would be favorable for my scenario but failed. I also thought about creating a class which will contain the year and the list from the flattened but I don't know how.
Please help me with this.
Also, is there also another way that doesn't have the need to create a new class?
It seems to me you are trying to do only filtering, you do not need join for that:
var anotherListIDs = new HashSet<int>(anotherList.Select(c => c.ChannelId));
foreach (var x in myDictionary)
{
list.AddRange(x.Value
.Where(c => anotherListIDs.Contains(c.ChannelId))
.Select(c => new NewObject
{
NewObjectYear = x.Key,
NewObjectName = c.First().ChannelName,
}));
}
You do realise, that if the second element of the list in a specific dictionary element has a matching channelId, that you return the first element of this list, don't you?
var otherList = new OtherItem[]
{
new OtherItem() {ChannelId = 1, ...}
}
var dictionary = new Dictionary<int, List<Channel>[]
{
{ 10, // Key
new List<Channel>() // Value
{
new Channel() {ChannelId = 100, Name = "100"},
new Channel() {ChannelId = 1, Name = "1"},
},
};
Although the 2nd element has a matching ChannelId, you return the Name of the first element.
Anyway, let's assume this is what you really want. You are right, your function isn't very efficient.
Your dictionary implements IEnumerable<KeyValuePair<int, List<Channel>>. Therefore every x in your foreach is a KeyValuePair<int, List<Channel>. Every x.Value is a List<Channel>.
So for every element in your dictionary (which is a KeyValuePair<int, List<Channel>), you take the complete list, and perform a full inner join of the complete list with otherList, and for the result you take the key of the KeyValuePair and the first element of the List in the KeyValuePair.
And even though you might not use the complete result, but only the first or the first few, because of FirstOrDefault(), or Take(3), you do this for every element of every list in your Dictionary.
Indeed your query could be much more efficient.
As you use the ChannelIds in your OtherList only to find out if it is present, one of the major improvements would be to convert the ChannelIds of OtherList to a HashSet<int> where you have superior fast lookup to check if the ChannelId of one of the values in your Dictionary is in the HashSet.
So for every element in your dictionary, you only have to check every ChannelId in the list to see if one of them is in the HashSet. As soon as you've found one, you can stop and return only the first element of the List and the Key.
My solution is an extension function of Dictionary>. See Extension Methods Demystified
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<OtherItem> otherList)
{
// I'll only use the ChannelIds of the otherList, so extract them
IEnumerable<int> otherChannelIds = otherList
.Select(otherItem => otherItem.ChannelId);
return dictionary.ExtractNewObjects(otherChannelIds);
}
This calls the other ExtractNewobjects:
public static IEnumerable<NewObject> ExtractNewObjects(this Dictionary<int, List<Channel>> dictionary,
IEnumerable<int> otherChannelIds)
{
var channelIdsSet = new HashSet<int>(otherChannelIds));
// duplicate channelIds will be removed automatically
foreach (KeyValuePair<int, List<Channel>> keyValuePair in dictionary)
{
// is any ChannelId in the list also in otherChannelIdsSet?
// every keyValuePair.Value is a List<Channel>
// every Channel has a ChannelId
// channelId found if any of these ChannelIds in in the HashSet
bool channelIdFound = keyValuePair.Value
.Any(channel => otherChannelIdsSet.Contains(channel.ChannelId);
if (channelIdFound)
{
yield return new NewObject()
{
NewObjectYear = keyValuePair.Key,
NewObjectName = keyValuePair.Value
.Select(channel => channel.ChannelName)
.FirstOrDefault(),
};
}
}
}
usage:
IEnumerable<OtherItem> otherList = ...
Dictionary<int, List<Channel>> dictionary = ...
IEnumerable<Newobject> extractedNewObjects = dictionary.ExtractNewObjects(otherList);
var someNewObjects = extractedNewObjects
.Take(5) // here we see the benefit from the yield return
.ToList();
We can see four efficiency improvements:
the use of HashSet<int> enables a very fast lookup to see if the ChannelId is in OtherList
the use of Any() stops enumerating the List<Channel> as soon as we've found a matching Channelid in the HashSet
the use of yield return makes that you don't enumerate over more elements in your Dictionary than you'll actually use.
The use of Select and FirstOrDefault when creating NewObjectName prevents exceptions if List<Channel> is empty
I'm trying to order a C# Dictionary<int, int> by its value without using LINQ's OrderBy as it's not supported on iPhones.
I can't seem to figure it out, so your help would be much appreciated!
There are many possible ways of doing this. All of the following assume myDictionary is the original dictionary to be sorted.
① Create a list and then sort the list
var myList = myDictionary.ToList();
myList.Sort((a, b) => a.Value.CompareTo(b.Value));
② Create an array and then sort the array
var myArray = myDictionary.ToArray();
Array.Sort(myArray, (a, b) => a.Value.CompareTo(b.Value));
③ Create a new SortedDictionary that has keys and values swapped
This solution is appropriate only if you know that every value occurs only once.
var mySortedDict = new SortedDictionary<int, int>();
foreach (var kvp in myDictionary)
mySortedDict[kvp.Value] = kvp.Key;
④ Create a new SortedDictionary and use lists for values
This solution is appropriate only if values can occur more than once.
var mySortedDict = new SortedDictionary<int, List<int>>();
foreach (var kvp in myDictionary)
{
if (!mySortedDict.ContainsKey(kvp.Value))
mySortedDict[kvp.Value] = new List<int>();
mySortedDict[kvp.Value].Add(kvp.Key);
}
We can generate a List of KeyValuePair and then sort it using Sort,
Dictionary<int, int> myList = new Dictionary<int, int>();
List<KeyValuePair<int, int>> mySortedList = myList.ToList();
mySortedList.Sort(( firstValue, nextValue) =>
{
return firstValue.Value.CompareTo(nextValue.Value);
}
);
Dictionary<int, int> mySortedDict = mySortedList.ToDictionary(keyItem => keyItem.Key, keyItem => keyItem.Value);
I think Sort will be supported on iPhones
List<List<String>> ls = new List<List<String>>();
List<String> l1 = new List<String>();
l1.Add("Peter");
l1.Add("123");
ls.Add(l1);
List<String> l2 = new List<String>();
l2.Add("Peter");
l2.Add("123");
ls.Add(l2);
ls = ls.Distinct().ToList();
I suppose there are only one element in ls, but actually there are still 2 elements. What are the possible reasons?
That's because List<T> has no Equals and GetHashCode implemented, so standard reference comparison is being performed. And it returns false, because you have two separated lists.
You can write your own IEqualityComparer<List<string>> implementation and provide it as Distinct method parameter. Within the comparer you can use Enumerable.SequenceEqual) method to check if lists has the same content.
With your case, you have to build the custom comparer to implement the interface IEqualityComparer<List<string>>, and use SequenceEqual to compare in Equal method:
public class CustomComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(List<string> obj)
{
int hashCode = 0;
foreach (string str in obj)
{
hashCode ^= str.GetHashCode();
}
return hashCode;
}
}
Then:
ls = ls.Distinct(new CustomComparer()).ToList();
Another tricky way to distinct by using GroupBy:
ls = ls.GroupBy(x => string.Join("", x))
.Select(g => g.First())
.ToList();
The Comparison used by List is based on reference comparison. Since the 2 lists are different instances, they are not the same and distinct considers them to be different.
If you want the distinct values, you can use .SelectMany() to select the strings in each list within the parent list:
var list = new List<List<String>>();
var list1 = new List<String>();
list1.Add("Peter");
list1.Add("123");
list.Add(list1);
var list2 = new List<String>();
list2.Add("Peter");
list2.Add("123");
list.Add(list2);
var distinct = list.SelectMany(x => x).Distinct().ToList();
distinct.ForEach(x => Console.WriteLine(x));
I'm borrowing code from this question as I went there for inspiration. I have a list of objects, the object has an integer property and I want to foreach the list and the loop the number of integers.
It's a very basic for inside a foreach but I suspect I could use a SelectMany but can't get it working. The following code works but I would like a linq version.
//set up some data for our example
var tuple1 = new { Name = "Tuple1", Count = 2 };
var tuple2 = new { Name = "Tuple2", Count = 3 };
//put the tuples into a collection
var tuples = new [] { tuple1, tuple2 };
foreach(var item in tuples)
{
for(int i = 0; i < item.Count; i++)
Console.WriteLine(item.Name);
}
var flattened = tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count));
foreach(var word in flattened)
{
Console.WriteLine(word);
}
You can use SelectMany; you simply need to generate sequences:
tuples.SelectMany(t => Enumerable.Repeat(t.Name, t.Count))
There is no Values property in your anonymous type. But i assume that you mean the Count property instead and you want to repeat the name this number. You can either use Enumerable.Range or Enumerable.Repeat:
IEnumerable<String> tupleNames = tuples
.Select(t => string.Join(Environment.NewLine, Enumerable.Repeat(t.Name, t.Count)));
Console.Write(string.Join(Environment.NewLine, tupleNames));
Output:
Tuple1
Tuple1
Tuple2
Tuple2
Tuple2
There is no linq equivalent of a foreach. You should use an actual foreach to iterate an IEnumerable and perform an action on each item.