Sorting objects using IComparable - c#

I am trying to implement the IComparable interface in my custom object so that List.Sort() can sort them alphabetically.
My object has a field called _name which is a string type, and I want it to sort based on that. Here is the method I implemented:
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
return _name.CompareTo(otherUpgrade.Name);
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Not sure if I did something wrong or if it's just the way the string CompareTo works, but basically my List was sorted like this:
Test Upgrade
Test Upgrade 10
Test Upgrade 11
Test Upgrade 12
Test Upgrade 13
Test Upgrade 14
Test Upgrade 15
Test Upgrade 2
Test Upgrade 3
Test Upgrade 4
Test Upgrade 5
I want them to be sorted like this:
Test Upgrade
Test Upgrade 2
Test Upgrade 3
...etc

You want "natural order" -- the collation that a human who is familiar with the conventions of English would choose -- as opposed to what you've got, which is "lexicographic" collation: assign every letter a strict ordering, and then sort by each letter in turn.
Jeff has a good article on some of the ins and outs here, with links to different algorithms that try to solve the problem:
http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html
and Raymond discussed how Windows dealt with it here:
http://technet.microsoft.com/en-us/magazine/hh475812.aspx
Basically the problem is: natural order collation requires solving an artificial intelligence problem; you're trying to emulate what a human would do, and that can be surprisingly tricky. Good luck!

Strings are sorted in lexicographic order. You'll have to either format all your numbers to have the same length (eg: Test Upgrade 02) or parse the number in your comparer and incorporate it in your comparison logic.

The reason this happens is that you are doing string comparison, which has no explicit knowledge of numbers. It orders each string by the respective character codes of each character.
To get the effect you want will require a bit more work. See this question: Sort on a string that may contain a number

AlphaNumeric Sorting
public class AlphanumComparatorFast : IComparer
{
public int Compare(object x, object y)
{
string s1 = x as string;
if (s1 == null)
{
return 0;
}
string s2 = y as string;
if (s2 == null)
{
return 0;
}
int len1 = s1.Length;
int len2 = s2.Length;
int marker1 = 0;
int marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2)
{
char ch1 = s1[marker1];
char ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
char[] space1 = new char[len1];
int loc1 = 0;
char[] space2 = new char[len2];
int loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do
{
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
{
ch1 = s1[marker1];
}
else
{
break;
}
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do
{
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
{
ch2 = s2[marker2];
}
else
{
break;
}
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
string str1 = new string(space1);
string str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0]))
{
int thisNumericChunk = int.Parse(str1);
int thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else
{
result = str1.CompareTo(str2);
}
if (result != 0)
{
return result;
}
}
return len1 - len2;
}
}
Usage :
using System;
using System.Collections;
class Program
{
static void Main()
{
string[] highways = new string[]
{
"100F",
"50F",
"SR100",
"SR9"
};
//
// We want to sort a string array called highways in an
// alphanumeric way. Call the static Array.Sort method.
//
Array.Sort(highways, new AlphanumComparatorFast());
//
// Display the results
//
foreach (string h in highways)
{
Console.WriteLine(h);
}
}
}
Output
50F 100F SR9 SR100

Thanks for all the replies guys. I did my own method and it seems to work fine. It doesn't work for all cases but it works for my scenario. Here is the code for anyone who is interested:
/// <summary>
/// Compares the upgrade to another upgrade
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public int CompareTo(object obj)
{
//Int reference table:
//1 or greater means the current instance occurs after obj
//0 means both elements occur in the same position
//-1 or less means the current instance occurs before obj
if (obj == null)
return 1;
Upgrade otherUpgrade = obj as Upgrade;
if (otherUpgrade != null)
{
//Split strings into arrays
string[] splitStringOne = _name.Split(new char[] { ' ' });
string[] splitStringTwo = otherUpgrade.Name.Split(new char[] { ' ' });
//Will hold checks to see which comparer will be used
bool sameWords = false, sameLength = false, bothInt = false;
//Will hold the last part of the string if it is an int
int intOne = 0, intTwo = 0;
//Check if they have the same length
sameLength = (splitStringOne.Length == splitStringTwo.Length);
if (sameLength)
{
//Check to see if they both end in an int
bothInt = (int.TryParse(splitStringOne[splitStringOne.Length - 1], out intOne) && int.TryParse(splitStringTwo[splitStringTwo.Length - 1], out intTwo));
if (bothInt)
{
//Check to see if the previous parts of the string are equal
for (int i = 0; i < splitStringOne.Length - 2; i++)
{
sameWords = (splitStringOne[i].ToLower().Equals(splitStringTwo[i].ToLower()));
if (!sameWords)
break;
}
}
}
//If all criteria is met, use the customk comparer
if (sameWords && sameLength && bothInt)
{
if (intOne < intTwo)
return -1;
else if (intOne > intTwo)
return 1;
else //Both equal
return 0;
}
//Else use the default string comparer
else
return _name.CompareTo(otherUpgrade.Name);
}
else
throw new ArgumentException("Passed object is not an Upgrade.");
}
Will work for strings spaced out using a " " character, like so:
Test data:
Hello 11
Hello 2
Hello 13
Result
Hello 2
Hello 11
Hello 13
Will not work for data such as Hello11 and Hello2 since it cannot split them. Not case sensitive.

