Custom Sorting based on predefined keys - c#

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

Related

C# dictionary add values array

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.

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;

Modifying Observable Collection elements efficiently

Basically I have an Observable Collection in my class and a static integer keeping track of how many elements there are in the collection. Every element in the collection has a unique ID starting from 1 up to the total number of elements.
What I want to do is, take out an element with a random ID, and then change the IDs of the succeeding elements accordingly so the IDs run continuously from 1 to the total number of elements. So for example if I have 5 elements and I remove the element with ID number 3, then I need some code that will modify the ID property of element with ID 4 and change it to 3, and modify the ID property of element with ID 5 and change it to 4, so all the IDs are in order without gaps.
I thought of doing something like this:
var matches = MyObject.MyCollection.Where((myobject) => myobject.UniqueId.Equals(ID_value_of_removed_item_plus_one))
foreach (MyDataType CollectionItem in matches)
{
MyDataType CollectionItemCopy = ColectionItem
CollectionItemCopy.UniqueId--;
MyCollection.Remove(CollectionItem);
MyCollection.Add(CollectionItemCopy);
}
But I can't help but imagine there's a more efficient way to go about doing this. I know the Observable Collection isn't a suitable choice for this kind of application but the thing is the elements are bound to a ListView so I can't use any other type of generic collection.
Is this what you want?
class Entity
{
public int Id { get; set; }
public string Name { get; set; }
}
var collection = new ObservableCollection<Entity>
{
new Entity { Id = 1, Name = "Apple" },
new Entity { Id = 2, Name = "Peach" },
new Entity { Id = 3, Name = "Plum" },
new Entity { Id = 4, Name = "Grape" },
new Entity { Id = 5, Name = "Orange" },
};
collection.CollectionChanged += (sender, args) =>
{
if (args.Action == NotifyCollectionChangedAction.Remove || args.Action == NotifyCollectionChangedAction.Replace)
{
for (var i = args.OldStartingIndex; i < collection.Count; i++)
{
collection[i].Id--;
}
}
};
collection.RemoveAt(2); // Grape.Id == 3, Orange.Id == 4
Just get all matches at once, then subtract one of the ID. There is no need to remove and add it again, since you are just adding and removing the same item. At the end, remove the original item.
var matches = MyObject.MyCollection.Where(myobject => myobject.UniqueId >= ID_value_of_removed_item_plus_one);
foreach (MyDataType CollectionItem in matches)
{
CollectionItem.UniqueId--;
}
MyCollection.Remove(itemToDelete);

System.Array does not contain definition for Add

I am creating a blackjack game and so far I have made a card class, deck class and shoe class. The card class works the deck class works, the shoe class works but I am still working on my hand class. I created a method that throws an exception if there are already MAX_CARDS cards in the hand otherwise it adds the card to the hand and increments _cardCount but for some reason on my code _hand.Add(card) says that
System.Array does not contain a definition for Add.
Any help or guidance in the right direction would be appreciated
Here's what I have for my hand class
class Hand
{
const Int32 MAX_CARDS = 12;
private Card[] _hand = new Card[MAX_CARDS];
private Int32 _cardCount = 0;
public Int32 CardCount
{
get
{
return _cardCount;
}
}
public void AddCard(Card card)
{
if (_cardCount >= MAX_CARDS)
{
throw new Exception("Cannot of more than 12 cards in a hand");
}
else
{
_hand.Add(card);
_cardCount++;
}
}
public Card GetCard(Int32 cardIndex)
{
if (cardIndex >= _cardCount)
{
throw new Exception("Invalid Entry");
}
else
{
return _hand[cardIndex];
}
}
Int32[] cardValues = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 10 };
String[] cardSymbols = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" };
private Int32 SymbolToValue(String symbol)
{
int index = Array.IndexOf(cardSymbols, symbol);
if (index != -1)
{
return cardValues[index];
}
else
{
throw new Exception("Value Not In Table");
}
}
public Int32 GetSum()
{
int value = 0;
Boolean aceflag;
for (int i = 0; i < _hand.Length; i++)
{
value += SymbolToValue(_hand[i].Value);
if (String.Equals(_hand[i].Value, "A"))
{
aceflag = true;
}
else
{
aceflag = false;
}
if (aceflag == true && value <= 21)
{
value = value + 10;
}
}
return value;
}
}
An Array's size in c# is immutable so I would suggest using a List instead.
private List<Card> _hand = new List<Card>(MAX_CARDS);
I think your code has a lot of problems, but if you really want to do it the way you're doing it, use the index to add an item:
public void AddCard(Card card)
{
if (_cardCount >= MAX_CARDS)
{
throw new Exception(string.Format("This hand already contains {0} cards, " +
"which is the maximum.", MAX_CARDS));
}
else
{
_hand[_cardCound] = card;
_cardCount++;
}
}
enter code here we create a new List with the elements in an array that already exists. We use the List constructor and pass it the array. List receives this parameter and fills its values from it.
Caution:The array element type must match the List element type or compilation will fail.
Program that copies array to List: C#
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
int[] arr = new int[3]; // New array with 3 elements
arr[0] = 2;
arr[1] = 3;
arr[2] = 5;
List<int> list = new List<int>(arr); // Copy to List
Console.WriteLine(list.Count); // 3 elements in List
}
}
Output : 3
Array is not good for your code in c# list is used array is not a good option. So use list type data. You can use list and store every type data in this object and convert it in any type and you can also get single data from list using a loop. It so easy to use it and implement so use list it good and easy for code .
First, we declare a List of int values. We create a new List of unspecified size and adds four prime numbers to it. Values are stored in the order added. There are other ways to create Lists—this is not the simplest.
using System.Collections.Generic;
class Program
{
static void Main()
{
List<int> list = new List<int>();
list.Add(2);
list.Add(3);
list.Add(5);
list.Add(7);
}
}

