Best way to maintain counts of objects - c#

I have a set of objects (say they are all different kinds of animals --- Bear, Bear, Lion, Tiger, Bear)
What is the best way to maintain a count for each animal?
I tried to use Dictionary but the "int" is of course read-only.
Ideally, I want something like animal["Bear"] = 2, animal["Lion"] = 1, animal["Tiger"] = 1 etc.

I tried to use Dictionary but the int is of course read-only.
int being a value type shouldn't stop you: you just need to treat it the way you treat immutable objects when you use them as dictionary values that need to change, i.e. by reassigning them.
Here is an example of how to increment a count inside a dictionary:
var counts = new Dictionary<Animal,int>();
Animal someAnimal = ... // Get some animal
int currentCount;
if (counts.TryGetValue(someAnimal, out currentCount)) {
counts[someAnimal] = currentCount+1;
} else {
counts.Add(someAnimal, 1);
}

You can try something like this
string[] animals = new string[] { "Bear", "Bear", "Lion", "Tiger", "Bear" };
var groups = animals.GroupBy(v => v);
foreach(var group in groups)
Console.WriteLine("[{0}] = {1}", group.Key, group.Count());
You can also make a Collection or List of Animals and get the count by its value type. Since it is not clear how your class looks like I have given example with the string.

Related

c# how to best put array or list into class that also has name?

Instead of doing my old non OOP way I am trying to do what people claim is best. I need to store about 9 different Int arrays of differing lengths. I also need to associate them with a String Name "this is called etc.." I was thinking it would make sense to store that all into a class object so I can cleanly iterate through them later on without looking to two different places using the same for loop iterator.
Example:
public class Thing
{
public List<int> SDNA {get; set;}
public string Name {get; set;}
}
List<Thing> things = new List<Thing>
{
new Thing { SDNA = {2,4,5,7,9,11},Name = "First Thing"}
}
I get a null ref exception (I am assuming its cause of the list within a class somehow) I tried creating a list this way to clear the null ref but it had some other errors.
List<Thing> things = new List<Thing>();
things.Add(new Thing() {SDNA = {2,4,5,7,9,11},Name = "The first things name"});
Errors of invalid token etc. Should I just do it with two different stored arrays, one for names and a jagged array for the Ints and then reference them each? That feels ugly to me. Why can't I store them all into one thing?
Thanks!
In the simplest case if you want just to have name to value (array) association, you can try using a simple Dictionary, e.g.
private Dictionary<string, List<int>> things = new Dictionary<string, List<int>>() {
{"First thing", new List<int>() {2, 4, 5, 7, 9, 11}},
};
then you can use it
// Add new thing
things.Add("Some other thing", new List<int>() {1, 2, 3, 4, 5});
// Try get thing
if (things.TryGetValue("First thing", out var list)) {
// "First thing" exists, list is corresponding value
}
else {
// "First thing" doesn't found
}
// Remove "Some other thing"
things.Remove("Some other thing");
// Iterate over all {Key, Value} pairs (let's print them):
foreach (var pair in things)
Console.WriteLine($"{pair.Key} :: [{string.Join(", ", pair.Value)}]");
However, if Thing is not just SDNA + Name combination (more properties, methods are expected) I suggest
private Dictionary<string, Thing> things
declaration

Compare two nested object lists based on the List<string> values in c#