Related

c# binary search strings from array (including json)

So I have a Json implimentation that reads characters, the names go into arrays then I use Array.BinarySearch to get the position of the element.
I'm researching how to impliment the Binary Search own my own. I'm having trouble seeing logically what to do with the string name that is entered for the search.
Instead of using Array.BinarySearch, I need a separate method with the algorithm.
Any advice / strategy? :)
example:
/* json array implimented, manu printed etc... before this point, */
static void FindCharacters(Characters[] characters)
{
Characters result = new Characters();
string userInput = Console.ReadLine();
string[] name = new string[10000];
Console.Write("Search Name : ");
string searchKeyword = Console.ReadLine();
if (userInput.ToLower() == "name")
{
name = characters.Select(m => m.Name).ToArray();
Array.Sort(name);
Sorting.Sort(characters, searchKeyword);
var tmp = BinarySearch(name, searchKeyword);
if (tmp < 0)
{
Console.WriteLine("No data found!");
return;
}
else
{
result = characters[tmp];
CharacterPrint(result);
}
//result = characters[tmp]; //Convert.ToInt32(tmp)
//CharacterPrint(result);
}
public static int BinarySearch(int[] name, int item)
{
int min = 0;
int N = name.Length;
int max = N - 1;
do
{
int mid = (min + max) / 2;
if (item > name[mid])
min = mid + 1;
else
max = mid - 1;
if (name[mid] == item)
return mid;
//if (min > max)
// break;
} while (min <= max);
return -1;
}
Your int solution will work perfectly fine for strings. In fact, by just tweaking a couple lines, it would work for any data type that implements IComparable:
public static int BinarySearch<T>(T[] name, T item)
where T : IComparable<T>
{
int min = 0;
int N = name.Length;
int max = N - 1;
do
{
int mid = (min + max) / 2;
int t = item.CompareTo(name[mid]); // Temp value holder
if (t > 0) // item > name[mid]
min = mid + 1;
else if (t < 0) // item < name[mid]
max = mid - 1;
else // item == name[mid]
return mid;
//if (min > max)
// break;
} while (min <= max);
return -1;
}
You can call it like this:
string[] names = // ...
string name = //...
// Explicit calling
int idx = BinarySearch<string>(names, name);
// Implicit calling
// The following option works because the C# compiler can tell you are
// using two values of type string and inserts the correct generic
// type for you
int idx = BinarySearch(names, name);
You can see the changes made above reflect how to replace the default comparison operators (i.e. "<", ">", "==") with their CompareTo equivolents. The extra variable t is there to avoid redundantly calling CompareTo on the objects twice.
The way that CompareTo works is that it takes the calling object and compares it with the passed object. If the passed object would appear before the calling object in a sorted list, the method returns -1. If it would appear after, it returns 1. If they are the same, it returns 0.
See the following example for an illustration of this:
// The following values are compared based on standard lexical alphabetical order
a.CompareTo(b); // "b" would appear after "a", so this returns 1
c.CompareTo(b); // "b" would appear before "c", so this returns -1
b.CompareTo(b); // "b" and "b" are the same value, so this returns 0

What's a good algorithm for checking if a given list of values alternates up and down?

Assuming the function takes in a list of double and an index to perform the check from, I need to check if the values alternates up and down consecutively.
For example, a list of [14.0,12.3,13.0,11.4] alternates consecutively but a list of [14.0,12.3,11.4,13.0] doesn't.
The algorithm doesn't have to be fast, but I'd like it to be compact to write (LINQ is totally fine). This is my current method, and it looks way too crude to my taste:
enum AlternatingDirection { Rise, Fall, None };
public bool CheckConsecutiveAlternation(List<double> dataList, int currDataIndex)
{
/*
* Result True : Fail
* Result False : Pass
*/
if (!_continuousRiseFallCheckBool)
return false;
if (dataList.Count < _continuousRiseFallValue)
return false;
if (currDataIndex + 1 < _continuousRiseFallValue)
return false;
AlternatingDirection direction = AlternatingDirection.None;
int startIndex = currDataIndex - _continuousRiseFallValue + 1;
double prevVal = 0;
for (int i = startIndex; i <= currDataIndex; i++)
{
if (i == startIndex)
{
prevVal = dataList[i];
continue;
}
if (prevVal > dataList[i])
{
prevVal = dataList[i];
switch (direction)
{
case AlternatingDirection.None:
direction = AlternatingDirection.Fall;
continue;
case AlternatingDirection.Rise:
direction = AlternatingDirection.Fall;
continue;
default:
//Two falls in a row. Not a signal.
return false;
}
}
if (prevVal < dataList[i])
{
prevVal = dataList[i];
switch (direction)
{
case AlternatingDirection.None:
direction = AlternatingDirection.Rise;
continue;
case AlternatingDirection.Fall:
direction = AlternatingDirection.Rise;
continue;
default:
//Two rise in a row. Not a signal.
return false;
}
}
return false;
}
//Alternated n times until here. Data is out of control.
return true;
}
Try this:
public static bool IsAlternating(double[] data)
{
var d = GetDerivative(data);
var signs = d.Select(val => Math.Sign(val));
bool isAlternating =
signs.Zip(signs.Skip(1), (a, b) => a != b).All(isAlt => isAlt);
return isAlternating;
}
private static IEnumerable<double> GetDerivative(double[] data)
{
var d = data.Zip(data.Skip(1), (a, b) => b - a);
return d;
}
Live demo
The idea is:
If the given list of values is alternating up and down, mathematically it means that it's derivative keeps changing its sign.
So this is exactly what this piece of code does:
Get the derivative.
Checks for sign fluctuations.
And the bonus is that it will not evaluate all of the derivative / signs arrays unless it is necessary.
I'd do it with a couple of consecutive zips, bundled in an extension method:
public static class AlternatingExtensions {
public static bool IsAlternating<T>(this IList<T> list) where T : IComparable<T>
{
var diffSigns = list.Zip(list.Skip(1), (a,b) => b.CompareTo(a));
var signChanges = diffSigns.Zip(diffSigns.Skip(1), (a,b) => a * b < 0);
return signChanges.All(s => s);
}
}
Edit: for completeness, here's how you'd use the feature:
var alternatingList = new List<double> { 14.0, 12.3, 13.0, 11.4 };
var nonAlternatingList = new List<double> { 14.0, 12.3, 11.4, 13.0 };
alternatingList.IsAlternating(); // true
nonAlternatingList.IsAlternating(); // false
I also changed the implementation to work on more types, making use of generics as much as possible.
Here is a small pseudo code. Assuming no repeated elements (can be handled easily though by few tweaks)
Idea is to have a sign variable which is alternating 1,-1,... that is multipled by the difference of two consecutive pairs, the difference multipled by this sign variable must always be positive. If it's not at some point, return false.
isUpAndDown(l):
if size(l) < 2: // empty,singleton list is always good.
return true
int sign = (l[0] < l[1] ? 1 : -1)
for i from 0 to n-1 (exclusive):
if sign * (l[i+1] - l[i]) < 0:
return false //not alternating
sign = sign * -1
end for
return true //all good
You may create kind of a signed array first:
double previous = 0;
var sign = myList.Select(x => {
int s = Math.Sign(x - previous);
previos = x;
return s;
});
This gives you a list similar to
{ -1, 1, -1, ... }
Now you can take a similar appraoch as the previos Select-statement to check if a -1 follows a 1:
var result = sign.All(x => {
bool b = x == -previous;
previous = x;
return b;
});
Now result is true if your list alternates, false otherwise.
EDIT: To ensure that the very first check within the second query also passes add previous = -sign[0]; before the second query.
Assuming that two equal values in a row are not acceptable (if they are, just skip over equal values):
if (dataList[0] == dataList[1])
return false;
bool nextMustRise = dataList[0] > dataList[1];
for (int i = 2; i < dataList.Count; i++) {
if (dataList[i - 1] == dataList[i] || (dataList[i - 1] < dataList[i]) != nextMustRise)
return false;
nextMustRise = !nextMustRise;
}
return true;
public double RatioOfAlternations(double[] dataList)
{
double Alternating = 0;
double Total = (dataList.Count()-2);
for (int (i) = 0; (i) < Total; (i)++)
{
if (((dataList[i+1]-dataList[i])*(dataList[i+2]-dataList[i+1]))<0)
// If previous change is opposite sign to current change, this will be negative
{
Alternating++;
}
else
{
}
}
return (Alternating/Total);
}

Any alternative in C# similar to Java's nextInt() to extract int from string, without using Split()? [duplicate]

In Java I can pass a Scanner a string and then I can do handy things like, scanner.hasNext() or scanner.nextInt(), scanner.nextDouble() etc.
This allows some pretty clean code for parsing a string that contains rows of numbers.
How is this done in C# land?
If you had a string that say had:
"0 0 1 22 39 0 0 1 2 33 33"
In Java I would pass that to a scanner and do a
while(scanner.hasNext())
myArray[i++] = scanner.nextInt();
Or something very similar. What is the C#' ish way to do this?
I'm going to add this as a separate answer because it's quite distinct from the answer I already gave. Here's how you could start creating your own Scanner class:
class Scanner : System.IO.StringReader
{
string currentWord;
public Scanner(string source) : base(source)
{
readNextWord();
}
private void readNextWord()
{
System.Text.StringBuilder sb = new StringBuilder();
char nextChar;
int next;
do
{
next = this.Read();
if (next < 0)
break;
nextChar = (char)next;
if (char.IsWhiteSpace(nextChar))
break;
sb.Append(nextChar);
} while (true);
while((this.Peek() >= 0) && (char.IsWhiteSpace((char)this.Peek())))
this.Read();
if (sb.Length > 0)
currentWord = sb.ToString();
else
currentWord = null;
}
public bool hasNextInt()
{
if (currentWord == null)
return false;
int dummy;
return int.TryParse(currentWord, out dummy);
}
public int nextInt()
{
try
{
return int.Parse(currentWord);
}
finally
{
readNextWord();
}
}
public bool hasNextDouble()
{
if (currentWord == null)
return false;
double dummy;
return double.TryParse(currentWord, out dummy);
}
public double nextDouble()
{
try
{
return double.Parse(currentWord);
}
finally
{
readNextWord();
}
}
public bool hasNext()
{
return currentWord != null;
}
}
Using part of the answers already given, I've created a StringReader that can extract Enum and any data type that implements IConvertible.
Usage
using(var reader = new PacketReader("1 23 ErrorOk StringValue 15.22")
{
var index = reader.ReadNext<int>();
var count = reader.ReadNext<int>();
var result = reader.ReadNext<ErrorEnum>();
var data = reader.ReadNext<string>();
var responseTime = reader.ReadNext<double>();
}
Implementation
public class PacketReader : StringReader
{
public PacketReader(string s)
: base(s)
{
}
public T ReadNext<T>() where T : IConvertible
{
var sb = new StringBuilder();
do
{
var current = Read();
if (current < 0)
break;
sb.Append((char)current);
var next = (char)Peek();
if (char.IsWhiteSpace(next))
break;
} while (true);
var value = sb.ToString();
var type = typeof(T);
if (type.IsEnum)
return (T)Enum.Parse(type, value);
return (T)((IConvertible)value).ToType(typeof(T), System.Globalization.CultureInfo.CurrentCulture);
}
}
While this isn't the exact same fundamental concept, what you're looking for can be done with this lambda expression:
string foo = "0 0 1 22 39 0 0 1 2 33 33";
int[] data = foo.Split(' ').Select(p => int.Parse(p)).ToArray();
What this does is first Split the string, using a space as a delimiter. The Select function then allows you to specify an alias for a given member in the array (which I referred to as 'p' in this example), then perform an operation on that member to give a final result. The ToArray() call then turns this abstract enumerable class into a concrete array.
So in this end, this splits the string, then converts each element into an int and populates an int[] with the resulting values.
To my knowledge, there are no built in classes in the framework for doing this. You would have to roll your own.
That would not be too hard. A nice C# version might implement IEnumerable so you could say:
var scanner = new Scanner<int>(yourString);
foreach(int n in scanner)
; // your code
To get as close as possible to your syntax, this'll work if you're only interested in one type ("int" in the example):
static void Main(string[] args)
{
if (args.Length == 0) { args = new string[] { "3", "43", "6" }; }
IEnumerator<int> scanner = (from arg in args select int.Parse(arg)).GetEnumerator();
while (scanner.MoveNext())
{
Console.Write("{0} ", scanner.Current);
}
}
Here's an even more whiz-bang version that allows you to access any type that is supported by string's IConvertible implementation:
static void Main(string[] args)
{
if (args.Length == 0) { args = new string[] { "3", "43", "6" }; }
var scanner = args.Select<string, Func<Type, Object>>((string s) => {
return (Type t) =>
((IConvertible)s).ToType(t, System.Globalization.CultureInfo.InvariantCulture);
}).GetEnumerator();
while (scanner.MoveNext())
{
Console.Write("{0} ", scanner.Current(typeof(int)));
}
}
Just pass a different type to the "typeof" operator in the while loop to choose the type.
These both require the latest versions of C# and the .NET framework.
You could use linq to accomplish this like so:
string text = "0 0 1 22 39 0 0 1 2 33 33";
text.Where(i => char.IsNumber(i)).Write(); // do somthing usefull here...
I would do this in one of a couple ways depending on whether 1) you are using the latest .NET framework with LINQ support and 2) you know the values are valid integers. Here's a function to demonstrate both:
int[] ParseIntArray(string input, bool validateRequired)
{
if (validateRequired)
{
string[] split = input.Split();
List<int> result = new List<int>(split.Length);
int parsed;
for (int inputIdx = 0; inputIdx < split.Length; inputIdx++)
{
if (int.TryParse(split[inputIdx], out parsed))
result.Add(parsed);
}
return result.ToArray();
}
else
return (from i in input.Split()
select int.Parse(i)).ToArray();
}
Based on comments in other answer(s), I assume you need the validation. After reading those comments, I think the closest thing you'll get is int.TryParse and double.TryParse, which is kind of a combination of hasNextInt and nextInt (or a combination of hasNextDouble and nextDouble).