Append an item to an existing list

I am working with a link list. I have set my constructor to take an array named ax with a set of already defined items. I also decided to have an input box which through a BtnAddTree_Click appends the new item to the list ax. But instead of appending to the list ax it creates a whole new separate list. How can I append items to the array list ax through my AddTree function?
public ListForTrees(IEnumerable<fruit_trees> trees)
{
foreach (fruit_trees t in trees)
{
this.AddTree(t);
}
}
public void AddTree(fruit_trees new_tree)
{
fruit_trees current = first_tree;
if (count == 0)
{
first_tree = new_tree;
last_tree = new_tree;
count = 1;
}
else if (count != 0)
{
if (new_tree.tree_price <= first_tree.tree_price)
{
new_tree.next_tree = first_tree;
first_tree = new_tree;
}
else if (new_tree.tree_price >= last_tree.tree_price)
{
last_tree.next_tree = new_tree;
last_tree = new_tree;
}
else
{
while (new_tree.tree_price > current.next_tree.tree_price)
{
current = current.next_tree;
}
new_tree.next_tree = current.next_tree;
current.next_tree = new_tree;
}
count++;
}
}
}
ListForTrees mainlist = new ListForTrees();
private void BtnGo_Click(object sender, EventArgs e)
{
fruit_trees[] ax = { new fruit_trees("cherry", 48, 12.95, 3),
new fruit_trees("pine", 36, 9.95, 8),
new fruit_trees("oak", 60, 14.95, 2),
new fruit_trees("peach", 54, 19.95, 3),
new fruit_trees("pear", 36, 11.85, 2),
new fruit_trees("apple", 62, 13.45, 5)
};
mainlist = new ListForTrees(ax);
fruit_trees current = mainlist.first_tree;
while (current != null)
{
current = current.next_tree;
}
}
}
}
It doesn't seem to be creating a new separate list. I tested out the following code with yours:
public class TreeTester
{
public static void Main(string[] args)
{
var list = new ListForTrees(
new[] { new fruit_trees("tree10",10,10,10), new fruit_trees("tree2",2,2,2) });
list.AddTree( new fruit_trees("tree3",3,3,3) ); // middle
list.AddTree( new fruit_trees("tree1",1,1,1) ); // first
list.AddTree( new fruit_trees("tree50",50,50,50) ); // last
list.AddTree( new fruit_trees("tree5",5,5,5) ); // middle
Console.Write(list);
}
}
And got the following output, which seems correct.
tree1 1 1 1
tree2 2 2 2
tree3 3 3 3
tree5 5 5 5
tree10 10 10 10
tree50 50 50 50
What is the expected behavior, if this is not correct? Clearly these items are all being added to the original list, since they're present when I iterate through the list.
By the way, I also added the following ToString function to your ListForTrees class; it makes debugging easier.
public override string ToString()
{
string s = "";
for (var tree=first_tree; tree!=null; tree = tree.next_tree)
s += tree + "\n";
return s;
}
Edit: I must comment that you may find it helpful to cleanup your code a bit in trying to understand where it is going wrong. For example, your ListForTrees(fruit_trees new_tree) constructor does the same exact thing as calling Add(new_tree) would. Also, think about three cases you have in Add, under else if (count != 0) -- perhaps there's a way they could elegantly combined into one general while loop? It makes it easier to analyze, and (potentially) less error-prone.

Categories