Reduce list of enum values to single int - c#

I have an enum which designates categories (columns) for some data. I would like to be able to store a sorting order, so a list of these enums as a single int value. So that no matter what the size of the list (within reason) there is a unique int that can reconstruct the list.
public enum SortingCategories
{
ORDER_DATE = 1,
CUSTOMER_STATE = 2,
CUSTOMER_NAME = 3,
CUSTOMER_HAIRCOLOR = 4,
...
}
public IEnumerable<SortingCategories> GetSortingOrder(int code)
{
...
}
public int GetSortingCode(IEnumerable<SortingCategories> order)
{
...
}
public const List<SortingCategories> PossibleSortingOrder = new List<SortingCategories>()
{
SortingCategories.CUSTOMER_NAME,
SortingCategories.CUSTOMER_STATE,
SortingCategories.ORDER_DATE,
SortingCategories.CUSTOMER_HAIRCOLOR
};
public const List<SortingCategories> AnotherPossibleSortingOrder = new List<SortingCategories>()
{
SortingCategories.CUSTOMER_STATE,
SortingCategories.CUSTOMER_HAIRCOLOR
};

How about just concatenating the values if they are < 10
Eg.
1324
2341
4231

Traditionally, the solution is to use binary flags for this purpose. This lets you save the options, but not the order. There is no way to save the selections AND there order in a single number unless you create flags for every legal combination. You could however, construct your own custom string format for such a list. i.e) {3}{6}{1} and cast those values as SortingCategories before doing the sort.
Here is how the flags option might look.
public enum SortingCategories
{
ORDER_DATE = 0x1,
CUSTOMER_STATE = 0x2,
CUSTOMER_NAME = 0x4,
CUSTOMER_HAIRCOLOR = 0x8,
...
}
Then use binary operators. i.e)
uint categories = SortingCategories.ORDER_DATE | SortingCategories.CUSTOMER_NAME;
if((categories & SortingCategories.ORDER_DATE) == SortingCategories.ORDER_DATE)
do something...

Assuming you've set the enum up where each value is a unique power of 2, like this:
[Flags]
public enum SortingCategories
{
ORDER_DATE = 1,
CUSTOMER_STATE = 2,
CUSTOMER_NAME = 4,
CUSTOMER_HAIRCOLOR = 8,
}
You can use the Aggregate extension method:
var myEnumList = new List<SortingCategories>()
{
SortingCategories.CUSTOMER_NAME,
SortingCategories.CUSTOMER_STATE,
SortingCategories.ORDER_DATE,
SortingCategories.CUSTOMER_HAIRCOLOR
};
var myEnumValue = myEnumList.Aggregate((x, y) => x | y);
Update this is a generalization from Rob van der Veer's answer which will work for enum types with up to 9 members—or more generally for enum types with n members it can encode lists up to logn(231-1) items. It will ensure you can decode the integer to get the original list, including the order in which the items appeared in the list:
// Get the number of members in the enum type
// Note, you could hard code this as a private const as well
int numberOfMembers = Enum.GetValues(typeof(SortingCategories)).Length;
// Get integer value from enum list
int EnumListToInt(IEnumerable<SortingCategories> list, int numberOfMembers)
{
return list.Aggregate(
new { i = 0, p = 1 },
(a, e) => new { i = (a.i + (int)e * a.p), p = a.p * numberOfMembers },
a => a.i);
}
// Get integer value representing list
List<SortingCategories> EnumListToInt(int intVal, int numberOfMembers)
{
return Enumerable.Range(1, numberOfMembers).Select(
p => {
var result = intValue % numberOfMembers;
intValue /= numberOfMembers;
return (SortingCategories)result;
}).ToList();
}
Note that unlike the previous method, this method assumes the enum values range from 0 to N, as though you didn't specify any explicit values for the enum members. This could be modified to support any values by using something along the lines of:
int numberOfMembers = Enum.GetValues(typeof(SortingCategories)).Cast<int>().Max() + 1;

Related

Comparison process with enum element values