How to compare two "numbers" with multiple dots?

I have an unordered list that can look something like this:
1
2.2
1.1.1
3
When i sort the list, 1.1.1 becomes greater than 3 and 2.2, and 2.2 becomes greater than 3.
This is because Double.Parse removes the dots and makes it a whole number.
This is the method i use to sort with:
public class CompareCategory: IComparer<Category>
{
public int Compare(Category c1, Category c2)
{
Double cat1 = Double.Parse(c1.prefix);
Double cat2 = Double.Parse(c2.prefix);
if (cat1 > cat2)
return 1;
else if (cat1 < cat2)
return -1;
else
return 0;
}
}
How can i fix this?
Thanks
Are these version #s by chance? Can you use the Version class? It sorts each part as you seem to want, although it only works up to 4 parts. I would not recommend parsing into a numeric value like you are doing.
It has an IComparable interface. Assuming your inputs are strings, here's a sample:
public class CompareCategory: IComparer<Category>
{
public int Compare(Category c1, Category c2)
{
var cat1 = new Version(c1.prefix);
var cat2 = new Version(c2.prefix);
if (cat1 > cat2)
return 1;
else if (cat1 < cat2)
return -1;
else
return 0;
}
}
If you need something with more than 4 "parts", I think I would create a comparer which split the strings at the dots, and then parse each element as an integer and compare them numerically. Make sure to consider cases like 1.002.3 and 1.3.3 (what do you want the sort order to be?).
Update, here is a sample of what I mean. Lightly tested:
public class CategoryComparer : Comparer<Category>
{
public override int Compare(Category x, Category y)
{
var xParts = x.prefix.Split(new[] { '.' });
var yParts = y.prefix.Split(new[] { '.' });
int index = 0;
while (true)
{
bool xHasValue = xParts.Length > index;
bool yHasValue = yParts.Length > index;
if (xHasValue && !yHasValue)
return 1; // x bigger
if (!xHasValue && yHasValue)
return -1; // y bigger
if (!xHasValue && !yHasValue)
return 0; // no more values -- same
var xValue = decimal.Parse("." + xParts[index]);
var yValue = decimal.Parse("." + yParts[index]);
if (xValue > yValue)
return 1; // x bigger
if (xValue < yValue)
return -1; // y bigger
index++;
}
}
}
public static void Main()
{
var categories = new List<Category>()
{
new Category { prefix = "1" },
new Category { prefix = "2.2" },
new Category { prefix = "1.1.1" },
new Category { prefix = "1.1.1" },
new Category { prefix = "1.001.1" },
new Category { prefix = "3" },
};
categories.Sort(new CategoryComparer());
foreach (var category in categories)
Console.WriteLine(category.prefix);
}
Output:
1
1.001.1
1.1.1
1.1.1
2.2
3
public class CodeComparer : IComparer<string>
{
public int Compare(string x, string y)
{
var xParts = x.Split(new char[] { '.' });
var yParts = y.Split(new char[] { '.' });
var partsLength = Math.Max(xParts.Length, yParts.Length);
if (partsLength > 0)
{
for (var i = 0; i < partsLength; i++)
{
if (xParts.Length <= i) return -1;// 4.2 < 4.2.x
if (yParts.Length <= i) return 1;
var xPart = xParts[i];
var yPart = yParts[i];
if (string.IsNullOrEmpty(xPart)) xPart = "0";// 5..2->5.0.2
if (string.IsNullOrEmpty(yPart)) yPart = "0";
if (!int.TryParse(xPart, out var xInt) || !int.TryParse(yPart, out var yInt))
{
// 3.a.45 compare part as string
var abcCompare = xPart.CompareTo(yPart);
if (abcCompare != 0)
return abcCompare;
continue;
}
if (xInt != yInt) return xInt < yInt ? -1 : 1;
}
return 0;
}
// compare as string
return x.CompareTo(y);
}
}
Maybe you could just string compare it?
I'm surprised that Double.Parse doesn't throw an exception with those numbers with more than one decimal place.
You really need to write some rules about how to compare these strings.
I would split the strings using String.Split() on the dot character, then iterate through the two lists created and as soon as one of the levels contained a lower or higher number than the other, or if you ran out of items in one of the lists then you wold return 1 or -1 as appropriate. If you get to the end of both lists in the same iteration of the loop then they are the same and return 0.
I would write the code but I don't have VS in front of me.

