C# dictionary add values array - c#

I want to add an array with 3 values to the value of an dictionary. For example:
var dict = new Dictionary<int, ComputeNTree>();
dict.Add(1, new ComputeNTree(12, 1, 0));
dict.Add(1, new ComputeNTree(14, 2, 0));
dict.Add(2, new ComputeNTree(16, 4, 0));
public class ComputeNTree
{
public ComputeNTreeIndexes[] ComputeNTreeIndexes
}
public class ComputeNTreeIndexes
{
public int IdCategory { get; set; }
public int Nleft { get; set; }
public int Nright { get; set; }
}
It's possible for me to declare the amount of indexes at an existing key but if it's possible to add values to a certain key and it's associated array, that's no problem.
Edit: I want to translate some pieces of code from prestashop to c#:
public static function regenerateEntireNtree()
{
$id = Context::getContext()->shop->id;
$idShop = $id ? $id : Configuration::get('PS_SHOP_DEFAULT');
$sql = new DbQuery();
$sql->select('c.`id_category`, c.`id_parent`');
$sql->from('category', 'c');
$sql->leftJoin('category_shop', 'cs', 'c.`id_category` = cs.`id_category` AND cs.`id_shop` = ' . (int) $idShop);
$sql->orderBy('c.`id_parent`, cs.`position` ASC');
$categories = Db::getInstance()->executeS($sql);
$categoriesArray = array();
foreach ($categories as $category) {
$categoriesArray[$category['id_parent']]['subcategories'][] = $category['id_category'];
}
$n = 1;
if (isset($categoriesArray[0]) && $categoriesArray[0]['subcategories']) {
$queries = Category::computeNTreeInfos($categoriesArray, $categoriesArray[0]['subcategories'][0], $n);
// update by batch of 5000 categories
$chunks = array_chunk($queries, 5000);
foreach ($chunks as $chunk) {
$sqlChunk = array_map(function ($value) { return '(' . rtrim(implode(',', $value)) . ')'; }, $chunk);
Db::getInstance()->execute('INSERT INTO `' . _DB_PREFIX_ . 'category` (id_category, nleft, nright)
VALUES ' . rtrim(implode(',', $sqlChunk), ',') . '
ON DUPLICATE KEY UPDATE nleft=VALUES(nleft), nright=VALUES(nright)');
}
}
}
protected static function computeNTreeInfos(&$categories, $idCategory, &$n)
{
$queries = array();
$left = $n++;
if (isset($categories[(int) $idCategory]['subcategories'])) {
foreach ($categories[(int) $idCategory]['subcategories'] as $idSubcategory) {
$queries = array_merge($queries, Category::computeNTreeInfos($categories, (int) $idSubcategory, $n));
}
}
$right = (int) $n++;
$queries[] = array($idCategory, $left, $right);
return $queries;
}
It's the last function which I am struggling with and that part was my question.
Thanks!
Paul

I will assume your goal is to have a dictionary that is a <int, List<ComputeNTree>> pair, as what I was able to understand by your code in:
dict.Add(1, new ComputeNTree(12, 1, 0));
dict.Add(1, new ComputeNTree(14, 2, 0));
dict.Add(2, new ComputeNTree(16, 4, 0));
In this sense, you should use the Key as an accessor to a list.
Initialize the dictionary with the int keys you need. You will also have to create code to check whether a key exists or not. Add a new key and remove a key if that functionality is necessary.
In order to add new elements to your list in a certain dictionary pair. Retrieve the List from the pair. As simply as
var list = dict[key];
From there you can operate as you normally would with C# lists.
list.add(new ComputeNTree(16,4,0);
So instead of dict.Add() you will need to implement these methods. Checking if the key exists and has an initialized list and go from there.

Related

How to display/output/manipulate the contents of Dictionary?

I need to display the List of a Dictionary based on the int key I've entered in the console and throw an error message if it out of the given bounds of inputs in the console as well.
public class Channel
{
const string displayChannel = "{0},{1},{2}";
private Dictionary <int, string> ChannelLookup =
new Dictionary <int, string> ()
{
{1, new List<string>{"[A]","[B]","[C]"}},
{2, new List<string>{"[A]"}
};
public string channelDisplay (int val)
{
if (!ChannelLookup.ContainsKey(val))
{
// throw exception
}
else
{
string display = string.Format(displayChannel, ChannelLookup[val]);
return display;
}
}
}
System.Format.Exception:'Index(zerobased) must be greater than or
equal to zero and less than the size of the argument list.
Just for completeness: Sometimes I use Aggregate instead of Join, since it gives you more control.
var values = ChannelLookup[val].Values;
var display = values.Aggregate((a, b) => $"{a}, {b}");
To use the Linq function Aggregate, you need to add System.Linq to your using directives.
You have to display arbitrary number of items (say, 3 - "[A]", "[B]", "[C]" or just 1 - "A"); let's Join them instead of using Format
public class Channel {
private Dictionary <int, List<string>> ChannelLookup =
new Dictionary <int, List<string>> () {
{1, new List<string>() {"[A]", "[B]", "[C]"} },
{2, new List<string>() {"[A]"} },
};
public string channelDisplay (int key) {
if (ChannelLookup.TryGetValue(key, out var items))
return string.Join(",", items);
else
throw new ArgumentException($"{nameof(key)} = {key} not found", nameof(key));
}
}
Or even
public string channelDisplay(int key) => ChannelLookup.TryGetValue(key, out var items)
? string.Join(",", items)
: throw new ArgumentException($"{nameof(key)} = {key} not found", nameof(key));
.NET doesn't let you have unused format parameters, i.e. you can't do this
string.Format("{0},{1}", "first value") without also providing a value for {1}.
Your best bet here is probably string.Join. string.Join will concatenate the values you provide to it, placing the specified delimiter between each value.
See the docs here
public class Channel
{
private Dictionary <int, string> ChannelLookup =
new Dictionary <int, string> ()
{
{1, new List<string>{"[A]","[B]","[C]"}},
{2, new List<string>{"[A]"}
};
public string channelDisplay (int val)
{
if(!ChannelLookup.ContainsKey(val))
{
// throw exception
}
else
{
string display = string.Join(",", ChannelLookup[val]);
return display;
}
}
}

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();
}
}

Adding a value to a specific location in a list of queues

Queues:
public class Queue
{
public Queue() { }
public process Front() { return this.q_list.ElementAt(0); }
public int Size { get { return this.q_list.Count; } }
public bool IsEmpty { get { return this.q_list.Count <= 0 ? true : false; } }
public void Enqueue(process proc) { this.q_list.Add(proc); }
public void Dequeue() { this.q_list.RemoveAt(0); }
public List<process> q_list = new List<process>();
};
Creation of a list:
List<Queue> rr_list = new List<Queue>();
The process struct:
public class process
{
public int Proc_a;
public int Proc_b;
public int Proc_Index;
};
Let's say I want to add a process to the list at a specific location depending on the value of Proc_Index. How can I do that? Let's also assume the list is initially empty.
process proc = new process{
Proc_a = 1,
Proc_b = 2,
Proc_Index = 4 };
I want to add that to a queue that is in the list located at index 4.
Is this possible?
I've tried:
rr_list[proc.Proc_Index].Enqueue(proc);
But it says there's an issue with index not being found or something.
The only thing I can thing of is initializing the list by adding empty queues for up to 20 indexes, but I don't know if there's a better way.
You should use a System.Collections.Generic.Queue instead of writing your own. Use a System.Collections.Generic.Dictionary if you want key-value lookup.
var rr_list = new Dictionary<int, Queue<process>>();
process proc = new process{
Proc_a = 1,
Proc_b = 2,
Proc_Index = 4 };
rr_list[proc.Proc_Index].Enqueue(proc);
You may want to use a dictionary instead of a list.
var rr_list = new Dictionary<int, Queue>();
Then have an addprocess function as such
function void AddProcess(proccess proc){
if(rr_list.ContainsKey(proc.Proc_Index){
rr_list[proc.Proc_Index].Enqueue(proc);
} else {
rr_list[proc.Proc_Index] = (new Queue()).Enqueue(proc);
}
}
A list is usually supposed to have no holes, so if you were to add an element at index 4 to an empty list, this would make indexes 0 to 3 contain null.
Now, you can do it like that. You could check if the length is bigger than the requested index, and if not, keep adding null values until it is. Then the index would exist, and you could assign something to it:
static void EnsureLength<T> (List<T> list, int index)
{
while (list.Count <= index)
list.Add(default(T));
}
Then you could use it like this:
List<int?> list = new List<int?>();
EnsureLength(list, 3);
list[3] = 123;
A possibly better way would be to simply use a Dictionary, especially if you know that you will have holes. So you would just have a Dictionary<int, T>:
Dictionary<int, int?> dict = new Dictionary<int, int?>();
dict[3] = 123;

Custom Sorting based on predefined keys

I need to sort an employee list based on predefined uniqueIds.
In simple words, consider a list of employee Ids 1 to 10 in random order.
I have a predefined rule that says order employee objects in 2, 8, 1, 4, 6 And if any employee UId is not in range [1,10] put them at the end of list...(any order).
I wrote following code using IComparer<Employee>.
public class Employee
{
public int UId { get; set; }
public string Name { get; set; }
}
class Comparision : IComparer<Employee>
{
List<int> referenceKeys = new List<int> { 2, 8, 1, 4, 6 };
public int Compare(Employee thisOne, Employee otherOne)
{
var otherIndex = referenceKeys.IndexOf(otherOne.UId);
var thisIndex = referenceKeys.IndexOf(thisOne.UId);
if (thisIndex > otherIndex)
{
return 1;
}
else if (thisIndex < otherIndex)
{
return -1;
}
else
{
//if uid not found in reference list treat both employee obj as equal
return 0;
}
}
}
class CustomSorting
{
public static
List<Employee> employees = new List<Employee>
{
new Employee{UId=1, Name="Ram"},
new Employee{UId=2 , Name="Shyam"},
new Employee{UId=3 , Name="Krishna"},
new Employee{UId=4 , Name="Gopal"},
new Employee{UId=5 , Name="Yadav"},
new Employee{UId=6 , Name="Vishnu"},
new Employee{UId=7 , Name="Hari"},
new Employee{UId=8 , Name="Kanha"},
};
void sort()
{
employees.Sort(new Comparision());
}
static void Main()
{
new CustomSorting().sort();
}
}
I have been able to sort the list, with following result-
(5, 7, 3), 2, 8, 1, 4, 6 ==> 5, 7, 3 are not listed in reference key, so should appear in last, any order..
But items not found in my reference keys, are sorted first. I need to put them at the end.
For such a scenario, is IComparer, best way to go for ?
var otherIndex = referenceKeys.IndexOf(otherOne.UId); will return -1 if the item isn't found, which will be less than any found value.
You want all not found items to be greater than any found value, so just add:
if(otherIndex == -1) otherIndex = int.MaxValue;
if(thisIndex == -1) thisIndex = int.MaxValue;
On a side note, you can simplify the remainder of the method by just using:
return thisIndex.CompareTo(otherIndex);

Categories