Enum A
{ a = 10, a1 = 35, ....}
Enum B
{ b = 5, b1 = 20, ..... }
How can I get the int values ​​of any two of the two enum class elements? To compare with these values ​​and destroy the smaller one.
Sorry. Maybe this is what you're looking for?
enumA.CompareTo(enumB) > 0 ? "A is greater than B" : "B is greater than or equal to A";
enumA.CompareTo(enumB) = 0 ? "A is equal to B" : "A is not equal to B";
This should compare two Enum instances (enumA and enumB), and execute the code inserted in place of the strings.
Take this console app as an example to obtain the lowest int from your enum type:
using System;
using System.Linq;
using System.Collections.Generic;
namespace ConsoleApp1 {
class Program {
enum Colors {
blue = 1,
orange = 2,
purple = 3
}
enum Stages {
level1 = 35,
level2 = 62,
level3 = 13
}
public static int getMinEnumValue(Type enumType) {
Array values = Enum.GetValues(enumType);
List<int> intList = ((int[])values).ToList();
int minValue = intList.Min();
return minValue;
}
static void Main(string[] args) {
int minStagegInt = getMinEnumValue(typeof(Stages));
int minColorInt = getMinEnumValue(typeof(Colors));
Console.WriteLine($"Min stage int: {minStagegInt}, which is: {Enum.GetName(typeof(Stages), minStagegInt)}");
Console.WriteLine($"Min color int: {minColorInt}, which is: {Enum.GetName(typeof(Colors), minColorInt)}");
Console.ReadLine();
//keyValue anonymous type
var myInstance1 = new[] { new { a = 10 }, new { a = 35 }, new { a = 3 } };
var myInstance2 = new[] { new { b = 5 }, new { b = 20 } };
var minObject1 = myInstance1.ToList().OrderBy(elem => elem.a).FirstOrDefault();
var minObject2 = myInstance2.ToList().OrderBy(elem => elem.b).FirstOrDefault();
Console.WriteLine($"Min a instance1: {minObject1.ToString()}");
Console.WriteLine($"Min b instance2: {minObject2.ToString()}");
Console.ReadLine();
}
}
}
Output:
Once you are able to get your min, or apply the logic you wish to your enum type, you can destroy or do whatever you want to your obtained enumeration intance of your enum type.
Enum can be cast to int
EnumA valA = EnumA.a; //10
EnumB valB = EnumB.b; //5
if((int)valA < (int)valB) // 10 < 5
{ Destroy(a);}
else {Destroy(b);}
Edit:
public static int CompareAtoB(int a, int b)
{
if(a==b) return 0;
return (a < b) ? 1 : -1;
}
You can now pass any value that you cast when you give as parameter.
int result = CompareAtoB((int)someEnum, (int) otherEnum);
Then act based on result.

Add/Insert double AND string into a list

so, quickly. Is it possible to insert a double AND a string into a list? like this:
if (input2 == 0) // this boolean checks if the first number is devided by zero, then:
{
listOfResults.Insert(index: temp, item: "You divided by 0! Wait, thats illegal"); // and thats what i want, to add a string into the position of the list when the input is 0
}
else
{
result = (double)input1 / (double)input2; // the input numbers are int but i cast them to double
listOfResults.Insert(index: position, item: result);
}
My input are : 3 and 2, 6 and 3, -4 and 0, 1 and 2, i devide every first number by the second input number.
The output should be like:
1.5
2
You divided by 0! Wait, thats illegal
0.5
So is it possible to store doubles AND string for each position in List?
Yes, you could make a List< object > which can contain any data type, double, string, int, other objects, etc.
A better option may be to define a Result object such as
class Result
{
public bool Error { get; set; } = false;
public double Value { get; set; }
public string ErrorMessage { get; set; } = "";
}
And then store a list of List< Result > so that you don't need to convert or check types.
You can use a list of tuples:
var tupleList = new List<(double, string)>();
tupleList.Add((2.5, "a string"));
Here's what I'd do given your code:
var listOfResults = new List<(double? result, string error)>();
if (input2 == 0)
{
listOfResults.Insert(index: temp, item: (null, "You divided by 0! Wait, thats illegal"));
}
else
{
result = (double)input1 / input2;
listOfResults.Insert(index: position, item: (result, null));
}
And here's how to print the output:
foreach (var item in listOfResults)
{
if (item.result.HasValue)
Console.WriteLine(item.result);
else
Console.WriteLine(item.error);
}
List will allow both types. You can use typeof() == typeof(double), for instance, when using the values, or simply ToString().
static void Main(string[] args)
{
List<object> myData = new List<object>()
{
1.234,
-0.1,
"divide by zero",
100.0
};
foreach (object item in myData)
{
Console.WriteLine(item.ToString());
}
}

