Searching for a Key in Multivalue Dictionaries in C# - c#

I want to set up a lookup table like so:
Key Value
----- ------------------
Cat Lion, Tiger, Cheetah
Fish Dolphin, Whale
Dog . Pitbull, Doberman
An input of "Lion" would return the key "Cat"
I have set up 3 possible ways to initialize the data:
A Dictionary:
var map = new Dictionary<string,string>
{
["Dolphin"] = "Fish",
["Lion"] = "Cat",
//....
};
A HashSet:
var data = new Dictionary<string, HashSet<string>>
{
{"cat", new HashSet<string> {"Lion", "Tiger", "Cheetah"}},
{"fish", new HashSet<string> {"Dolphin", "Whale"}},
{"Dog", new HashSet<string> {"Pitbull", "Doberman"}}
};
A tuple:
var data = new List<Tuple<string, List<string>>>
{
Tuple.Create<string,List<string>> ("Cat", new List<string> { "Cheetah", "Lion" }),
Tuple.Create<string,List<string>> ("Dog", new List<string> { "Doberman", "Pitbull" }),
Tuple.Create<string,List<string>> ("Fish", new List<string> { "Dolphin", "Whale" }),
};
Given an animal, I want to return its type.
I know for the dictionary I can call the ContainsKey method.
Is there something similar for the other two options?
The data set isn't that big, (~15 keys that have 10 or so values), so I'm also wondering if one option would be better than the other in terms of performance.

I am suggesting a bit different approach.
public abstract class Animal
{
public string Type { get; }
public string Name { get; }
protected Animal(string type, string name)
{
Type = type;
Name = name;
}
}
public class Cat : Animal
{
public Cat(string name) : base("Cat", name)
{
}
}
public class Fish : Animal
{
public Fish(string name) : base("Fish", name)
{
}
}
public static class Program
{
public static void Main(string[] args)
{
List<Animal> list = new List<Animal>();
list.Add(new Cat("Cheetah"));
list.Add(new Fish("Dolphin"));
var cheetahType = list.FirstOrDefault(animal => animal.Name == "Cheetah")?.Type;
var doplhinType = list.FirstOrDefault(animal => animal.Name == "Dolphin")?.Type;
}
}
If you don't actually need that much, you can make Animal nonabstract and define enum instead of string Type and remove derived children.

Use the Dictionary<string, string> option.
var animalToType = new Dictionary<string,string>
{
["Dolphin"] = "Fish",
["Lion"] = "Cat",
//....
};
var lionType = animalToType["Lion"];
The other options are not going to be as simple. They will all involve loops, whether directly or hidden by Linq calls.

Personally, this architecture is a bit tricky... I mean, normally the lookup is based on keys, not on values, but I understand that sometimes you need to reverse the common logics and retrieve what you need within a data structure that is adequate in many other situations, except that one!
Anyway, if I was given the choice I would go for the HashSet approach, since it can grant a uniqueness of its values and also an immediate aggregation of the subtypes. But you can eventually run a benchmark and find out the fastest lookup solution in a few minutes.
Using the aforementioned approach (the HashSet one I mean), you can retrieve your type as follows:
String animalType = data.Where(kvp => kvp.Value.Contains("Dolphin")).Select(p => p.Key).FirstOrDefault();
// Animal type found: Fish!
if (animalType != null)
Console.WriteLine("Animal type found: " + animalType + "!");
else
Console.WriteLine("No animal type found!");

Related

how to use string.join to join all values from an object array?

