I am writing a piece of code that to work would require an extensive amount of if/then statements. In order to eliminate the need for writing line upon line of if/then statements can I use a dictionary or a list? If so can someone direct me to a good web resource or show an instance where they have done it before?
Edit
Clarification: I have six inputs, each are to be combo boxes with a group of selections. Below is a detail of the inputs and the selections.
(Amps) 1:1 - 1:12 (12 different selections)
(Cable Size) 2:1 - 2:13 (13 different selections) Certain items in this list will be excluded by the selection of the first input.
(Cable Type) 3:1 - 3:2 (2 different selections)
(Temp Rating) 4:1 - 4:3 (3 different selections)
(System Type) 5:1 - 5:2 (2 different selections)
(Conduit Type) 6:1 - 6:2 (2 different selections)
From the above input will come two outputs which will appear in two text boxes.
(Cable Qty) 7:1 - 7:16 (16 different outputs)
(Conduit Size) 8:1 - 8:8 (8 different outputs)
I hope this serves to help and not hinder.
It looks like you're trying to map each combination of the 6 inputs (12 * 13 * 2 * 3 * 2 * 2 possibilities) to one of the (16 * 8) outputs. If that's the case, you'll still have a lot of typing to do - but moving to a collection will allow you to easily externalize the mapping. I would guess that this would probably be best suited for a database table:
Amps | CableSize | CableType | TempRating | SystemType | ConduitType | CableQty | ConduitSize
You'd put a primary key on the 6 input columns, and then just do a simple SELECT:
SELECT CableQty, ConduitSize
FROM Table
WHERE Amps = #amps AND CableSize = #cableSize...etc
To do this in quick and dirty code, arrays would work:
const int AMPS = 0; const int CABLE_SIZE = 1; const int TEMP_RATING = 2; // etc.
var mappings = new Dictionary<int[], int[]>(12 * 13 * 2 * 3 * 2 * 2);
mappings.Add(
new int[] { 1, 1, 1, 1, 1, 1 }, // inputs
new int[] { 1, 2 } //outputs
);
// repeat...a lot
var outputs = mappings.First(inputs => {
inputs[AMPS] == myAmps
&& inputs[CABLE_SIZE] == myCableSize
&& inputs[TEMP_RATING] == myTempRating
&& // etc
});
It doesn't save you much typing - though you could use for loops and the like to populate the mappings if there's some sort of logic to it - but it's a hell of a lot more readable than 6 pages of if statements (I'd probably region off or partial class loading the mappings).
might want to give some idea of what you're doing with the if/the statements. If you're just obtaining a value from a key then, yes, a dictionary probably would work.
Dictionary<string,string> map = new Dictionary<string,string>();
... populate the map with keys...
Then use it...
string value = "default value";
if (map.ContainsKey(key))
{
value = map[key];
}
I actually suggest building a object model to store your settings. This will give you an opportunity to encapsulate your logic regarding what options are available at what times. Another benefit is that your Amp[1] control can bind to your SettingsContainer.Amp[1].Value, or however it ends up.
If you are always just obtaining a simple key-value lookup, then yes a dictionary lookup can replace a long string of if-then statements. However, if sometimes your logic is more complex than a simple key-value lookup, you may have to create a hybrid of if-then statements plus dictionary lookups. Sometimes these will be combined to make one logical statement.
The only correct answer in your case is to follow exactly what your business domain dictates. If you can simplify to dictionary lookups most of the time, then use them. Don't be too rigid to choose one over the other, though. Usually business logic is too messy to fall neatly into place like that.
A dictionary look up is possible but I don't believe it is feasible with the problem you have described.
David Thomas Garcia has given a good solution to your problem. I like that solution because it makes a nice encapsulation in a business object that you could possibly reuse and I would expect it to simplify maintenance/debugging for you as well.
Have the object model expose default lists for each list of choices, then as each choice is selected, have the lower levels of choices automatically filter.
Related
I have two lists. The first one contains entries like
RB Leipzig vs SV Darmstadt 98
Hertha Berlin vs Hoffenheim
..
and in the second contains basically the same entries but could but written in different forms. For example:
Hertha BSC vs TSG Hoffenheim
RB Leipzig vs Darmstadt 98
..
and so on. Both lists represent the same sport games but they can use alternate team names and don't appear in the same order.
My goal (hehe pun) is to unify both lists to one and match the same entries and discard entries which don't appear in both lists.
I already tried to use Levensthein distance and fuzzy search.
I thought about using machine learning but have no idea how to start with that.
Would appriciate any help and ideas!
You can solve this problem using Linear Programming combined with the Levenshtein Distance you already mentioned. Linear Programming is a commonly used optimization technique for solving optimization problems, like this one. Check this link to find out an example how to use Solver Foundation in C#. This example isn't related with the specific problem you have, but is a good example how the library works.
Hints:
You need to build a matrix of distances between each pair of teams/strings between 2 lists. Let's say both lists have N elements. In i-th row of the matrix you will have N values, the j-th value will indicate the Levenshtein Distance between i-th element from the first and j-th element from the second list. Then, you need to set the constraints. The constraints would be:
The sum in each row needs to equal 1
The sum in each column equals 1
Each of the coefficient (matrix entry) needs to be either 0 or 1
I have solved the same problem a couple of months ago and this approach worked perfectly for me.
And the cost function would be the sum: `
sum(coef[i][j] * dist[i][j] for i in [1, n] and for j in [1, n])
`. You want to minimize this function, because you want the overall "distance" between the 2 sets after the mapping to be as low as possible.
You can use a BK-tree (I googled C# implementations and found two: 1, 2). Use the Levenshtein distance as the metric. Optionally, delete the all-uppercase substrings from the names in the lists in order to improve the metric (just be careful that this doesn't accidentally leave you with empty strings for names).
1. Put the names from the first list in the BK-tree
2. Look up the names from the second list in the BK-tree
a. Assign an integer token to the name pair, stored in a Map<Integer, Tuple<String, String>>
b. Replace each team name with the token
3. Sort each token pair (so [8 vs 4] becomes [4 vs 8])
4. Sort each list by its first token in the token pair,
then by the second token in the token pair (so the list
would look like [[1 vs 2], [1 vs 4], [2 vs 4]])
Now you just iterate through the two lists
int i1 = 0
int i2 = 0
while(i1 < list1.length && i2 < list2.length) {
if(list1[i1].first == list2[i2].first && list1[i1].second == list2[i2].second) {
// match
i1++
i2++
} else if(list1[i1].first < list2[i2].first) {
i1++
} else if(list1[i1].first > list2[i2].first) {
i2++
} else if(list1[i1].second < list2[i2].second {
i1++
} else {
i2++
}
}
The problem I'm trying to solve gives me a matrix like
10101
11100
11010
00101
where the rows are supposed to represented topics that a person knows; e.g. Person 1, represented by 10101, knows topics 1, 3 and 5, but not 2 or 4. I need to find the maximum number of topics that a 2-person team could know; e.g. the team that is Person 1 and 3 knows all the topics because between 10101 and 11010 there are 1s at every index.
I have an O(n^2) solution
string[] topic = new string[n];
for(int topic_i = 0; topic_i < n; topic_i++)
{
topic[topic_i] = Console.ReadLine();
}
IEnumerable<int> teamTopics =
from t1 in topic
from t2 in topic
where !Object.ReferenceEquals(t1, t2)
select t1.Zip(t2, (c1, c2) => c1 == '1' || c2 == '1').Sum(b => b ? 1 : 0);
int max = teamTopics.Max();
Console.WriteLine(max);
which is passing all the test cases it doesn't time out on. I suspect the reason it's not fast enough has to do with the time complexity rather than the overhead of the LINQ machinery. But I can't think of a better way to do it.
I thought that maybe I could map the indices of topics to the persons who know them, like
1 -> {1,2,3}
2 -> {2,3}
3 -> {1,2,4}
4 -> {3}
5 -> {1,4}
but I can't think of where to go from there.
Can you supply me with a "hint"?
Let's say we have n people and m topics.
I would argue that your algorithm is O(n^2 * m), where n is number of people, because:
from t1 in topic gets you O(n)
from t2 in topic gets you to O(n^2)
t1.Zip(t2 ... get you to O(n^2 * m)
An optimisation that I see is first to modify strings a bit:
s1 = '0101', where i-th element shows whether a person i knows 1st topic
s2 = '1111', where i-th element shows whether a person i knows 2nd topic.
etc...
Then you analyse string s1. You pick all possible pairs of 1s (O(n^2) elements) that show pairs of people that together know 1st topic. Then go pick a pair from that list and check whether they know 2nd topic as well and so on. When they don't, delete it from the list and move on to another pair.
Unfortunately this looks to be O(n^2 * m) as well, but this should be quicker in practise. For very sparse matrix, it should be close to O(n2), and for dense matrices it should find a pair pretty soon.
Thoughts:
as a speculative optimization: you could do an O(n) sweep to find the individual with the highest number of skills (largest hamming weight); note them, and stop if they have everything: pair them with anyone, it doesn't matter
you can exclude anyone without testing who only has skilled shared with the "best" individual - we already know about everything they can offer and have tested against everyone; so only test if (newSkills & ~bestSkills) != 0 - meaning: the person being tested has something that the "best" worker didn't have; this leaves m workers with complementary skills plus the "best" worker (you must include them explicitly, as the ~/!=0 test above will fail for them)
now do another O(m) sweep of possible partners - checking to see if the "most skilled" plus any other gives you all the skills (obviously stop earlier if a single member has all the skills); but either way: keep track of best combination for later reference
you can further half the time by only considering the triangle, not the square - meaning: you compare row 0 to rows 1-(m-1), but row 1 to rows 2-(m-1), row 5 to 6-(m-1), etc
you can significantly improve things by using integer bit math along with an efficient "hamming weight" algorithm (to count the set bits) rather than strings and summing
get rid of the LINQ
short-circuit if you get all ones (compare to ~((~0)<<k), where k is the number of bits being tested for)
remember to compare any result to the "best" combination we found against the most skilled worker
This is still O(n) + O(m^2) where m <= n is the number of people with skills different to the most skilled worker
Pathological but technically correct answer:
insert a Thread.Sleep(FourYears) - all solutions are now essentially O(1)
Your solution is asymptotically as efficient as it gets, because you need to examine all pairs to arrive at the maximum. You can make your code more efficient by replacing strings with BitArray objects, like this:
var topic = new List<BitArray>();
string line;
while ((line = Console.ReadLine()) != null) {
topic.Add(new BitArray(line.Select(c => c=='1').ToArray()));
}
var res =
(from t1 in topic
from t2 in topic
select t1.Or(t2).Count).Max();
Console.WriteLine(res);
Demo.
I was digging around in .NET's implementation of Dictionaries, and found one function that I'm curious about: HashHelpers.GetPrime.
Most of what it does is quite straightforward, it looks for a prime number above some minimum which is passed to it as a parameter, apparently for the specific purpose of being used as a number of buckets in a hashtable-like structure. But there's one mysterious part:
if (HashHelpers.IsPrime(j) && (j - 1) % 101 != 0)
{
return j;
}
What is the purpose of the (j - 1) % 101 != 0 check? i.e. Why do we apparently want to avoid having a number of buckets which is 1 more than a multiple of 101?
The comments explain it pretty well:
‘InitHash’ is basically an implementation of classic DoubleHashing
(see http://en.wikipedia.org/wiki/Double_hashing)
1) The only ‘correctness’ requirement is that the ‘increment’ used to
probe a. Be non-zero b. Be relatively prime to the table size
‘hashSize’. (This is needed to insure you probe all entries in the
table before you ‘wrap’ and visit entries already probed)
2) Because
we choose table sizes to be primes, we just need to insure that the
increment is 0 < incr < hashSize
Thus this function would work: Incr = 1 + (seed % (hashSize-1))
While this works well for ‘uniformly distributed’ keys, in practice,
non-uniformity is common. In particular in practice we can see
‘mostly sequential’ where you get long clusters of keys that ‘pack’.
To avoid bad behavior you want it to be the case that the increment is
‘large’ even for ‘small’ values (because small values tend to happen
more in practice). Thus we multiply ‘seed’ by a number that will make
these small values bigger (and not hurt large values). We picked
HashPrime (101) because it was prime, and if ‘hashSize-1’ is not a
multiple of HashPrime (enforced in GetPrime), then incr has the
potential of being every value from 1 to hashSize-1. The choice was
largely arbitrary.
I have a linq query which is not ordered the way I would like.
The Query:
return (from obj in context.table_orders
orderby obj.order_no
select obj.order_no.ToString() + '-' + obj.order_description).ToList<string>();
What happens is that my records are ordered alphabeticaly, is there a Linq keyword I can use so my records are ordered correctly (so order 30 comes before order 100)?
I want the result to be a list of string since this is used to populate a ComboBox.
Also some of the 'order_no' in the DB are like '2.10' and '9.1.1'.
What happens is that my records are ordered alphabeticaly, is there a Linq keyword I can use so my records are
ordered correctly (so order #30 comes before order #100)?
If I would get a cente everytime someone asks this I would be rich.
Yes, there is - simple answer: ORDER A NUMBER NOT A STRING.
so order #30 comes before order #100)
But #30 comes AFTER #100 for the simple reason that they ARE sorted alphabetically becase THEY ARE STRINGS.
Parse the string, convert the number to - well - a number, and order by it.
WHOEVER had the idea that order_no should be a string WITHOUT A FIXED LENGH (like 00030) should - well - ;) get a basic education on database modelling. I really like things like invoice numbers etc. to be strings (they are NOT numbers) but keeping them in (a) a defiable pattern and (b) checksummed (so that data entry errors are easily catched) should be basics ;)
This is the kind of issue you get with junior people defining databases and data models and not thinking about the consequences.
You are in for some pain - parse the string, order by parsing result.
If obj.order_no is a string, then convert it to a number for sorting
orderby Int32.Parse(obj.order_no)
or
orderby Decimal.Parse(obj.order_no)
of cause this works only if the string represents a valid number.
If order_no is not a valid number (e.g. "17.7-8A") then write a function that formats it to contain right aligned numbers, like "00017.007-0008A" and sort using this function
orderby FormatOrderNo(obj.order_no)
UPDATE
Since you are working with EF you cannot call this function in the EF part of your query. Convert the EF-result to an IEnumerable<T> and perform the sorting using LINQ-To-Objects
return (from obj in context.table_orders select ...)
.AsEnumerable()
.OrderBy(obj => FormatOrderNo(obj.order_no))
.ToList();
Based on what you said - that the numbers are not really numbers but rather custom sequence identifiers (i.e. you don't even know what level of depth you get) I would suggest implementing a custom comparer.
If you do this you can define what you exaclty want - and that is I believe something along these lines:
split the string on .
compare the sequences up to and including the array.length of the shorter sequence
if there was a shorter sequence and by now there is a tie, pick the shorter before the longer (i.e. 2.1 before 2.1.1)
if both sequences had the same length, by the end of the comparison you should know which one is 'bigger'
solved.
If you need inspiration on IComparer implementation an example below:
http://zootfroot.blogspot.co.uk/2009/09/natural-sort-compare-with-linq-orderby.html
I would either change the datatype if possible or add it as another firld if applicable.
If that is a no-go you can look at the solutions mentioned by others in this question but beware - The .ToList() option is nice for small tables if you are pulling everything from them but getting used to it will eventually give you a world of pain. You don't wanna get everything in the long run, either use a Where or top critera.
The other solutions are nice but to complex for my taste to accomplish the task.
You could go with shooting sql directly through LinqToSql. http://msdn.microsoft.com/en-us/library/bb399403.aspx
In the sql you are free to convert and sort however you like. Some think this is a great idea and some will tell you it it bad. You loose strong typing and gain performance. You will have to know WHY you take this kinds of decision, that is the most important thing imho.
Since nobody came up with a custom orderby function translatable into SQL, I went for the IComparer function like so:
public class OrderComparer<T> : IComparer<string>
{
#region IComparer<string> Members
public int Compare(string x, string y)
{
return GetOrderableValue(x.Split('-').First()).CompareTo(GetOrderableValue(y.Split('-').First()));
}
#endregion
private int GetOrderableValue(string value)
{
string[] splitValue = value.Split('.');
int orderableValue = 0;
if (splitValue.Length.Equals(1))
orderableValue = int.Parse(splitValue[0]) * 1000;
else if (splitValue.Length.Equals(2))
orderableValue = int.Parse(splitValue[0]) * 1000 + int.Parse(splitValue[1]) * 100;
else if (splitValue.Length.Equals(3))
orderableValue = int.Parse(splitValue[0]) * 1000 + int.Parse(splitValue[1]) * 100 + int.Parse(splitValue[2]) * 10;
else
orderableValue = int.Parse(splitValue[0]) * 1000 + int.Parse(splitValue[1]) * 100 + int.Parse(splitValue[2]) * 10 + int.Parse(splitValue[3]);
return orderableValue;
}
}
The values have a maximum of 4 levels.
Anyone has a recommandation?
I've had quite a bit of experience with programming (three semesters teaching VBasic, C++, and Java), and now I'm in college and I'm taking a C# class, which is quite boring (the teacher knows less than I do).
Anyways, for one of our exercises, we're creating a number guessing/lottery game. It works kind of like this:
User inputs three integers from 1-4 and clicks submit (I have them storing into an array)
Program generates three numbers from 1-4 (also in an array)
Function that checks matching runs and checks the two arrays
If all three match in order (i.e. 1,2,3 = 1,2,3 and NOT 1,2,3 = 1,3,2), matching = 4
If all three match NOT in order, matching = 3
If only two match, matching = 2
I want to make sure that only one match counts as one (i.e. [1,1,2][1,2,3] only gives one match to the user.
If only one matches, matching = 1
If no matches, matching stays at 0 (it's instantiated at submit_click)
I've got all of the code and GUI working except for the matching logic. I know I could do it with a LARGE amount of if statements, and I know cases would probably work, but I'm not as experienced with cases.
I'm not expecting my 'homework' to be done here, but I just want to know what method would be most effective to get this to correctly work (if it's easier to exclude the one match per item, then that's fine), and to possibly see some working code.
Thanks!
EDIT
I apologize if I come across as arrogant, I didn't mean to come across as a know-it-all (I definitely do not).
I have NOT taught classes, I've just taken classes from a teacher who's primarily a programming in and I'm at a community college and my professor isn't primarily a programming teacher.
I didn't take time to write a ton of if statements because I know that it would just get shot down as ineffective. I currently don't have the resources to test the answers, but as soon as I can I'll check them out and post back.
Again, I apologize for coming across as rude and arrogant, and I appreciate your answers more than you know.
Thanks again!
You can use a loop to achieve this functionality. I've used a list simply for ease of use, performing remove operations and the like. Something like this should work:
public static int getNumberOfMatches(List<int> userGuesses, List<int> machineGuesses) {
// Determine list equality.
bool matchedAll = true;
for (int i = 0; i < userGuesses.Count; i++) {
if (userGuesses[i] != machineGuesses[i]) {
matchedAll = false;
break;
}
}
// The lists were equal; return numberOfGuesses + 1 [which equals 4 in this case].
if (matchedAll) {
return userGuesses.Count + 1;
}
// Remove all matches from machineGuesses.
foreach (int userGuess in userGuesses) {
if (machineGuesses.Contains(userGuess)) {
machineGuesses.Remove(userGuess);
}
}
// Determine number of matches made.
return userGuesses.Count - machineGuesses.Count;
}
I think for the first case, for all matches in order you would scan the arrays together and maybe increment a counter. Since you mentioned you know c++, this would be
int userGuesses[3];
int randomGen[3];
int matches = 0;
for(int i=0; i < 3; i++) if(userGuesses[i] == randoGen[i]) matches++;
if(matches == 3) //set highest score here.
if(matches == 2) // next score for ordered matches etc.
For the not-in-order case, you will need to lookup the generated array for each user guess to see if it has that value.