c# use one variable value to set a second from a fixed list

I'm parsing a CSV file in a c# .net windows form app, taking each line into a class I've created, however I only need access to some of the columns AND the files being taken in are not standardized. That is to say, number of fields present could be different and the columns could appear in any column.
CSV Example 1:
Position, LOCATION, TAG, NAME, STANDARD, EFFICIENCY, IN USE,,
1, AFT-D3, P-D3101A, EQUIPMENT 1, A, 3, TRUE
2, AFT-D3, P-D3103A, EQUIPMENT 2, B, 3, FALSE
3, AFT-D3, P-D2301A, EQUIPMENT 3, A, 3, TRUE
...
CSV Example 2:
Position, TAG, STANDARD, NAME, EFFICIENCY, LOCATION, BACKUP, TESTED,,
1, P-D3101A, A, EQUIPMENT 1, 3, AFT-D3, FALSE, TRUE
2, P-D3103A, A, EQUIPMENT 2, 3, AFT-D3, TRUE, FALSE
3, P-D2301A, A, EQUIPMENT 3, 3, AFT-D3, FALSE, TRUE
...
As you can see, I will never know the format of the file I have to analyse, the only thing I know for sure is that it will always contain the few columns that I need.
My solution to this was to ask the user to enter the columns required and set as strings, the using their entry convert that to a corresponding integer that i could then use as a location.
string standardInpt = "";
string nameInpt = "";
string efficiencyInpt = "";
user would then enter a value from A to ZZ.
int standardLocation = 0;
int nameLocation = 0;
int efficiencyLocation = 0;
when the form is submitted. the ints get their final value by running through an if else... statement:
if(standard == "A")
{
standardLocation = 0;
}
else if(standard == "B")
{
standardLocation = 1;
}
...
etc running all the way to if VAR1 == ZZ and then the code is repeated for VAR2 and for VAR3 etc..
My class would partially look like:
class Equipment
{
public string Standard { get; set;}
public string Name { get; set; }
public int Efficiency { get; set; }
static Equipment FromLine(string line)
{
var data = line.split(',');
return new Equipment()
{
Standard = data[standardLocation],
Name = [nameLocation],
Efficiency = int.Parse(data[efficiencyLocation]),
};
}
}
I've got more code in there but i think this highlights where I would use the variables to set the indexes.
I'm very new to this and I'm hoping there has got to be a significantly better way to achieve this without having to write so much potentially excessive, repetitive If Else logic. I'm thinking some kind of lookup table maybe, but i cant figure out how to implement this, any pointers on where i could look?
You could make it automatic by finding the indexes of the columns in the header, and then use them to read the values from the correct place from the rest of the lines:
class EquipmentParser {
public IList<Equipment> Parse(string[] input) {
var result = new List<Equipment>();
var header = input[0].Split(',').Select(t => t.Trim().ToLower()).ToList();
var standardPosition = GetIndexOf(header, "std", "standard", "st");
var namePosition = GetIndexOf(header, "name", "nm");
var efficiencyPosition = GetIndexOf(header, "efficiency", "eff");
foreach (var s in input.Skip(1)) {
var line = s.Split(',');
result.Add(new Equipment {
Standard = line[standardPosition].Trim(),
Name = line[namePosition].Trim(),
Efficiency = int.Parse(line[efficiencyPosition])
});
}
return result;
}
private int GetIndexOf(IList<string> input, params string[] needles) {
return Array.FindIndex(input.ToArray(), needles.Contains);
}
}
You can use the reflection and attribute.
Write your samples in ,separated into DisplayName Attribute.
First call GetIndexes with the csv header string as parameter to get the mapping dictionary of class properties and csv fields.
Then call FromLine with each line and the mapping dictionary you just got.
class Equipment
{
[DisplayName("STND, STANDARD, ST")]
public string Standard { get; set; }
[DisplayName("NAME")]
public string Name { get; set; }
[DisplayName("EFFICIENCY, EFFI")]
public int Efficiency { get; set; }
// You can add any other property
public static Equipment FromLine(string line, Dictionary<PropertyInfo, int> map)
{
var data = line.Split(',').Select(t => t.Trim()).ToArray();
var ret = new Equipment();
Type type = typeof(Equipment);
foreach (PropertyInfo property in type.GetProperties())
{
int index = map[property];
property.SetValue(ret, Convert.ChangeType(data[index],
property.PropertyType));
}
return ret;
}
public static Dictionary<PropertyInfo, int> GetIndexes(string headers)
{
var headerArray = headers.Split(',').Select(t => t.Trim()).ToArray();
Type type = typeof(Equipment);
var ret = new Dictionary<PropertyInfo, int>();
foreach (PropertyInfo property in type.GetProperties())
{
var fieldNames = property.GetCustomAttribute<DisplayNameAttribute>()
.DisplayName.Split(',').Select(t => t.Trim()).ToArray();
for (int i = 0; i < headerArray.Length; ++i)
{
if (!fieldNames.Contains(headerArray[i])) continue;
ret[property] = i;
break;
}
}
return ret;
}
}
try this if helpful:
public int GetIndex(string input)
{
input = input.ToUpper();
char low = input[input.Length - 1];
char? high = input.Length == 2 ? input[0] : (char?)null;
int indexLow = low - 'A';
int? indexHigh = high.HasValue ? high.Value - 'A' : (int?)null;
return (indexHigh.HasValue ? (indexHigh.Value + 1) * 26 : 0) + indexLow;
}
You can use ASCII code for that , so no need to add if else every time
ex.
byte[] ASCIIValues = Encoding.ASCII.GetBytes(standard);
standardLocation = ASCIIValues[0]-65;

