Custom Ordering in C# - c#

There is a list of Package item is sorted by GUID, but I need to order them as follows
KK %, AB, AB art, DD %, FV, ER, PP and WW
I have implemented as follows, but I wonder is there a better way of doing it?
List<PackageType> list = new List<PackageType> (8);
foreach (var a in mail.Package)
{
if (a.Name == "KK %")
list[0] = a;
else if (a.Name == "AB art")
list[1] = a;
else if (a.Name == "AB")
list[2] = a;
else if (a.Name == "DD %")
list[3] = a;
else if (a.Name == "FV")
list[4] = a;
else if (a.Name == "ER")
list[5] = a;
else if (a.Name == "PP")
list[6] = a;
else if (a.Name == "WW")
list[7] = a;
}

You can get this down to two lines (one for array definition, one for ordering):
var PackageOrder = new[] { "KK %", "AB", "AB art", "DD %", "FV", "ER", "PP", "WW"};
//...
var list = mail.Package.OrderBy(p => Array.IndexOf(PackageOrder, p.Name)).ToList();
But we can do even better.
The code so far either requires several O(n) lookups into the reference array, or switching to a Dictionary<string,int>, which is O(1) for each lookup for a value of 1 that might be disproportionate to the task. Each package item may need several of these lookups over the course of a sort operation, which means this might be less efficient than you want.
We can get around that like this:
private static string[] Names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };
//...
var list = mail.Package.
Select(p => new {Package = p, Index = Array.IndexOf(Names, p.Name)}).
OrderBy(p => p.Index).
Select(p => p.Package).ToList();
This guarantees only one lookup per package over the course of the sort. The idea is to first create a projection of the original data that also includes an index, then sort by the index, and finally project back to just the original data. Now the only question is whether to use an array or dictionary, which mainly depends on the size of the reference array (for this size data stick with the array, for more than about 15 items, switch to the dictionary; but it varies depending on the GetHashCode() performance of your type).
Of course, there's also YAGNI to consider. For large sets this will typically be much better, but for small data it might not be worth it, or if the data happens to be sorted in a certain lucky ways it can make things slower. It can also make things slower if your are more constrained by memory pressure than cpu time (common on web servers). But in the general sense, it's a step in the right direction.
Finally, I question the need for an actual List<T> here. Just change the declaration to var and remove the .ToList() at the end. Wait to call ToList() or ToArray() until you absolutely need it, and work with the simple IEnumerable<T> until then. This can often greatly improve performance.
In this case (and for reference, I added this paragraph later on), it seems like you only have eight items total, meaning the extra code isn't really saving you anything. With that in mind, I'd just stick with the two-line solution at the top of this answer (when performance doesn't matter, go for less or simpler code).

// List<PackageType> list = ...;
var names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };
var sortedList = list.OrderBy(packageType => Array.IndexOf(names, packageType.Name));
Here's a longer version of the above that explains in more detail what's going on:
// this array contains all recognized keys in the desired order;
private static string[] Names = new[] { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };
// this helper method will return the index of a `PackageType`'s `Name`
// in the above array, and thus a key by which you can sort `PackageType`s.
static int GetSortingKey(PackageType packageType)
{
var sortingKey = Array.IndexOf(Names, packageType.Name);
if (sortingKey == -1) throw new KeyNotFoundException();
return sortingKey;
}
// this is how you could then sort your `PackageType` objects:
// List<PackageType> list = ...;
IEnumerable<PackageType> sortedList = list.OrderBy(GetSortingKey);

Updated version of #stakx answer. Well I think this should be better solution, if these values are fixed, also can be used elsewhere. Each value in enum has its int value, by which they can be ordered.
public enum Names
{
KK, //0
AB, //1
BC, //2
DD, //3
FV, //4
ER, //5
PP, //6
WW //7
}
var packageList = list.OrderBy(p => p.Name);
UPDATE
Use example for enum in class
public class Package
{
public Names Name { get; set; }
}
If there is need to get string value of this enum, then just use this
Package package - Package class variable
package.Name.ToString();
If you need whole list of enum names (enum key names), you can use Enum class method:
Enum.GetNames(Type enumType) which returns string array with all defined enum key names.
Enum.GetNames(typeof(Names))