I have three lists, two object lists to be compared based on the third list which is of type string and the resultant should be the common values from both the lists.
Example
List<Object1>
- Name
- ID
- AddressList[]
AddressList Item
- State
- City
- Zip
AddressList Item
- State
- City
- Zip
List<Object1>
- Name
- ID
- AddressList[]
AddressList Item
- State
- City
- Zip
AddressList Item
- State
- City
- Zip
List<string> = new List<string> {"State","City"};
In this case only the values of State and City must be compared. If the List= new List {"State"} then only the State values from both the list objects needs to be compared.
I've been trying to find a way to do this by myself but I feel like everything I did is completely wrong. I'd be more than happy if someone could help me. Thanks a lot!
There are some open source libraries that provide that kind of functionality, such as ObjectComparer: https://github.com/ValeraT1982/ObjectsComparer
It allows you to compary variable number of properties such as this:
//Initialize objects and comparer
var a1 = new ClassA { StringProperty = "String", IntProperty = 1 };
var a2 = new ClassA { StringProperty = "String", IntProperty = 1 };
var comparer = new Comparer<ClassA>();
//Compare objects
IEnumerable<Difference> differences;
var isEqual = comparer.Compare(a1, a2, out differences);
//Print results
Debug.WriteLine(isEqual ? "Objects are equal" : string.Join(Environment.NewLine, differenses));
Without using a third party library, you would need to use reflection to compare them dynamically like that.
Something like this(missing all kinds of safety code):
var propertyInfo = typeof(Object1).GetProperty(propName);
object valueA;
object valueB;
valueA = propertyInfo.GetValue(objectA, null);
valueB = propertyInfo.GetValue(objectB, null);