C# How to work-around identical keys in a Dictionary

This program add's values with their other values into a dictionary, all is fine until there are identical keys (var eat(.8) and var extra(.8) with different values. How do i ensure that i can use the right key every time even though they are similar? For example, var example = gigantDictionary[.8] (but i want var edmg value instead of '500' in the code?
var wqat = 1.1; //| index 0
var rat = .2; //| index 1
var eat = .8; //| index 2
var baat = 1.2; //| index 3
var extra = .8; //| index 4
var wqdmg = 120; //| index 0
var rdmg = 60; //| index 1
var edmg = 50; //| index 2
var badmg = 40; //| index 3
var extradmg = 500; //| index 4
List<double> theOneList = new List<double>();
List<double> damageList = new List<double>();
theOneList.Add(wqat);
theOneList.Add(rat);
theOneList.Add(eat);
theOneList.Add(baat);
damageList.Add(wqdmg);
damageList.Add(edmg);
damageList.Add(rdmg);
damageList.Add(badmg);
Dictionary<double, double> gigantDictionary = new Dictionary<double, double>();
for (int i = 0; i < theOneList.Count; i++)
{
gigantDictionary.Add(theOneList[i], damageList[i]);
gigantDictionary.Add(extra, 500); //this is the malignant similar key
}
theOneList.Sort((c, p) => -c.CompareTo(p)); //orders the list
List<double> finalList = new List<double>();
for (int i = 0; i < theOneList.Count; i++)
{
finalList.Add(gigantDictionary[theOneList[i]]); //grabs damage values and add's it to 'finalList'
Console.WriteLine(finalList[i]);
}
So ultimately, i want to order 'theOneList' by descent, in doing so i can get the damages from 'gigantDictionary' and put those into 'finalList', now i have an ordered damage list that i need, but since 2 keys are similar... this is holding me back.
*Edit: Could identical indexes be the key to this? be the bridge? for example, in index 0 i get 1.1 and 120, maybe the answer lies with the identical indexes, i want to get '120' damage from '1.1', notice both have index 0, this might work
They keys aren't "similar" they're "identical". If the keys were just "similar" then, as far as the dictionary is concerned, it's no different than being "completely different". From the point of view of a dictionary items are either equal, or not equal.
For example,
var example = gigantDictionary[.8]
(but i want var edmg value instead of '500' in the code?)
But how should the dictionary possibly know that? How would it know if you actually wanted to get 500?
Do you want to prevent the duplicate keys from being added, and instead always use the first value paired with every key? If so, just check if a key exists before adding a new one.
Do you want to just get all values associated with a key, if there are duplicates? Then have a dictionary where the value is a collection, and add all values associated with that one key to the collection.
Is there actually some way to distinguish the keys so that they're not actually identical? Then do that. With just a double (which is a very bad type to use as a key for a dictionary in the first place, as it's easy for floating point rounding errors to result in similar but different doubles that you consider equivalent) there's no good way to do this, but if your actual key could be different in such a way that distinguishes the two keys, then each could point to a unique value.
Right now you have two separate list for values that must go together. A better approach is to create a structure with the two values and keep a single list.
public class Thing
{
public string Name { get; set; }
public double TheOne { get; set; }
public double Dmg { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Thing> list=new List<Thing>() {
new Thing() { Name = "wq", TheOne = 1.1, Dmg=120 },
new Thing() { Name = "r", TheOne = 0.2, Dmg=60 },
new Thing() { Name = "e", TheOne = 0.8, Dmg=50 },
new Thing() { Name = "ba", TheOne = 1.2, Dmg=40 },
new Thing() { Name = "extra", TheOne = 0.8, Dmg=500 },
};
list.Sort((t1, t2) => t1.TheOne.CompareTo(t2.TheOne));
double[] dmg_list=list.Select((t) => t.Dmg).ToArray();
}
}
Edit 1
A constructor to Thing can be used to assign values with one operation.
public class Thing
{
// Constructor sets all the values
public Thing(string name, double theone, double dmg)
{
this.Name=name;
this.TheOne=theone;
this.Dmg=dmg;
}
public string Name { get; private set; }
public double TheOne { get; private set; }
public double Dmg { get; private set; }
}
class Program
{
static void Main(string[] args)
{
List<Thing> list=new List<Thing>();
list.Add(new Thing("wq", 1.1, 120));
list.Add(new Thing("r", 0.2, 60));
list.Add(new Thing("e", 0.8, 50));
list.Add(new Thing("ba", 1.2, 40));
list.Add(new Thing("extra", 0.8, 500));
list.Sort((t1, t2) => t1.TheOne.CompareTo(t2.TheOne));
double[] dmg_list=list.Select((t) => t.Dmg).ToArray();
}
}

