I'm trying to sort a list of strings in alphabetical order in C#. My code looks like this:
public static List<Result> sort(List<Result> listToSort)
{
int listSize = listToSort.Count;
for (int i = 0; i < listSize; i++)
{
for (int j = 0; j < listSize; j++)
{
if (listToSort[i].SN[0] < listToSort[j].SN[0])
{
Result tempValue = listToSort[j];
listToSort[j] = listToSort[i];
listToSort[i] = tempValue;
}
}
}
return listToSort;
}
But it's only sorting it based on the first letter of a string. In other words, if I have a list like this:
donald, abby, dave, bob, sam, pete
It will sort it like so:
abby, bob, donald, dave, pete, sam
One would expect 'dave' to come before 'donald'..
Any ideas?
Currently you are only sorting by the first letter that is why you are seeing this result. You can use Enumerable.OrderBy - LINQ
List<Result> sortedList = listToSort.OrderBy(r=> r.SN).ToList();
Or for your current code you can modify your check to:
if (string.Compare(listToSort[i].SN,listToSort[j].SN) < 0)
How about using LINQ for this:
return listToSort.OrderBy(report => report.SN)
I'm assuming your Report class has a string property you want the list to be sorted by?
EDIT
Didn't notice that you'd already specified the SN property, have amended my answer.
public static List<Result> sort(List<Result> listToSort)
{
return listToSort.OrderBy(x=>x.SN[0]).ToList();
}
You're only ever evaluating the first letter. Try using the traditional sorting method:
public static void Sort(List<Result> listToSort)
{
listToSort.Sort(new ResultComparator());
}
public class ResultComparator : IComparer<Result>
{
public int Compare(Result x, Result y)
{
if (x == null && y == null) return 0;
if (x == null) return 1;
if (y == null) return 0;
// compare based in SN
return string.Compare(x.SN, y.SN);
}
}
Take a look at this part:
for (int i = 0; i < listSize; i++)
{
for (int j = 0; j < listSize; j++)
{
if (listToSort[i].SN[0] < listToSort[j].SN[0])
{
You are
only comparing on SN[0]. If SN is a string then that explains your main result.
always using the same compare, whether i < j or i > j
Best thing to do is to use a built-in sort. Linq's OrderBy(lambda) is the easiest but it creates a new list. For an in-place sort, use List<T>.Sort(Comparer).
If you do have to do it yourself, look up a good sorting algorithm (wikipedia).
It was happened because of comparing character of the first string (listToSort[i].SN[0] => which produces the first character of your input). If you want to compare the string values, you should use string.Compare() method.
--SJ
Related
So I have this homework assignment that requires me to assign output to labels after I compare two arrays. My problem is that after I compare the two arrays, the output I assign is wrong. I'm supposed to out 'Y' if at a specific index of the two arrays are equal and 'N' if they're not equal but every time I run the code, it outputs 'Y' to all the labels no matter what. How can I fix what is being outputted after the comparison?
private void evaluateStudentAnswers()
{
/* Use a "for" loop to cycle through the answerKey[] and studentAnswers[] arrays, and compare the answers
* in the two arrays at each index. If they match, then increment the global variable "correctAnswers"
* and assign the value 'Y' to the corresponding index in the correctOrIncorrect[] array. if they
* don't match, then increment the global variable "incorrectAnswers" and assign the value 'N' to the
* corresponding indes in the correctOrIncorrec[] array. These two variables will be used to calculate
* the grade percentage.
*/
for (int i = 0; i < studentAnswers.Length; i++)
{
for(int j = 0; j < answerKey.Length; j++)
{
// I think the indexes below are being checked if they're the same and I need to make sure not just the
//indexes are the same but the values as well
if (studentAnswers[i] == answerKey[j])
{
correctAnswers++;
for(int k = 0; k < correctOrIncorrect.Length; k++)
{
correctOrIncorrect[k] = 'Y';
}
}
else
{
incorrectAnswers++;
for (int k = 0; k < correctOrIncorrect.Length; k++)
{
correctOrIncorrect[k] = 'N';
}
}
}
}
}
I think your code can be simplified quite a lot. assuming there's a 1-1 mapping between studentAnswers and answerKey.
for (int i = 0; i < studentAnswers.Length; i++)
{
var studentAnswer = studentAnswers[i];
var answer = answerKey[i];
if (studentAnswer == answer)
{
++correctAnswers;
correctOrIncorrect[i] = 'Y';
}
else
{
++incorrectAnswers;
correctOrIncorrect[i] = 'N'
}
}
All of the arrays are the same size. So when we loop over each answer the student provided, we know we can find the corresponding correct answer in answerKey. Also, the tracking of correct answers also follows the same pattern, for each studentAnswer, we want to record the correctness in correctOrIncorrect, which corresponds to the particular answer the student provided. As such, we only need to perform a single loop, since the i refers to the appropriate index in all the arrays as we're processing.
If studentAnswers.Length == answerKey.Length == correctOrIncorrect.Length
Then
for (int i = 0; i < studentAnswers.Length; i++)
{
if(studentAnswers[i] == answerKey[j])
{
correctAnswers++;
correctOrIncorrect[k] = 'Y';
}
else
{
incorrectAnswers++;
correctOrIncorrect[k] = 'N';
}
}
Since it is an assignment I wont give an answer :) but since you are stuck I encourage you use the guidance below.
These two are unnecessary in your code
inner for-loop on correctOrIncorrect[]
variable "k", you can use "i" instead for correctOrIncorrect value assignment
Since the arrays have to have the same size/order, you only need to loop through them once. Also I find ternary assignments more clear than if blocks:
Func<bool, int> toInt = (b) => b ? 1 : 0;
for (int i = 0; i < studentAnswers.Length; i++)
{
var studentAnswer = studentAnswers[i];
var answer = answerKey[i];
var isCorrect = studentAnswer == answer;
correctOrIncorrect[i] = isCorrect ? 'Y' : 'N';
correctAnswers = isCorrect ? 1 : 0; // toInt(isCorrect)
incorrectAnswers = !isCorrect ? 1 : 0; // toInt(!isCorrect)
}
}
Or in LINQ (just because it's worth learning, but probably not appropriate for homework):
correctOrIncorrect = answerKey.Zip(studentAnswer, (a,b) => a == b ? "Y" : "N").ToArray();
incorrectAnswers = correctOrIncorrect.Count(x => x == "Y");
...
I have implemented my own selection sort method that seems to be doing it's job for the most part; However, when I am printing files to an excel sheet the printer does not print the first item. I am unsure whether or not the sort method is the source of the problem. My test method for my sort method passes, which is why I am doubting that that is the source. My sort method is shown below. Does it have an error in the scope or order or operations? When I manually move through it on paper everything sorts properly.
public bool sortMaterial()
{
for (int i = 0; i < salesList.Count - 2; i++)
{
Sales curr = salesList[i];
Sales temp;
Sales min = curr;
int swap = 0;
for (int j = i + 1; j < salesList.Count; j++ )
{
temp = salesList[j];
if (String.Compare(temp.material, min.material) == -1)
{
min = temp;
swap = j;
}
}
salesList[i] = min;
salesList[swap] = curr;
}
return true;
}
A neat way to do custom sorting is by implementing the IComparer<T> interface:
public class SalesMaterialComparer : IComparer<Sales> {
public int Compare(Sales x, Sales y) {
return String.Compare(x.material, y.material);
}
}
You can pass your custom comparer to the LINQ OrderBy() method.
IEnumerable<Sales> salesList;
var myComparer = new SalesMaterialComparer();
var sorted = salesList.OrderBy(s => s, myComparer);
I have the following struct
public struct Colors
{
public double MinV;
public double MaxV;
public string color;
}
List<Colors> ColorValues = new List<Colors>();
i need to retrieve the color of the element that has value located between Minv and Maxv.
i work as the following
i=0;
while (i < ColorValues.Count)
{
if (value> ColorValues[i].MinV && value< ColorValues[i].MaxV)
Color=ColorValues[i].color;
i++;
}
i need to replace this loop with fast searching way .
Use LINQ:
If you need all the structs that matches the criteria:
var allThatFitsCriteria = ColorValues.Where(x => value > x.MinV && value < x.MaxV);
If you need only the first one (if it exists):
var firstThatFitsCriteriaOrNull = ColorValues.FirstOrDefault(x => value > x.MinV && value < x.MaxV);
An optimization would be to break out once you've found what you're looking for
i=0;
while (i < ColorValues.Count)
{
if (value> ColorValues[i].MinV && value< ColorValues[i].MaxV)
{
Color=ColorValues[i].color;
break;
}
i++;
}
Something like:
forelement (element G_Element, Grid)
{
Grid[G_Element.dim1, G_Element.dim2] =
new clsGridElement(G_Element.dim1, G_Element.dim2);
}
instead of
for (int X = 0; X < GridWidth; X++)
for (int Y = 0; Y < GridHeight; Y++)
Grid[X, Y] = new clsGridElement(X, Y);
If something doesn't innately exist, is that something that could be created?
Thanks,
Tim
You could do this - just make a custom type that exposes these, and use a regular foreach:
public class TwoDimensionalIterator
{
public TwoDimensionalIterator(int i, int j)
{
this.Dim1 = i; this.Dim2 = j;
}
public int Dim1 { get; private set; }
public int Dim2 { get; private set; }
}
Then make an extension method somewhere to return an enumerable of this:
public static IEnumerable<TwoDimensionalIterator> IterateTwoDim<T>(this T[,] array)
{
for (int i=0;i<array.GetLength(0);++i)
for (int j=0;i<array.GetLength(1);++j)
yield return new TwoDimensionalIterator(i,j);
}
With this, you could then do:
foreach(var index in Grid.IterateTwoDim())
{
Grid[index.Dim1, index.Dim2] = new clsGridElement(index.Dim1, index.Dim2);
}
Not sure exactly what you are trying to do here, or why, or what you expect to get from it, but if you implement your own iterator than implements the IEnumerator interface then you could create something that would hit every cell in your 2D (or more) collection.
Of course, you won't actually gain anything performance-wise from doing this versus just using nested loops, but I guess it'd be syntactic sugar.
Such creation of indexes can be obtained by using "Cartesian product" of all indexes. Here is sample based on Is there a good LINQ way to do a cartesian product? (courtesy of the Eric Lippert):
var allIndexes = from dim1 in Enumerable.Range(0, Grid.GetLength(0))
from dim2 in Enumerable.Range(0, Grid.GetLength(1))
select new {dim1, dim2};
foreach (var G_Element in allIndexes)
{
Grid[G_Element.dim1, G_Element.dim2] =
new clsGridElement(G_Element.dim1, G_Element.dim2);
}
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.