I have the same situation as this user how to use string.join to join value from an object array? in this question. However, I want to join all values from the object instead of only 1 value.
To recap my question:
I have an array of object e.g:
MyObject[] objs;
and within MyObject it contains 3 string property,
object[0].stringValue1
object[0].stringValue2
object[0].stringValue3
If I want to join the whole array of objects by all of their stringValues (stringValues1,2 and 3), how can I do it?
I think selector doesn’t allow me to select several elements, then how to use string.join to join several values from an object array?
See below for example usage of the two extension methods provided in the implementation section below. The first allows you to select the properties to include, and the second includes all string properties of the object in the source collection which I believe is the exact answer to your question.
Example Usage
Note, the resulting output from the two examples below are ordered differently as a result of how each implementation works, however the results are otherwise identical as a result of the first example specifying all string properties on the MyObj sample type
Live Fiddle Example
// Test Object
public class MyObj
{
public MyObj(string prop1, string prop2, string prop3)
{
Prop1 = prop1;
Prop2 = prop2;
Prop3 = prop3;
}
public string Prop1 { get; set; }
public string Prop2 { get; set; }
public string Prop3 { get; set; }
}
// Sample Data
var list = new List<MyObj> {
new MyObj("A1", "B1", "C1"),
new MyObj("A1", "B2", "C2"),
new MyObj("A3", "B3", "C3")
};
Samples using above object and data
// Example #1 - All properties separated by single separator
Console.WriteLine(list.Join(colSeparator: ','));
// RESULT: A1,A1,A3,B1,B2,B3,C1,C2,C3
// Example #2 - Object property separator, and different object separator
Console.WriteLine(list.Join(colSeparator: ',', rowSeparator: '\n'));
// RESULT: A1,B1,C1
A1,B2,C2
A3,B3,C3
Implementation
public static class EnumerableStringJoinExtension
{
public static string Join<T>(this IEnumerable<T> values, char colSeparator, char? rowSeparator = null)
{
var strProperties = typeof(T).GetProperties().Where(r=>r.PropertyType == typeof(string));
var sb = new StringBuilder();
foreach(var val in values)
sb.Append(string.Join(colSeparator, strProperties.Select(r=> r.GetValue(val)))).Append(rowSeparator ?? colSeparator);
sb.Remove(sb.Length - 1, 1);
return sb.ToString();
}
}
A possible way to solve it is to first create an array of each object's properties (using .Select()), and then flatten the resulting property arrays by using .SelectMany(). Both of those methods are found in the System.Linq namespace. The resulting IEnumerable<string> object's items can then be joined using string.Join().
If MyObject is defined as
class MyObject
{
public string First { get; set; }
public string Second { get; set; }
public string Third { get; set; }
}
and you define objects as
List<MyObject> objects = new()
{
new() { First = "ABC", Second = "DEF", Third = "GHI" },
new() { First = "JKL", Second = "MNO", Third = "PQR" },
new() { First = "STU", Second = "VWX", Third = "YZ" },
};
, a possible implementation is:
var flattenedObjects = objects
.Select(obj => new[] { obj.First, obj.Second, obj.Third })
.SelectMany(properties => properties);
var objectString = string.Join("_", flattenedObjects);
Printing the value of objectString to the console gives:
ABC_DEF_GHI_JKL_MNO_PQR_STU_VWX_YZ
Example fiddle here.
If you just want a one-line, handy C# without hard-coding property name you could try this:
string.Join("\n", objs.Select(o => string.Join("_", o.GetType().GetProperties().Select(p => p.GetValue(o)))));
This produces the following result:
"A_B_C\nD_E_F\nG_H_I"
with the object array as:
var objs = new MyObject[]
{
new MyObject("A", "B", "C"),
new MyObject("D", "E", "F"),
new MyObject("G", "H", "I")
};
But please note that if your class has properties in other types then you might need a Where() between GetProperties() and the second Select() to exclude unwanted properties.

C# - Generic custom List.Add method to add checks before adding?