An alternative.. (I haven't compiled it)
var indexPositions = new Dictionary<string, int> {
{ "KK", 0 },
{ "AB", 1 },
{ "BC", 2 },
{ "DD", 3 },
{ "FV", 4 },
{ "ER", 5 },
{ "PP", 6 },
{ "WW", 7 }
}
foreach (var package in mail.Package)
{ // access position
int index;
if (!indexPositions.TryGetValue(a.Name, out index)) {
throw new KeyNotFoundException()
}
list[index] = package;
}

In addition to the usual OrderBy Array.IndexOf method, the list can also sorted in-place:
string[] order = { "KK", "AB", "BC", "DD", "FV", "ER", "PP", "WW" };
list.Sort((a, b) => Array.IndexOf(order, a.Name).CompareTo(Array.IndexOf(order, b.Name)));
A bit more advanced O(n)ish alternative:
var lookup = list.ToLookup(x => x.Name);
list = order.SelectMany(x => lookup[x]).ToList();

Related

c# : avoid branching while iterating on a dictionary

I have the below code in which i branch for each sample in a dictionary , is there a way either by using LINQ or any other method in which i can avoid branching -> may be a functional approach
Dictionary<string, int> samples = new Dictionary<string, int>()
{
{"a", 1},
{"aa", 2},
{"b", 1},
{"bb", 3}
};
foreach (var sample in samples)
{
if (sample.Value ==)
{
Console.WriteLine("sample passed");
}
else if (sample.Value == 2)
{
Console.WriteLine("sample isolated");
}
else if (sample.Value == 3)
{
Console.WriteLine("sample biased");
}
}
UPD
What if i have other type of comprasion:
foreach (var sample in samples)
{
if (sample.Value <= 1)
{
Console.WriteLine("sample passed");
}
else if (sample.Value <= 2)
{
Console.WriteLine("sample isolated");
}
else if (sample.Value <= 3)
{
Console.WriteLine("sample biased");
}
}
One option would be to create a list of Actions that you wish to perform, then execute them based on the index. This way your methods can be quite varied. If you need to perform very similar actions for each option, then storing a list of values would be a better than storing Actions.
List<Action> functions = new List<Action>();
functions.Add(() => Console.WriteLine("sample passed"));
functions.Add(() => Console.WriteLine("sample isolated"));
functions.Add(() => Console.WriteLine("sample biased"));
foreach (var sample in samples)
{
Action actionToExecute = functions[sample.Value - 1];
actionToExectute();
}
If you wanted to use a dictionary as your comment implies:
Dictionary<int, Action> functions = new Dictionary<int, Action>();
functions.Add(1, () => Console.WriteLine("sample passed"));
functions.Add(2, () => Console.WriteLine("sample isolated"));
functions.Add(3, () => Console.WriteLine("sample biased"));
foreach (var sample in samples)
{
Action actionToExecute = functions[sample.Value];
actionToExectute();
}
For this concrete case you can introduce another map(Dictionary or an array, as I did):
Dictionary<string, int> samples = new Dictionary<string, int>()
{
{"a", 1},
{"aa", 2},
{"b", 1},
{"bb", 3}
};
var map = new []
{
"sample passed",
"sample isolated",
"sample biased"
};
foreach (var sample in samples)
{
Console.WriteLine(map[sample.Value - 1]);
}
As for actual code it highly depends on usecases and how you want to handle faulty situations.
UPD
It seems that if you will be using dictionary for your map there still will be some branching, but if you will not have misses branch prediction should take care of it.
So you have a Dictionary<string, int>. Every item in the dictionary is a KeyValuePair<string, int>. I assume that the string is the name of the sample (identifier), and the int is a number that says something about the sample:
if the number equals 0 or 1, the sample is qualified as Passed;
if the number equals 2, then you call it Isolated
if the number equals 3, then you call it Biased.
All higher numbers are not interesting for you.
You want to group the samples in Passed / Isolated / Biased samples.
Whenever you have a sequence of similar items and you want to make groups of items, where every element has something in common with the other elements in the group, consider using one of the overloads of Enumerable.GroupBy
Let's first define an enum to hold your qualifications, and a method that converts the integer value of the sample into the enum:
enum SampleQualification
{
Passed,
Isolated,
Biased,
}
SampleQualification FromNumber(int number)
{
switch (number)
{
case 2:
return SampleQualification.Isolated;
case 3:
return SampleQualification.Biased;
default:
return SampleQualification.Passed;
}
}
Ok, so you have your dictionary of samples, where every key is a name of the sample and the value is a number that can be converted to a SampleQualification.
Dictionary<string, int> samples = ...
var qualifiedSamples = samples // implements IEnumerable<KeyValuePair<string, int>>
// keep only samples with Value 0..3
.Where(sample => 0 <= sample.Value && sample.Value <= 3)
// Decide where the sample is Passed / Isolated / Biased
.Select(sample => new
{
Qualification = FromNumber(sample.Value)
Name = sample.Key, // the name of the sample
Number = sample.Value,
})
// Make groups of Samples with same Qualification:
.GroupBy(
// KeySelector: make groups with same qualification:
sample => sample.Qualification,
// ResultSelector: take the qualification, and all samples with this qualification
// to make one new:
(qualification, samplesWithThisQualification) => new
{
Qualification = qualification,
Samples = samplesWithThisQualification.Select(sample => new
{
Name = sample.Name,
Number = sample.Number,
})
.ToList(),
});
The result is a sequence of items. Where every item has a property Qualification, which holds Passed / Isolated / Biased. Every item also has a list of samples that have this qualification.
// Process Result
foreach (var qualifiedSample in qualifiedSamples)
{
Console.WriteLine("All samples with qualification " + qualifiedSample.Qualification);
foreach (var sample in qualifiedSample.Samples)
{
Console.WriteLine({0} - {1}, sample.Name, sample.Value);
}
}

array/list management questionon c#, total noob

i am a total noob when it comes to c#
i migrated to a new platform which uses c#, trying to migrate trading rules to new design.
i need to do this for algorithm
here is the question
i have keys array a,b,c,d
i have value set1 3,8,9,10
another value set2 77,89,100,76
these values are related to each other , (a) has values 3,77 and so on
what i need is , i need to filter with value set 2 , for example only values more than 80 then (probably create a new list with the remaining rows) , from the remaining list i need to get the keyname with highest set1 value
i tried it with this probably very bad way,
Array.Sort on one dimensional array value set1, take value[3]
- if this equals 3 then (if value set1 >80 valuefound else take value[2] and repeat
can you show me an easier way , please take my inexperience into account and include as more informatioan and code as possible
You should really start learning more about Linq. C# has very powerful functional-like features that you can do these things really easily with. It's actually really fun :)
Basically, this code does what you want with 3 lines.
var set1 = new[] {3, 8, 9, 10};
var set2 = new[] {77, 89, 100, 76};
var maxFromSet1 = set1
.Zip(set2, (fromSet1, fromSet2) => new {FromSet1 = fromSet1, FromSet2 = fromSet2}) //Match the sets to one another
.Where(zipped => zipped.FromSet2 > 80) // Filter by value
.Max(zipped => zipped.FromSet1); //Gets max
Another way of doing what you want would be to use a Dictionary which holds keys and values together instead of having them in different arrays.
Dictionary<string, int[]> dic = new Dictionary<string, int[]>()
{
{ "a", new[] { 3, 77 } },
{ "b", new[] { 8, 89 } },
{ "c", new[] { 9, 100 } },
{ "d", new[] { 10, 76 } }
};
Then using LINQ you can retrieve the key really easily
string key = dic.Where(x => x.Value[1] > 80) // Filter by second value
.OrderByDescending(x => x.Value[0]) // Order by first value
.First() // Get the max value
.Key; // Get the matching key

C# Linq question

I am desperately trying to understand linq and now I have a concrete example of what I want to do (and fail):
Console.WriteLine("{0}", (from myaddresses[x].PostalNr where x => myaddresses[x].SortType == "110" ))
myaddress is a dictionary of OneAddress objects (my own object) and that object contains the properties SortType and PostalNr.
I thought I didn't need a loop to do the above, but when the above is rewritten to work it might only take the first hit it gets or?
The questions I want to perform is:
For each entry in the dictionary that has SortType set to 110, print out it's postal number.
Below is a step-by-step walkthrough of one approach to this.
To setup the sample data (based on your question) we have the OneAddress class:
class OneAddress
{
public string PostalNr { get; set; }
public string SortType { get; set; }
}
This is in a Dictionary so we then have:
var myAddresses = new Dictionary<int, OneAddress>();
myAddresses.Add(1, new OneAddress() { PostalNr = "123", SortType = "101" });
myAddresses.Add(2, new OneAddress() { PostalNr = "124", SortType = "110" });
myAddresses.Add(3, new OneAddress() { PostalNr = "125", SortType = "101" });
myAddresses.Add(4, new OneAddress() { PostalNr = "126", SortType = "110" });
myAddresses.Add(5, new OneAddress() { PostalNr = "127", SortType = "110" });
First, a basic Linq query to get all dictionary entries:
var results = from a in myAddresses
select a;
This returns an IEnumerable<T> where T is a KeyValuePair<int, OneAddress> (same as our Dictionary).
As stated, you only want the PostalNr not the KeyValuePair so we change our query to:
var results = from a in myAddresses
select a.Value.PostalNr;
The Value contains the OneAddress object, and we get only the property we need (in an IEnumerable<T>).
However this is for all items in the collection; we can now add our filter.
var results = from a in myAddresses
where a.Value.SortType == "110"
select a.Value.PostalNr;
Now we're getting the PostalNr for any OneAddress in the Dictionary where SortType is "110", and that only leaves printing the results to the console screen.
As highlighted in other answers, Console.WriteLine() doesn't work with an enumerable list of strings, so we can enumerate the items with:
foreach (string postalNr in results)
{
Console.WriteLine(postalNr);
}
Or (if we're using System.Collections.Generic) we can do it on one line with:
results.ToList().ForEach(p => Console.WriteLine(p));
The LINQ query you're looking for takes the form:-
from <item> in <collection> where <item.someclause> select <item.targetfield>
That will return an IEnumerable<targetfieldtype> which Console.WriteLine doesn't handle.
If the type is a string as it is here you can then apply string.join() to concatenate it into a single string.
Like this:-
Console.WriteLine
(
string.Join
(
"\r\n",
from address in Addresses
where address.SortType=="110"
select address.PostalNr
)
);
I can't add a comment, because my reputation is to low.
Your problem is not linq itself but how you use it. What you are doing is printing one line. This line will contain the value of the ToString call on a list/collection/linq result. I think it might help you if you don't try to put everything in one line. The error should be obvious if you extract the linq query from the write line.
I don't have an IDE here at my workplace, so I can't confirm my code works, but what you want to do is basically:
var addresses = (from myaddresses[x].PostalNr where x => myaddresses[x].SortType == "110" )
addresses.each(Console.WriteLine)

How to remove Duplicates from two List except few elements which may be duplicate also?

i have two lists having few elements in common, i want to remove duplicates events except few as described below..and the order of the string must be same and both list may not contain same no of elements?
list A: List B
ASCB ASCB
test1 test1
test2 test5
test3 test3
test4 test6
Arinc Arinc
testA testC
testB testB
testC
tesctD
now i want to remove all common elements in two list except elements ASCB, ARINC.. how to do that can any one help me in that...
I would just store the special values ( ASCB, ARINC, ect ) in their own list so I can use Except to get the difference between the two sets. You can add the special values in afterwards.
List<string> except = ListA.Except(ListB).Concat(listB.Except(ListA)).Concat(SpecialValues).ToList();
You have to call except twice because first we get items in A that are not in B. Then we add items that are in B but not in A. Finally we add the special values (I'm assuming SpecialValues is a collection with the strings you don't want removed).
You'd have to test performance as I suspect it's not the most efficient.
List<string> wordstoKeep = new List<string>() { "ASCB", "Arinc" };
foreach (string str in listB)
{
int index = listA.FindIndex(x => x.Equals(str, StringComparison.OrdinalIgnoreCase));
if (index >= 0)
{
if (!wordstoKeep.Any(x => x.Equals(str, StringComparison.OrdinalIgnoreCase)))
listA.RemoveAt(index);
}
else
listA.Add(str);
}
var listA = new List<string>{"ASCB","test1","test2"};
var listB = new List<string>{"ASCB","test1","test2"};
var combinedList = listA.Where(a => a.Contains("test"))
.Concat(listB.Where(b => b.Contains("test")))
.Distinct().Dump();
outputs 'test1', 'test2'
your filter conditions are contained in your Where clause.
Where can be whatever condition you want to filter by:
Where(a => a != "ASCB" or whatever...
Concat joins the two lists. Then call Distinct() to get unique entries.
Going off the requirement that order must be the same
if(B.Count != A.Count)
return;
List<String> reserved = new List<string>{ "ARCB", "ARINC" };
for (int i = A.Count -1; i >= 0; i--)
{
if (!reserved.Contains(A[i].ToUpper()) && A[i] == B[i])
{
A.RemoveAt(i);
B.RemoveAt(i);
}
}
This works:
var listA = new List<string>()
{
"ASCB",
"test1",
"test2",
"test3",
"test4",
"Arinc",
"testA",
"testB"
};
var listB = new List<string>()
{
"ASCB",
"test1",
"test5",
"test3",
"test6",
"Arinc",
"testC",
"testB"
};
var dontRemoveThese = new List<string>(){"ASCB", "Arinc"};
var listToRemove = new List<string>();
foreach (var str in listA)
if (listB.Contains(str))
listToRemove.Add(str);
foreach (var str in listToRemove){
if (dontRemoveThese.contains(str))
continue;
listA.Remove(str);
listB.Remove(str);
}
I like this solution because you can see what happens. I'd rather have 10 lines of code where it's obvious what happens than 1-3 lines of obscure magic.

Creating a random name generator. How do I accomplish this?

I'm trying to grab a single item from each of the Lists here, and combine them to make a unique name. This is just for kicks. :)
Here are the lists:
List<string> FirstNames = new List<string>()
{
"Sergio",
"Daniel",
"Carolina",
"David",
"Reina",
"Saul",
"Bernard",
"Danny",
"Dimas",
"Yuri",
"Ivan",
"Laura"
};
List<string> LastNamesA = new List<string>()
{
"Tapia",
"Gutierrez",
"Rueda",
"Galviz",
"Yuli",
"Rivera",
"Mamami",
"Saucedo",
"Dominguez",
"Escobar",
"Martin",
"Crespo"
};
List<string> LastNamesB = new List<string>()
{
"Johnson",
"Williams",
"Jones",
"Brown",
"David",
"Miller",
"Wilson",
"Anderson",
"Thomas",
"Jackson",
"White",
"Robinson"
};
I know I get a single item via an index, and I also know that I can use the Random class to generate a random number from 0 to ListFoo.Count.
What I don't know is how to check if a random permutation has already been drawn from the collections.
I've thought about using the tuple class:
List<Tuple<int,int,int>> permutations = new List<Tuple<int,int,int>>();
But I'm having a brainfart here. ;) Any guidance? I'm not really looking for the entire code to this simple problem, just a suggestion or hint.
EDIT
Thanks to the suggestions given here, here what I've come up with. Any room for improvements?
static void Main(string[] args)
{
List<string> FirstNames = new List<string>()
{
"Sergio",
"Daniel",
"Carolina",
"David",
"Reina",
"Saul",
"Bernard",
"Danny",
"Dimas",
"Yuri",
"Ivan",
"Laura"
};
List<string> LastNamesA = new List<string>()
{
"Tapia",
"Gutierrez",
"Rueda",
"Galviz",
"Yuli",
"Rivera",
"Mamami",
"Saucedo",
"Dominguez",
"Escobar",
"Martin",
"Crespo"
};
List<string> LastNamesB = new List<string>()
{
"Johnson",
"Williams",
"Jones",
"Brown",
"David",
"Miller",
"Wilson",
"Anderson",
"Thomas",
"Jackson",
"White",
"Robinson"
};
var permutations = new List<Tuple<int, int, int>>();
List<string> generatedNames = new List<string>();
Random random = new Random();
int a, b, c;
//We want to generate 500 names.
while (permutations.Count < 500)
{
a = random.Next(0, FirstNames.Count);
b = random.Next(0, FirstNames.Count);
c = random.Next(0, FirstNames.Count);
Tuple<int, int, int> tuple = new Tuple<int, int, int>(a, b, c);
if (!permutations.Contains(tuple))
{
permutations.Add(tuple);
}
}
foreach (var tuple in permutations)
{
generatedNames.Add(string.Format("{0} {1} {2}", FirstNames[tuple.Item1],
LastNamesA[tuple.Item2],
LastNamesB[tuple.Item3])
);
}
foreach (var n in generatedNames)
{
Console.WriteLine(n);
}
Console.ReadKey();
}
You are on the right track!
Every time you generate a name, add it to your tuple list
//Create the tuple
Tuple <int, int, int> tuple = new Tuple<int, int, int>(index1, index2, index3)
if(!permutations.Contains(tuple))
{
permutations.Add(tuple);
//Do something else
}
I would think the simplest solution is to just the stuff the assembled name into a HashSet<string> which will ensure the list of created names is unique.
An alternative to the HashSet answer is to build all of the possible combinations in advance, shuffle them, then store them in a Queue, where you can retrieve them one at a time. This will avoid having to check the existing ones every time you build a new one, and will still be random.
This only works if you don't have a large set to begin with, since the work involved in creating the complete list and shuffling it would be huge for a large set of data.
It's really easy to generate them all using LINQ:
var combs =
(from first in FirstNames
from second in LastNamesA
from third in LastNamesB
select new Tuple<string, string, string>(first, second, third)).ToList();
After this, if you need to take unique elements from the list randomly, just shuffle the list and then pick them one-by-one in order.
You can use the Knuth-Fisher-Yates algorithm (that's an in-place shuffle):
Random rand = new Random();
for (int i = combs.Count - 1; i > 0; i--)
{
int n = rand.Next(i + 1);
var mem = combs[i];
combs[i] = combs[n];
combs[n] = mem;
}
I would create a HashSet<int> and store the numeric representation of the picks (eg 135 for first, third and 5th or use 010305) and then check if they are in the set.
Create a new tuple with 3 random digits
Check if permutations contains your new tuple
If not => Add new tuple to the list. If yes, start with point 1 again.

Categories

Resources