comparing strings, one from an array other from an entered value

Basically comparing a string that is entered, and trying to get that position from the array.
If I initialize position to 0 then it returns the position zero of the array, if I initialize to 1 then it gives me the item in slot 1, so it's skipping the compare statement.
I also tried using (custStatus == cardStatus[i])
public static int discount(string []cardStatus, int []pDiscount, string custStatus)
{
int position= 0;
int discount;
for(int i = 0; i < 2; i++)
{
if (string.Equals(custStatus, cardStatus[i]))
position = i;
}
discount = pDiscount[position];
return discount;
}
With your code, there's no way to tell if position = 0 means custStatus was found in your cardStatus array or if no match was made at all and the default value is being used. I'd recommend either using a boolean matchFound variable or setting position = -1 and adding an extra if statement at the end either way. Either:
boolean matchFound = false;
...
if(matchFound)
{
discount = pDiscount[position];
}
or else
int position = -1;
...
if(position >= 0)
{
discount = pDiscount[position];
}
Give this a try:
public static int discount(string[] cardStatus, int[] pDiscount, string custStatus) {
var position = Array.IndexOf(cardStatus, custStatus);
return (position == -1) ? -1 : pDiscount[position];
}
public static int discount(string []cardStatus, int []pDiscount, string custStatus)
{
for(int i = 0; i < Math.Min(cardStatus.Length, pDiscount.Length); i++)
{
if (string.Equals(custStatus, cardStatus[i]))
{
return pDiscount[i];
}
}
return -1;
}
Don't be afraid to return directly from FOR-loop, it is old-school that teaches to have only one return point from method. You can have as many returns as it helps you to keep your code clean and easy to read.
And perhaps it would be better to use the following expression in for-loop as it will guard you from possible different lengths of arrays:
for (int i = 0; i < Math.Min(cardStatus.Length, pDiscount.Length; i++)
This looks ok, even though this is somewhat more straightforward:
for(int i = 0; i < cardStatus.Length; i++)
{
if (custStatus == cardStatus[i])
{
position = i;
break;
}
}
Given your question it appears to be the case that all cardStatus[i] match custStatus - did you check the input?
Also given your code what happens if there is no match? Currently you would return pDiscount[0] - that doesn't seem to be correct.

Categories

Resources