I am wondering if something like this is possible. I am looking to create a method that can be called instead of List.Add. The method would check any/all string properties and make sure they don't exceed their specific given max lengths, and if so truncate to proper size. I would ideally like it to be generic so that it will not only work for ObjectA, but also ObjectB, ObjectC, etc.
I am open to any and all suggestions. I know it seems like a weird thing to do, but I have a lot of different objects I am working with and potentially millions of those object instances in totality across all my lists. I mainly just need a way to ensure that any objects with properties exceeding their max string limit are truncated and logged via the Worker class in a timely way. Thanks!
public class ObjectA {
public Guid aID {get; set;}
[MaxLength(128)]
public string aName {get; set;}
[MaxLegnth(30)]
public string aType {get; set;}
}
--
public class Worker {
private void Work() {
List<ObjectA> listOfA = new List<ObjectA>();
listOfA.CustomAddMethod(new ObjectA(new Guid, "Something", "Unknown"));
}
// ??????
private CustomAddMethod(T object) {
foreach property {
if (isStringProperty && isGreaterThanMaxLength) {
// truncate to proper size
// log for truncation message
}
// Then add to list
}
}
}
You can create an extension method.
Here is a code snip. You can improve the performance by implementing a cache, for example, using a dictionary to store the properties and the MaxLengthAttribute based on object's type.
public static class ListExtensions
{
public static void CustomAdd<T>(this List<T> list, T item, Action<string> logger = null)
{
var propertyInfos = typeof(T)
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Where(propertyInfo => propertyInfo.PropertyType == typeof(string))
.ToList();
foreach (var propInfo in propertyInfos)
{
var maxLengthAttr = propInfo
.GetCustomAttributes(typeof(MaxLengthAttribute))
.Cast<MaxLengthAttribute>()
.FirstOrDefault();
if (maxLengthAttr is null)
continue;
var currentString = (string)propInfo.GetValue(item);
if (!maxLengthAttr.IsValid(currentString))
{
var newValue = currentString.Substring(0, maxLengthAttr.Length);
logger?.Invoke(
$"Resolving error: {maxLengthAttr.FormatErrorMessage(propInfo.Name)}\n" +
$"Old Value: {currentString}\n" +
$"New Value: {newValue}"
);
propInfo.SetValue(item, newValue);
}
}
list.Add(item);
}
}
Example of usage (code removed for brevity):
public class Person
{
[MaxLength(4)]
public string Name { get; set; }
}
...
var personList = new List<Person>();
personList.CustomAdd(
new Person {Name = "John Doe"},
message => Debug.WriteLine(message)
);
...
As result the Jhon Doe string will be trimmed to Jhon

Filtering a Key Value from a Dictionary in C#