How to compare two List<string> and return non-matches (c#)

.net fiddle: https://dotnetfiddle.net/27wJLc
I have two lists of strings, the 'all' list and a list to compare, I then want to return the strings from the 'All' list that did not match:
public static void Main()
{
List<string> ratiodGids = new List<string>();
List<string> allGids = new List<string>();
List<string> notRatiodGids = new List<string>();
allGids.Add("tom");
allGids.Add("bob");
allGids.Add("bill");
allGids.Add("tim");
allGids.Add("sam");
ratiodGids.Add("tom");
ratiodGids.Add("tim");
ratiodGids.Add("sam");
foreach(var g in ratiodGids)
{
if(!allGids.Contains(g))
{
notRatiodGids.Add(g);
}
}
Console.WriteLine(notRatiodGids.Count);
foreach(var i in notRatiodGids)
Console.WriteLine(i);
}
I'm pretty sure I can re-type everything back to IEnumerable then use Intersect or Except, but that will take some work. So I wanted to see if it was possible with Lists before doing that.
With the code I have, the list returns nothing. If I remove the '!' I get back tom, tim, sam. So it works one way, but not the other.
UPDATE: The result I want is just the strings from ratiodGids that don't exist in allGids. Order does not matter. Also, I have tried using Exists(), Except(), Any(), Where() and they both give me the "List does not contain a method called blah ...".
Well, you can use Except() on a List as well, because it implements IList which derives from IEnumerable.
Update
As pointed out in the comments, the Linq-methods are extension methods on IEnumerable. To use them, you have to add their namespace by adding: using System.Linq;
You can compare two lists and return a new list with only the differences like so:
List<string> difference = list1.Except(list2).ToList();
You can do it like:
var results = allGids.Where( a=> !ratiodGids.Contains(a))
if you want to get uniqe items from both lists then:
var results = allGids.Where( a=> !ratiodGids.Contains(a))
.Union(ratiodGids.Where( a=> !allGids.Contains(a)));
change this
foreach(var g in ratiodGids)
{
if(!allGids.Contains(g))
{
notRatiodGids.Add(g);
}
}
to this
foreach(var g in allGids)
{
if(!ratioGids.Contains(g))
{
notRatiodGids.Add(g);
}
}
To be clear, if listA = { A,B,C } and listB = { B,C,D } do you want your result to be { A,D }? If so, here's one way:
var union = listA.Union(listB);
var intersection = listA.Intersect(listB);
var outside = union.Except(intersection);

Map two string arrays without a switch

I have two arrays that need to be mapped. In code
var result = "[placeholder2] Hello my name is [placeholder1]";
var placeholder = { "[placeholder1]", "[placeholder2]", "[placeholder3]", "[placeholder4]" };
var placeholderValue = { "placeholderValue3", "placeholderValue2", "placeholderValue3" };
Array.ForEach(placeholder , i => result = result.Replace(i, placeholderValue));
given i, placeholderValue needs to be set in an intelligent way. I can implement a switch statement. The cyclomatic complexity would be unacceptable with 30 elements or so. What is a good pattern, extension method or otherwise means to achieve my goal?
I skipped null checks for simplicity
string result = "[placeholder2] Hello my name is [placeholder1]";
var placeHolders = new Dictionary<string, string>() {
{ "placeholder1", "placeholderValue1" },
{ "placeholder2", "placeholderValue2" }
};
var newResult = Regex.Replace(result,#"\[(.+?)\]",m=>placeHolders[m.Groups[1].Value]);
The smallest code change would be to just use a for loop, rather than a ForEach or, in your case, a ForEach taking a lambda. With a for loop you'll have the index of the appropriate value in the placehoderValue array.
The next improvement would be to make a single array of an object holding both a placeholder and it's value, rather than two 'parallel' arrays that you need to keep in sync.
Even better than that, and also even simpler to implement, is to just have a Dictionary with the key being a placeholder and the value being the placeholder value. This essentially does the above suggestion for you through the use of the KeyValuePair class (so you don't need to make your own).
At that point the pseudocode becomes:
foreach(key in placeholderDictionary) replace key with placeholderDictionary[key]
I think you want to use Zip to combine the placeholders with their values.
var result = "[placeholder2] Hello my name is [placeholder1]";
var placeholder = new[] { "[placeholder1]", "[placeholder2]", "[placeholder3]", "[placeholder4]" };
var placeholderValue = new[] { "placeholderValue1", "placeholderValue2", "placeholderValue3", "placeholderValue4" };
var placeHolderPairs = placeholder.Zip(placeholderValue, Tuple.Create);
foreach (var pair in placeHolderPairs)
{
result = result.Replace(pair.Item1, pair.Item2);
}

Merge two (or more) lists into one, in C# .NET

Is it possible to convert two or more lists into one single list, in .NET using C#?
For example,
public static List<Product> GetAllProducts(int categoryId){ .... }
.
.
.
var productCollection1 = GetAllProducts(CategoryId1);
var productCollection2 = GetAllProducts(CategoryId2);
var productCollection3 = GetAllProducts(CategoryId3);
You can use the LINQ Concat and ToList methods:
var allProducts = productCollection1.Concat(productCollection2)
.Concat(productCollection3)
.ToList();
Note that there are more efficient ways to do this - the above will basically loop through all the entries, creating a dynamically sized buffer. As you can predict the size to start with, you don't need this dynamic sizing... so you could use:
var allProducts = new List<Product>(productCollection1.Count +
productCollection2.Count +
productCollection3.Count);
allProducts.AddRange(productCollection1);
allProducts.AddRange(productCollection2);
allProducts.AddRange(productCollection3);
(AddRange is special-cased for ICollection<T> for efficiency.)
I wouldn't take this approach unless you really have to though.
Assuming you want a list containing all of the products for the specified category-Ids, you can treat your query as a projection followed by a flattening operation. There's a LINQ operator that does that: SelectMany.
// implicitly List<Product>
var products = new[] { CategoryId1, CategoryId2, CategoryId3 }
.SelectMany(id => GetAllProducts(id))
.ToList();
In C# 4, you can shorten the SelectMany to: .SelectMany(GetAllProducts)
If you already have lists representing the products for each Id, then what you need is a concatenation, as others point out.
you can combine them using LINQ:
list = list1.Concat(list2).Concat(list3).ToList();
the more traditional approach of using List.AddRange() might be more efficient though.
List.AddRange will change (mutate) an existing list by adding additional elements:
list1.AddRange(list2); // list1 now also has list2's items appended to it.
Alternatively, in modern immutable style, you can project out a new list without changing the existing lists:
Concat, which presents an unordered sequence of list1's items, followed by list2's items:
var concatenated = list1.Concat(list2).ToList();
Not quite the same, Union projects a distinct sequence of items:
var distinct = list1.Union(list2).ToList();
Note that for the 'value type distinct' behaviour of Union to work on reference types, that you will need to define equality comparisons for your classes (or alternatively use the built in comparators of record types).
You could use the Concat extension method:
var result = productCollection1
.Concat(productCollection2)
.Concat(productCollection3)
.ToList();
I know this is an old question I thought I might just add my 2 cents.
If you have a List<Something>[] you can join them using Aggregate
public List<TType> Concat<TType>(params List<TType>[] lists)
{
var result = lists.Aggregate(new List<TType>(), (x, y) => x.Concat(y).ToList());
return result;
}
Hope this helps.
list4 = list1.Concat(list2).Concat(list3).ToList();
// I would make it a little bit more simple
var products = new List<List<product>> {item1, item2, item3 }.SelectMany(id => id).ToList();
This way it is a multi dimensional List and the .SelectMany() will flatten it into a IEnumerable of product then I use the .ToList() method after.
I've already commented it but I still think is a valid option, just test if in your environment is better one solution or the other. In my particular case, using source.ForEach(p => dest.Add(p)) performs better than the classic AddRange but I've not investigated why at the low level.
You can see an example code here: https://gist.github.com/mcliment/4690433
So the option would be:
var allProducts = new List<Product>(productCollection1.Count +
productCollection2.Count +
productCollection3.Count);
productCollection1.ForEach(p => allProducts.Add(p));
productCollection2.ForEach(p => allProducts.Add(p));
productCollection3.ForEach(p => allProducts.Add(p));
Test it to see if it works for you.
Disclaimer: I'm not advocating for this solution, I find Concat the most clear one. I just stated -in my discussion with Jon- that in my machine this case performs better than AddRange, but he says, with far more knowledge than I, that this does not make sense. There's the gist if you want to compare.
To merge or Combine to Lists into a One list.
There is one thing that must be true: the type of both list will be
equal.
For Example: if we have list of string so we can add add another list to the
existing list which have list of type string otherwise we can't.
Example:
class Program
{
static void Main(string[] args)
{
List<string> CustomerList_One = new List<string>
{
"James",
"Scott",
"Mark",
"John",
"Sara",
"Mary",
"William",
"Broad",
"Ben",
"Rich",
"Hack",
"Bob"
};
List<string> CustomerList_Two = new List<string>
{
"Perter",
"Parker",
"Bond",
"been",
"Bilbo",
"Cooper"
};
// Adding all contents of CustomerList_Two to CustomerList_One.
CustomerList_One.AddRange(CustomerList_Two);
// Creating another Listlist and assigning all Contents of CustomerList_One.
List<string> AllCustomers = new List<string>();
foreach (var item in CustomerList_One)
{
AllCustomers.Add(item);
}
// Removing CustomerList_One & CustomerList_Two.
CustomerList_One = null;
CustomerList_Two = null;
// CustomerList_One & CustomerList_Two -- (Garbage Collected)
GC.Collect();
Console.WriteLine("Total No. of Customers : " + AllCustomers.Count());
Console.WriteLine("-------------------------------------------------");
foreach (var customer in AllCustomers)
{
Console.WriteLine("Customer : " + customer);
}
Console.WriteLine("-------------------------------------------------");
}
}
In the special case: "All elements of List1 goes to a new List2": (e.g. a string list)
List<string> list2 = new List<string>(list1);
In this case, list2 is generated with all elements from list1.
You need to use Concat operation
When you got few list but you don't know how many exactly, use this:
listsOfProducts contains few lists filled with objects.
List<Product> productListMerged = new List<Product>();
listsOfProducts.ForEach(q => q.ForEach(e => productListMerged.Add(e)));
If you have an empty list and you want to merge it with a filled list, do not use Concat, use AddRange instead.
List<MyT> finalList = new ();
List<MyT> list = new List<MyT>() { a = 1, b = 2, c = 3 };
finalList.AddRange(list);

Categories