How can I do a "like" to find a dictionary key? I'm currently doing:
mydict.ContainsKey(keyName);
But some keyNames have an additional word appended (separated by a space), I'd like to do a "like" or .StartsWith(). The comparisons will look like this:
"key1" == "key1" //match
"key1" == "key1 someword" //partial match
I need to match in both cases.
You can use LINQ to do this.
Here are two examples:
bool anyStartsWith = mydict.Keys.Any(k => k.StartsWith("key1"))
bool anyContains = mydict.Keys.Any(k => k.Contains("key1"))
It is worth pointing out that this method will have worse performance than the .ContainsKey method, but depending on your needs, the performance hit will not be noticable.
mydict.Keys.Any(k => k.StartsWith("key1"));
While enumerating over the Keys you will lose the performance benefits of a dictionary:
mydict.ContainsKey(someKey); // O(1)
mydict.Keys.Any(k => k.StartsWith("key1")); // O(n)
If you run the .Contains() method of the string and not the dictionary you'll get what you want.
var matchingKeys = mydict.Keys.Where(x => x.Contains("key1"));
var keys = (from key mydict.keys where key.contains(keyName) select key).ToList();
The Keys member of the dictionary can be manipulated to check for presence with more complicated semantics than equality. It need not be a LINQ extension method check, but that is likely the simplest.
If you want the keys themselves and not a true/false you can do:
string match = "key1";
var matches = from k in mydict
where k.Key.Contains(match)
select new
{
k.Key
};
For matching and retrieving keys "like" you could use these extensions.
public static class Extensions
{
public static bool HasKeyLike<T>(this Dictionary<string, T> collection, string value)
{
var keysLikeCount = collection.Keys.Count(x => x.ToLower().Contains(value.ToLower()));
return keysLikeCount > 0;
}
public static List<string> GetKeysLike<T>(this Dictionary<string, T> collection, string value)
{
return collection.Keys.Select(x => x.ToLower().Contains(value.ToLower())).ToList();
}
}
Related
This question already has answers here:
Can I split an IEnumerable into two by a boolean criteria without two queries?
(6 answers)
Closed last year.
Say I have a List<string> listOfStrings and I want to divide this list into two lists based on some predicate. E.g., the first list should contain all strings that start with a letter, and second is a list of strings that don't.
Now I would do this like this:
var firstList = listOfStrings.Where(str => predicate(str));
var secondList = listOfStrings.Where(str => !predicate(str));
Is there a better way of doing this in one line?
You can use Linq's GroupBy():
var splitted = listOfStrings.GroupBy(s => Char.IsLetter(s[0]));
And with your predicate, it would be:
Func<string, bool> predicate;
var splitted = listOfStrings.GroupBy(predicate);
Usage:
The easiest way would be to convert the grouped data into a Dictionary<bool, IEnumerable<string>>, when the key is a bool that denotes whether the items in it start with a letter:
var splitted = list.GroupBy(x => Char.IsLetter(x[0]))
.ToDictionary(x => x.Key, z => z.ToArray());
var startWithLetter = splitted[true];
var dontStartWithLetter = splitted[false];
Of course, there are many ways to massage the data into your desired structure, but the above is pretty concise in my opinion.
See MSDN
Kotlin has partition function (sources). C# version:
var (first, second) = list.Partition(x => x.IsTrue);
Extension:
public static (IEnumerable<T> first, IEnumerable<T> second) Partition<T>(this IEnumerable<T> list, Func<T, bool> predicate)
{
var lookup = list.ToLookup(predicate);
return (lookup[true], lookup[false]);
}
Could be more convenient to return List<T>, or to use GroupBy or something else, depending on use case.
You can use 'GroupBy' or 'ToLookup', based on what you will be doing with the results.
Check also lookup vs. groupby
I would do something like this:
class Program
{
static void Main(string[] args)
{
Func<string, bool> startsWithA = s => s[0] == 'a';
List<string> listOfStrings = new List<string>()
{
"abc",
"acb",
"bac",
"bca",
"cab",
"cba"
};
Dictionary<bool, List<string>> dictionaryOfListsOfStrings = listOfStrings.GroupBy(startsWithA).ToDictionary(x => x.Key, x => x.ToList());
}
}
I have a list of { string Key, IEnumerable<string> Values } items and want to transform it into a list of { string Key, string Value } items, such that the new list contains n items for each Key.
I want to use LINQ and had the idea to use SelectMany:
list.SelectMany(item => item.Values)
However, as you can see, I lose the Key with that transformation. What's the trick? I guess it should be easy and I am just missing the forest for the trees...
I also ran into this brain freeze. Adding the answer by #PetSerAl from the comments:
list.SelectMany(item => item.Values.DefaultIfEmpty(), (item, value) => Tuple.Create(item.Key, value))
Not the compiled code but this may answer your question
var finalList=list.SelectMany(item => item).Select(final => new{KeyName=item.Key,Coll=item.Values}).ToList();
In C#, I have an object type 'A' that contains a list of key value pairs.
The key value pairs is a category string and a value string.
To instantiate object type A, I would have to do the following:
List<KeyValuePair> keyValuePairs = new List<KeyValuePair>();
keyValuePairs.Add(new KeyValuePair<"Country", "U.S.A">());
keyValuePairs.Add(new KeyValuePair<"Name", "Mo">());
keyValuePairs.Add(new KeyValuePair<"Age", "33">());
A a = new A(keyValuePairs);
Eventually, I will have a List of A object types and I want to manipulate the list so that i only get unique values and I base it only on the country name. Therefore, I want the list to be reduced to only have ONE "Country", "U.S.A", even if it appears more than once.
I was looking into the linq Distinct, but it does not do what I want because it I can't define any parameters and because it doesn't seem to be able to catch two equivalent objects of type A. I know that I can override the "Equals" method, but it still doesn't solve the my problem, which is to render the list distinct based on ONE of the key value pairs.
To expand upon Karl Anderson's suggestion of using morelinq, if you're unable to (or don't want to) link to another dll for your project, I implemented this myself awhile ago:
public static IEnumerable<T> DistinctBy<T, U>(this IEnumerable<T> source, Func<T, U>selector)
{
var contained = new Dictionary<U, bool>();
foreach (var elem in source)
{
U selected = selector(elem);
bool has;
if (!contained.TryGetValue(selected, out has))
{
contained[selected] = true;
yield return elem;
}
}
}
Used as follows:
collection.DistinctBy(elem => elem.Property);
In versions of .NET that support it, you can use a HashSet<T> instead of a Dictionary<T, Bool>, since we don't really care what the value is so much as that it has already been hashed.
Check out the DistinctBy syntax in the morelinq project.
A a = new A(keyValuePairs);
a = a.DistinctBy(k => new { k.Key, k.Value }).ToList();
You need to select the distinct property first:
Because it's a list inside a list, you can use the SelectMany. The SelectMany will concat the results of subselections.
List<A> listOfA = new List<A>();
listOfA.SelectMany(a => a.KeyValuePairs
.Where(keyValue => keyValue.Key == "Country")
.Select(keyValue => keyValue.Value))
.Distinct();
This should be it. It will select all values where the key is "Country" and concat the lists. Final it will distinct the country's. Given that the property KeyValuePairs of the class A is at least a IEnumerable< KeyValuePair< string, string>>
var result = keyValuePairs.GroupBy(x => x.Key)
.SelectMany(g => g.Key == "Country" ? g.Distinct() : g);
You can use the groupby statement. From here you can do all kind off cool stuf
listOfA.GroupBy(i=>i.Value)
You can groupby the value and then sum all the keys or something other usefull
I have these dictionaries:
public class CiscoVSAN
{
private string _VSANName;
private string _VSANNum;
public string VSANName{ get{return _VSANName;} set{_VSANName=value;} }
public string VSANNum{ get{return _VSANNum;} set{_VSANNum=value;} }
public Dictionary<int, CiscoSwitch> MemberSwitches = new Dictionary<int, CiscoSwitch>();
}
public Dictionary<int, CiscoVSAN> VSANList = new Dictionary<int, CiscoVSAN>();
I am trying to replace this foreach
foreach (KeyValuePair<int, CiscoVSAN> vsanpair in this.VSANList)
{
var currSwitchAsEnumerable = vsanpair.Value.MemberSwitches.Where(cs => cs.Value.switchName == RemoteSwitchName);
if (currSwitchAsEnumerable != null)
{
//currVSAN.MemberSwitches.Add(DomainID, currSwitchAsEnumerable.FirstOrDefault().Value);
currSwitch = currSwitchAsEnumerable.FirstOrDefault().Value;
break;
}
}
with a SelectMany on the outer Dictionary. I want the first match in MemberSwitches that matches the condition. Not all VSANs have the same member switches in their respective dictionary. I have tried this:
var selectmany = this.VSANList.SelectMany(cs => cs.Value).Where( => cs).Where(cs.Value.SwitchName == RemoteSwitchName).First();
and this:
var selectmany = this.VSANList.Values.SelectMany(cs => cs.Value).Where( => cs).Where(cs.Value.SwitchName == RemoteSwitchName).First();
But each time the compiler tells me it cannot infer the type arguments from usage. I also tried feeding it a type argument with after the SelectMany statement but that didn't work either. Most of the examples I looked at either flattened lists of lists or simple dictionaries. They also didn't specify any type on the SelectMany.
Edited to add I tried this:
Dictionary<int, List<string>> mydict = new Dictionary<int, List<string>>();
var selectlist = mydict.Values.SelectMany(n => n).ToList();
and did not get the compiler error about inferring type. So then I tried this, just to flatten the dictionary and turn it to a list:
var selectmany = this.VSANList.Values.SelectMany(vs => vs).ToList();
and I get the compiler warning again. I am not sure what type to specify or how to specify it.
Assuming there is no typo, you have a syntax error: the part .Where ( => cs ) is illegal. What did you try to express here?
EDIT:
Actually, to think about it, your entire expression is illegal or mistyped.
The delegate that you pass to SelectMany should return IEnumerable<T>, but your expression returns CiscoVSAN
I figured it out:
var selectmany = this.VSANList.Values.SelectMany(vs => vs.MemberSwitches).Where(cs => cs.Value.switchName == RemoteSwitchName).First();
The two lines below return an IGrouping<string, DataRow>:
var mbVals = GetMBValues_MBvsPU(mbRptDataPkg);
var puVals = GetPUValues_MBvsPU(puRptDataPkg);
I'd though that you could access the grouping's data like this mbVals[StringKey] but that doesn't look possible. I can do a foreach over the DataRows but it just seems to me one should be able to easily access through a linq expression somehow.
What I'd like to do is compare fields in the datarows from one with fields from datarows of the other through the keys.
Any thoughts?
Thanks!
IGrouping<> is IEnumerable<> so you use the ElementAt() extension method.
But for the situation you describe, you may be better off using Zip() to unify the two groups (if the items are in the same order and match exactly) or using Join() if they don't.
An instance that implements IGrouping<T, U> has a (one) key of type T. Since you want to compare based on keys (plural), an IGrouping<string, DataRow> isn't what you need.
You need an IEnumerable<IGrouping<string, DataRow>> or an ILookup<string, DataRow>. Something that has many keys.
ILookup<string, DataRow> source1 = GetSource1();
ILookup<string, DataRow> source2 = GetSource2();
var BothKeyed =
(
from key in source1.Select(g => g.Key).Union(source2.Select(g => g.Key))
select new
{
Key = key,
In1 = source1[key],//note, In1 may be empty.
In2 = source2[key] //note, In2 may be empty.
}
).ToLookup(x => x.Key);