I'm new to C# and am currently trying to figure out the best way to implement the following:
I have a list of species with an associated group:
Bird: "Budgie", "Parrot"
Dog: "Pitbull", "Labrador"
Cat: "Cheetah", "Lion"
Given a string of an animal, I need to return its group.
eg. "Cheetah" would return "Cat".
I have implemented the following:
// create list one and store values
List<string> valSetOne = new List<string>();
valSetOne.Add("Budgie");
valSetOne.Add("Parrot");
// create list two and store values
List<String> valSetTwo = new List<String>();
valSetTwo.Add("Lion");
valSetTwo.Add("Cheetah");
// create list three and store values
List<String> valSetThree = new List<String>();
valSetThree.Add("Labrador");
valSetThree.Add("Pitbull");
// add values into map
map.Add("Bird", valSetOne);
map.Add("Cat", valSetTwo);
map.Add("Dog", valSetThree);
foreach(KeyValuePair<string, List<string>> kvp in map){
foreach(string value in kvp.Value)
{
Console.WriteLine("Key = {0}, Value = {1}", kvp.Key, value);
}
}
Instead of having a foreach loop, is there a faster way to find the key given an animal value?
//EDIT:
Currently trying to initialize with a Tuple
var tupleList = new List<Tuple<string, List<string>>>
{
new Tuple<string, List<string>>("cat", { "cheetah", "lion"})
};
I'm getting the following error: Unexpected symbol `{'
A better modeling of the data will be to create a class of the group which will have a name and a collection of animals in it. Then you can hold a collection of groups and query it.
If you want to stick with the dictionary then: As your question is to retrieve the key for a given value out of the list of values for that key, I'd organize the data the other way around:
var map = new Dictionary<string,string>
{
["Cheetah"] = "Cat",
["Lion"] = "Cat",
//....
};
Then you can search by your real key - which is the species and not the group:
if(map.TryGetValue("Lion", out var group)) { }
As it is easier to construct the data by grouping to list of species you can:
var rawData = new List<(string group, List<string> species)>
{
("Cat", new List<string> { "Cheetah", "Lion" }),
//...
};
var result = rawData.SelectMany(item => item.species.Select(s => (s, item.group)))
.ToDictionary(k => k.s, v => v.group);
This works with C#7.0 named tuples. If you are using a previous version you can use anonymous types or "old fashion" tuples
For a pre C# 7.0 initialization of the collection:
var rawData = new List<Tuple<string, List<string>>>
{
Tuple.Create<string,List<string>> ("Cat", new List<string> { "Cheetah", "Lion" })
};
If you don't want to use another way to store your data and you store them in a dictionary with keys the animal species, I would suggest you change the type of the value that is associated with each key, to a HashSet
Dictionary<string, HashSet<string>>
Doing so you can iterate through the keys (O(n), where n is the number of keys) of the dictionary and using HashSet Contains method in O(1) you can find out if the animal is associated with the current key/spiece or not.
Using your existing dictionary structure, you can do this:
string cheetahGroup = map.FirstOrDefault(item =>
item.Value.Contains("cheetah", StringComparer.OrdinalIgnoreCase)).Key;
Note that if the animal doesn't exist, then cheetahGroup will be null. In the case above, it will be "Cat".
As others have suggested I think you should use classes here. But other responses stop at 1 level of classes. I would recommend a more holistic object-oriented approach.
public abstract class Animal {}
public abstract class Dog : Animal {}
public abstract class Cat : Animal {}
public abstract class Bird : Animal {}
public sealed class Budgie : Bird {}
public sealed class Parrot : Bird {}
public sealed class Pitbull : Dog {}
public sealed class Labrador : Dog {}
public sealed class Cheetah : Cat {}
public sealed class Lion : Cat {}
[note that sealed is optional here]
then you just say
Labrador spot = new Labrador();
to check if it's a dog
if (spot is Dog) {
print("it's a dog");
}
This solution has the option of being easily extensible, and super easy to read. There's not weird code that distracts you from the fact that a Labrador is a dog, and a budgie is a bird. It's very clear what you're trying to do and what classes Dog, Cat, Cheetah, etc represent.
If you further wanted to split up labradors into Chocolate, Yellow, and Black, you could just add another layer of classes inheriting from Labrador. If you needed to define the properties of the animals, that's easy too. For example if you only wanted Dogs and Cats to have names, you could add a name field in their classes, whereas if you want all animals to have a name you can put the name field in the Animal class.
If you needed to move the animals, you could at the animal level define a method called Move() that forces its children to override this method in their own way, so perhaps dogs would Move() by Walking(), whereas birds would Move() by Flying().
I would seriously recommending abandoning the dictionary and think about using something similar to what I described. If you start to think this way it will really help you plan out more complex projects and tasks. There's a reason Object Oriented Programming is so widespread!
I would make a class Animal, since you are working with animals, that would have two properties: Species and SubSpecies
public class Animal
{
public string Species { get; set; }
public string SubSpecies { get; set; }
public Animal(string Species, string SubSpecies)
{
this.Species = Species;
this.SubSpecies = SubSpecies;
}
}
Then you can instanciate all your animals inside a list :
List<Animal> myAnimals = new List<Animal>();
myAnimals.Add(new Animal("Bird", "Budgie"));
myAnimals.Add(new Animal("Bird", "Parrot"));
myAnimals.Add(new Animal("Dog", "Pitbull"));
myAnimals.Add(new Animal("Dog", "Labrador"));
myAnimals.Add(new Animal("Cat", "Cheetah"));
myAnimals.Add(new Animal("Cat", "Lion"));
And finally, you can use Linq to find whatever you're looking for:
//Will return null if the subSpecies doesn't exist
public string FindTheSpecies(string SubSpecies)
{
return myAnimals.FirstOrDefault(x => x.SubSpecies == SubSpecies)?.Species;
}

What kind of data structure to use?