List<T> - distinction by T.field

I have a List<X> where X has a couple of fields:
public string word;
public int count;
how do I get a List<X> with distinct X.word elements?
You can use grouping
var n = from n in items
group n by n.word into g
select g.First();
MoreLinq has a DistinctBy method:
var distinctByWord = list.DistinctBy(x => x.Word).ToList();
From your data structure, I'd suggest you probably want a Dictionary instead of a list.
If you are doing something like counting the number of times a word is seen, or even combining (word,count) pairs from some other input by adding the counts, it will be more efficient to do this with a Dictionary because you won't have to scan the list to find the entry to update.
You'll need to use the overload of the Distinct method that takes an instance of IEqualityComparer<X>:
new List<X>().Distinct(new XComparer());
public class XComparer : IEqualityComparer<X> {
public bool Equals(X x, X y) {
return x.word.Equals(y.word);
}
public int GetHashCode(X obj) {
return obj.word.GetHashCode();
}
}
public class X {
public string Word { get; set; }
public int Count { get; set; }
}
And then:
var myList = new List<X>() {
new X(){ Count = 1, Word = "A" },
new X(){ Count = 2, Word = "A"},
new X(){ Count = 1, Word = "B"}
};
foreach(var x in myList.Distinct(new XComparer()))
Console.WriteLine(x.Count + " " + x.Word);
Prints:
1 A
1 B
I think the idea is to count the words and not to lose counts for words with the same name, right? If so, it reminds me map-reduce algorithm. You have already done map, so you need to do reduce somehow. I recommend you to create new Dictionary<string,int> and loop your list. If Dictionary does not have word - add it (key - word, count - value), if has - add count to value.

Categories