I am working on a project where I need to keep track of:
5-6 Root items of just a string name
Each root item need to have multiple children of different identifier types (int, string, float, etc). All the children of one root will be the same type but each root will have different children types
user will need to be able to add/delete children from each root
i will later need to access each children individually and perform string manipulations and parsing when needed
I've thought about maybe using a dictionary where the Key is a string and Values are lists of objects. Or having a unique class for each root item and each class will include a List of children.
Does anyone have any good suggestions? I'm still quite new to OOP, please bear with me :)
Thanks!
public interface IRoot {}
public class RootItem<T> : IRoot
{
public string Name { get; set; }
public List<T> Children {get; set; }
}
And then keep a Dictionary<string, IRoot> to hold them all.
Dictionary<string, IRoot> hair = new Dictionary<string, IRoot>();
hair.Add(
new RootItem<int>()
{
Name = "None",
Children = new List<int>() {1, 2, 3, 4}
}
);
hair.Add(
new RootItem<decimal>()
{
Name = "None",
Children = new List<decimal>() {1m, 2m, 3m, 4m}
}
);
How about a generic class with a List<T> to contain the children:
public class Root<T>
{
private List<T> children = null;
public Root(string name)
{
Name = name;
}
public string Name { get; set; }
public List<T> Children
{
get
{
if (children == null)
{
children = new List<T>();
}
return children;
}
}
}
Root<int> intRoot = new Root<int>("IntRoot");
intRoot.Children.Add(23);
intRoot.Children.Add(42);
Root<string> stringRoot = new Root<string>("StringRoot");
stringRoot.Children.Add("String1");
stringRoot.Children.Add("String2");
stringRoot.Children.Add("String3");
stringRoot.Children.Add("String4");
If you want to hold all the roots in one object, you could write your own class or use a Tuple:
var rootGroup = Tuple.Create(intRoot, stringRoot);
// intRoot is accessible as rootGroup.Item1
// stringRoot is accessible as rootGroup.Item2
Sounds like Dictionary<string, Tuple<type1, type 2, etc>> is a good candidate.
The key will be the string(root). The children to the root is a Tuple. We can add items to tuple. Thanks for pointing thisout.
Good starting point on Tuple
Here's one way to go about it. There's a lot of casting that needs to happen, but it gets the job done:
static void Main(string[] args)
{
Dictionary<string, IRootCollection> values = new Dictionary<string, IRootCollection>();
values["strings"] = new RootCollection<string>();
(values["strings"] as RootCollection<string>).Add("foo");
(values["strings"] as RootCollection<string>).Add("bar");
values["ints"] = new RootCollection<int>();
(values["ints"] as RootCollection<int>).Add(45);
(values["ints"] as RootCollection<int>).Add(86);
}
interface IRootCollection { }
class RootCollection<T> : List<T>, IRootCollection { }

How to remove multiple objects in List by id?

I have a class as below
class MyClass
{
public string id { get; set; }
public string data { get; set; }
}
I have an array of ids I need to remove:
List<myClass> myObjArray = new List<myClass>;
myClass myObj1 = new myClass { id = "1", data = "aaa"};
myClass myObj2 = new myClass { id = "2", data = "bbb"};
myClass myObj3 = new myClass { id = "3", data = "ccc"};
myClass myObj4 = new myClass { id = "4", data = "ddd"};
myObjArray.Add(myObj1);
myObjArray.Add(myObj2);
myObjArray.Add(myObj3);
myObjArray.Add(myObj4);
string [] idToBeRemove = {"1", "3"};
Is there any method to remove the myObj in myObjArray where the id is in the idToBeRemove string?
List<T> has a method RemoveAll which will accomplish what you need. Unlike doing something like Where(...).ToList(), it will modify the existing list in place rather than create a new one.
myObjArray.RemoveAll(item => idToBeRemove.Contains(item.id));
Note that as your array of items to remove grows, you'll want to move to something more performant, such as a HashSet<T>, though with your current count of 2, it is not a pressing need.
#Richardissimo also offers a good suggestion in the comments to your question for when the main list itself grows, a Dictionary<K, V> could be useful. Again, not an immediate need with a small list, but something to keep in mind as it grows and if performance is an issue.
To remove from existing List you can use List.RemoveAll(Predicate):
myObjArray.RemoveAll(r => idToBeRemove.Contains(r.id));
To get result in new collection you can use Enumerable.Where and Enumerable.Contains:
var result = myObjArray.Where(m => !idToBeRemove.Contains(m.id)).ToList();
var result = myObjArray.Where(m => !idToBeRemove.Contains(m.id)).ToList();
foreach (var item in result)
{
myObjArray.Remove(item);
}
Use this to remove id:
myObjArray.RemoveAll(item => idToBeRemove.Contains(item.id));

Categories