How can I find the last element in a List<>? - c#

The following is an extract from my code:
public class AllIntegerIDs
{
public AllIntegerIDs()
{
m_MessageID = 0;
m_MessageType = 0;
m_ClassID = 0;
m_CategoryID = 0;
m_MessageText = null;
}
~AllIntegerIDs()
{
}
public void SetIntegerValues (int messageID, int messagetype,
int classID, int categoryID)
{
this.m_MessageID = messageID;
this.m_MessageType = messagetype;
this.m_ClassID = classID;
this.m_CategoryID = categoryID;
}
public string m_MessageText;
public int m_MessageID;
public int m_MessageType;
public int m_ClassID;
public int m_CategoryID;
}
I am trying to use the following in my main() function code:
List<AllIntegerIDs> integerList = new List<AllIntegerIDs>();
/* some code here that is ised for following assignments*/
{
integerList.Add(new AllIntegerIDs());
index++;
integerList[index].m_MessageID = (int)IntegerIDsSubstring[IntOffset];
integerList[index].m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
integerList[index].m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
integerList[index].m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
integerList[index].m_MessageText = MessageTextSubstring;
}
Problem is here: I am trying to print all elements in my List using a for loop:
for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++) //<----PROBLEM HERE
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", integerList[cnt3].m_MessageID,integerList[cnt3].m_MessageType,integerList[cnt3].m_ClassID,integerList[cnt3].m_CategoryID, integerList[cnt3].m_MessageText);
}
I want to find the last element so that I equate cnt3 in my for loop and print out all entries in the List. Each element in the list is an object of the class AllIntegerIDs as mentioned above in the code sample. How do I find the last valid entry in the List?
Should I use something like integerList.Find(integerList[].m_MessageText == null;?
If I use that it will need an index that will range from 0 to whatever maximum. Means I will have to use another for loop which I do not intend to use. Is there a shorter/better way?

To get the last item of a collection use LastOrDefault() and Last() extension methods
var lastItem = integerList.LastOrDefault();
OR
var lastItem = integerList.Last();
Remeber to add using System.Linq;, or this method won't be available.

If you just want to access the last item in the list you can do
if (integerList.Count > 0)
{
// pre C#8.0 : var item = integerList[integerList.Count - 1];
// C#8.0 :
var item = integerList[^1];
}
to get the total number of items in the list you can use the Count property
var itemCount = integerList.Count;

In C# 8.0 you can get the last item with ^ operator full explanation
List<char> list = ...;
var value = list[^1];
// Gets translated to
var value = list[list.Count - 1];

Lets get at the root of the question, how to address the last element of a List safely...
Assuming
List<string> myList = new List<string>();
Then
//NOT safe on an empty list!
string myString = myList[myList.Count -1];
//equivalent to the above line when Count is 0, bad index
string otherString = myList[-1];
"count-1" is a bad habit unless you first guarantee the list is not empty.
There is not a convenient way around checking for the empty list except to do it.
The shortest way I can think of is
string myString = (myList.Count != 0) ? myList [ myList.Count-1 ] : "";
you could go all out and make a delegate that always returns true, and pass it to FindLast, which will return the last value (or default constructed valye if the list is empty). This function starts at the end of the list so will be Big O(1) or constant time, despite the method normally being O(n).
//somewhere in your codebase, a strange delegate is defined
private static bool alwaysTrue(string in)
{
return true;
}
//Wherever you are working with the list
string myString = myList.FindLast(alwaysTrue);
The FindLast method is ugly if you count the delegate part, but it only needs to be declared one place. If the list is empty, it will return a default constructed value of the list type "" for string. Taking the alwaysTrue delegate a step further, making it a template instead of string type, would be more useful.

int lastInt = integerList[integerList.Count-1];

Though this was posted 11 years ago, I'm sure the right number of answers is one more than there are!
You can also doing something like;
if (integerList.Count > 0)
var item = integerList[^1];
See the tutorial post on the MS C# docs here from a few months back.
I would personally still stick with LastOrDefault() / Last() but thought I'd share this.
EDIT;
Just realised another answer has mentioned this with another doc link.

Change
for (int cnt3 = 0 ; cnt3 <= integerList.FindLastIndex ; cnt3++)
to
for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)

Use the Count property. The last index will be Count - 1.
for (int cnt3 = 0 ; cnt3 < integerList.Count; cnt3++)

Why not just use the Count property on the List?
for(int cnt3 = 0; cnt3 < integerList.Count; cnt3++)

Independent of your original question, you will get better performance if you capture references to local variables rather than index into your list multiple times:
AllIntegerIDs ids = new AllIntegerIDs();
ids.m_MessageID = (int)IntegerIDsSubstring[IntOffset];
ids.m_MessageType = (int)IntegerIDsSubstring[IntOffset + 1];
ids.m_ClassID = (int)IntegerIDsSubstring[IntOffset + 2];
ids.m_CategoryID = (int)IntegerIDsSubstring[IntOffset + 3];
ids.m_MessageText = MessageTextSubstring;
integerList.Add(ids);
And in your for loop:
for (int cnt3 = 0 ; cnt3 < integerList.Count ; cnt3++) //<----PROBLEM HERE
{
AllIntegerIDs ids = integerList[cnt3];
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n",
ids.m_MessageID,ids.m_MessageType,ids.m_ClassID,ids.m_CategoryID, ids.m_MessageText);
}

I would have to agree a foreach would be a lot easier something like
foreach(AllIntegerIDs allIntegerIDs in integerList)
{
Console.WriteLine("{0}\t{1}\t{2}\t{3}\t{4}\n", allIntegerIDs.m_MessageID,
allIntegerIDs.m_MessageType,
allIntegerIDs.m_ClassID,
allIntegerIDs.m_CategoryID,
allIntegerIDs.m_MessageText);
}
Also I would suggest you add properties to access your information instead of public fields, depending on your .net version you can add it like public int MessageType {get; set;} and get rid of the m_ from your public fields, properties etc as it shouldnt be there.

Related

How to apply arraylist with variables in an Object, inside a method using a for loop in c #

A hw was given to us to change a previous hw in C# which used 2d arrays and instead of using 2d arrays we use an Array list with variables declared in an object called Students.
I would like to use a method to calculate a student best mark; however, the method is giving me an error and a warning which are the following:
Error:
CS0161 'Form1.Calc_HighestMarkOutput(int)': not all code paths return a value.
Warning:
CS0162 Unreachable code detected.
Inside the arraylist the user inputed (through use of an overload constructor):
Student Name, Maths Mark, English Mark, Maltese Mark, Email Address.
and since in the method I am returning 3 highest marks in 3 subjects attained by all students, I decided to return an array. which will be accessed by a temporary array inside the main program by selectedindex.
Please help me find the problem.
And thanks in advance.
public int[] Calc_HighestMarkOutput(int HighestMarkIndex)
{
int[] HighestMarkOutput = new int[3];
int HighestMarkMaths = 0;
int HighestMarkEnglish = 0;
int HighestMarkMaltese = 0;
int TMPHighestMarkMaths = 0;
int TMPHighestMarkEnglish = 0;
int TMPHighestMarkMaltese = 0;
for (int i = 0; i < myStudents.Count; i++) //a loop through an array list.
{
if (myStudents[HighestMarkIndex].Maths_Result > HighestMarkMaths)
{
TMPHighestMarkMaths = myStudents[HighestMarkIndex].Maths_Result;
HighestMarkMaths = TMPHighestMarkMaths;
}
if (myStudents[HighestMarkIndex].English_Result > HighestMarkEnglish)
{
TMPHighestMarkEnglish = myStudents[HighestMarkIndex].English_Result;
HighestMarkEnglish = TMPHighestMarkEnglish;
}
if (myStudents[HighestMarkIndex].Maltese_Result > HighestMarkMaltese)
{
TMPHighestMarkMaltese = myStudents[HighestMarkIndex].Maltese_Result;
HighestMarkMaltese = TMPHighestMarkMaltese;
}
HighestMarkOutput[0] = HighestMarkMaths;
HighestMarkOutput[1] = HighestMarkEnglish;
HighestMarkOutput[2] = HighestMarkMaltese;
return HighestMarkOutput;
}
You are getting an error, because the return-statement is inside the loop. If the list is empty, the return statement will never be executed. Also, you know the result only after the loop has finished. So, place the return-statement after the loop.
Since the purpose of this method is to find the highest marks, it makes no sense to pass such an index into the routine as a parameter.
Using foreach is easier than for because you don't have to deal with indexes.
Instead of returning an array, return an unnamed student containing the results. You can drop useless temporary variables.
public Student Calc_HighestMarkOutput()
{
var result = new Student(); // You also might have to add a default constructor.
foreach (Student student in myStudents) {
if (student.Maths_Result > result.Maths_Result) {
result.Maths_Result = student.Maths_Result;
}
if (student.English_Result > result.English_Result) {
result.English_Result = student.English_Result;
}
if (student.Maltese_Result > result.Maltese_Result) {
result.Maltese_Result = student.Maltese_Result;
}
}
return result;
}
You could also use Math.Max to simplify finding the maximum value
foreach (Student student in myStudents) {
result.Maths_Result = Math.Max(result.Maths_Result, student.Maths_Result);
result.English_Result = Math.Max(result.English_Result, student.English_Result);
result.Maltese_Result = Math.Max(result.Maltese_Result, student.Maltese_Result);
}
With these refactorings, the method shrinks from 22 lines (not counting empty lines and lines containing only a brace) to 7 lines.

Modifying values within a list

I've been trying to write a program which can scan a raw data file and normalize it for data mining processes, I've trying to read the data from the file and store it in a list this way:
public static List<Normalize> NF()
{
//Regex r = new Regex(#"^\d+$");
List<Normalize> N = new List<Normalize>();
StreamReader ss = new StreamReader(#"C:\Users\User\Desktop\NN.txt");
String Line = null;
while (!ss.EndOfStream) {
Line = ss.ReadLine();
var L = Line.Split(',').ToList();
N.Add(new Normalize { age = Convert.ToInt16(L[0]),
Sex = L[1],
T3 = Convert.ToDouble(L[2]),
TT4 = Convert.ToDouble(L[3]),
TFU = Convert.ToDouble(L[4]),
FTI = Convert.ToDouble(L[5]),
RC = L[6],
R = L[7]
});
}
return N;
}
}
struct Normalize {
public int age;
public String Sex;
public double T3;
public double TT4;
public double TFU;
public double FTI;
public String RC;
public String R;
}
At this moment I want to go through the list that I've made and categorize the data , similar to this :
var X= NF();
for (int i = 0; i < X.Count; i++) {
if (X[i].age > 0 && X[i].age <= 5) { // Change the X[i].age value to 1 }
else if (X[i].age > 5 && X[i].age <= 10) { // Change the X[i].age value to 2 }
...
}
But the compiler says X[i].[variable name] is not a variable and cannot be modified in this way. My question is, what would be an efficient way to perform this operation.
struct Normalize is a value type, not a reference type, therefore you cannot change its fields like that. Change it to class Normalize
Change struct Normalize to class Normalize and iterate with foreach loop. It's way cleaner.
You could also set variables to private and use getters/setters to check/set variable.
foreach (Normalize x in X)
{
if (x.getAge() > 0 && x.getAge() <= 5)
x.setAge(1)
...
}
Edit:
just saw you already got your answer
Modifying struct field is fine as long as it's a single entity (Given its a mutable struct). This is possible -
var obj = new Normalize();
obh.Age = 10;
But in your case you are accessing the struct using indexer from the list.
Indexer will return copy of your struct and modifying the value won't reflect it back to the list which ain't you want.
Hence compiler is throwing error to stop you from writing this out.
As Alex mentioned, you should go for creating class instead of struct if you want to modify it.
On a side note, its always advisable to have immutable structs instead of mutable structs.

Two lists with a relationship, how to display them to the user?

My problem is that I want to make a program that uses two lists, which is almost impossible for me to understand. Okay, so the deal is that I want to make a program where you first type in a city name and then the temperature for the city. This is where the relationship comes from.
I have started by making a "list class", which looks like this:
class citytemp
{
private string city;
private double temp;
public citytemp(string city, double temp)
{
this.city = city;
this.temp = temp;
}
public string City
{
get { return city; }
set { city = value; }
}
public double Temp
{
get { return temp; }
set { temp = value; }
}
}
Then I make the list in the program like this
List<citytemp> temps = new List<citytemp>();
Which all looks good to me. But when I'm trying to show the user the list nothing shows up. I use these lines to show it:
for (int i = 0; i > temps.Count; i++)
{
Console.WriteLine(temps[i].City, temps[i].Temp);
}
BTW: I add "things" to the list by these rows:
temps.Add(new citytemp(tempcity, temptemp));
...where tempcity and temptemp are temporary variables. They are only there to make it more simple for me to add them to the list, since I'm using a switch statement to add them to the list.
To make things more clear, my problem is that I don't know how I'm suppose to show the list to the user in the program.
Your problem is in the for loop. Change it to this
for (int i = 0; i < temps.Count; i++)
i.e. change the greater than > operator to a less than <
You have an error in your for loop.
for (int i = 0; i > temps.Count; i++)
it should be:
for (int i = 0; i < temps.Count; i++)
First of all, I'm not sure what you mean by "2 lists" as you only have one list in your code.
However, the problem you're having with "nothing shows up" is easy to fix.
This line of code:
for (int i = 0; i > temps.Count; i++)
Should be read as follows:
i = 0;
while (i > temps.Count)
{
... rest of your loop body here
i++;
}
if you read this, you'll notice that the second part of the for statement is not when to terminate but how long to keep going.
Change it to this, and you should be good:
for (int i = 0; i < temps.Count; i++)
^
+-- changed from >
I think a hashtable, specifically a dictionary would help you here:
var cityTemps = new Dictionary<string, double>();
cityTemps.Add("TestCity", 56.4);
foreach (var kvp in cityTemps)
Console.WriteLine("{0}, {1}", kvp.Key, kvp.Value);
In addition to the loop that has been mentioned, be careful withConsole.WriteLine because it takes in a String as first argument which it assumes is a format and object[] params as a second parameter. When you pass temps[i].City to it since its String it will think that its the format and temps[i].Temp is the parameter and won't display correctly.
What you want instead:
Console.WriteLine("City: {0} Temp: {1}", temps[i].City, temps[i].Temp);
Here I am using "City: {0} Temp: {1}" as the format for the string and the proper parameters.
This answer is to save you a headache later on wondering why only the city name is being displayed.

Check if array is null or empty?

I have some problem with this line of code:
if(String.IsNullOrEmpty(m_nameList[index]))
What have I done wrong?
EDIT: The m_nameList is underlined with red color in VisualStudio, and it says "the name 'm_nameList' does not exist in the current context"??
EDIT 2: I added some more code
class SeatManager
{
// Fields
private readonly int m_totNumOfSeats;
// Constructor
public SeatManager(int maxNumOfSeats)
{
m_totNumOfSeats = maxNumOfSeats;
// Create arrays for name and price
string[] m_nameList = new string[m_totNumOfSeats];
double[] m_priceList = new double[m_totNumOfSeats];
}
public int GetNumReserved()
{
int totalAmountReserved = 0;
for (int index = 0; index <= m_totNumOfSeats; index++)
{
if (String.IsNullOrEmpty(m_nameList[index]))
{
totalAmountReserved++;
}
}
return totalAmountReserved;
}
}
}
If m_nameList is null, that will still blow up, because it will try to find the element to pass to String.IsNullOrEmpty. You'd want:
if (m_nameList == null || String.IsNullOrEmpty(m_nameList[index]))
That's also assuming that index is going to be valid if m_nameList is non-null.
Of course, this is checking if the element of an array is null or empty, or if the array reference itself is null. If you just want to check the array itself (as your title suggests) you want:
if (m_nameList == null || m_nameList.Length == 0)
EDIT: Now we can see your code, there are two problems:
As Henk showed in his answer, you're trying to use a local variable when you need a field
You're also going to get an ArrayIndexOutOfBoundsException (once you've used a field) due to this:
for (int index = 0; index <= m_totNumOfSeats; index++)
That will perform m_totNumOfSeats + 1 iterations because of your bound. You want:
for (int index = 0; index < m_totNumOfSeats; index++)
Note that m_nameList[m_totNumOfSeats] is not valid, because array indexes
start at 0 in C#. So for an array of 5 elements, the valid indexes are 0, 1, 2, 3, 4.
Another option for your GetNumReserved method would be to use:
int count = 0;
foreach (string name in m_nameList)
{
if (string.IsNullOrEmpty(name))
{
count++;
}
}
return count;
Or using LINQ, it's a one-liner:
return m_nameList.Count(string.IsNullOrEmpty);
(Are you sure you haven't got it the wrong way round though? I would have thought reservations would be the ones where the name isn't null or empty, not the ones where it is null or empty.)
If it's the wrong way round, it would be this instead in LINQ:
return m_nameList.Count(name => !string.IsNullOrEmpty(name));
After Edit2:
You are defining m_nameList as a local variable of the constructor.
The rest of your code needs it as a field:
class SeatManager
{
// Fields
private readonly int m_totNumOfSeats;
private string[] m_nameList;
private double[] m_priceList;
// Constructor
public SeatManager(int maxNumOfSeats)
{
m_totNumOfSeats = maxNumOfSeats;
// Create arrays for name and price
m_nameList = new string[m_totNumOfSeats];
m_priceList = new double[m_totNumOfSeats];
}
....
}
To avoid the error you can perform some pre conditions in the if, like these :
if(m_nameList == null || index < 0 || m_nameList.Length < index || String.IsNullOrEmpty(m_nameList[index]))
This should works fine(without causing error) in almost any conditions ...

C#: is there a way to determine if a range of elements is empty?

I'm wondering if theres a method to determine whether a certain range of array elements is empty or not. for example, if the array was initialized with 10 elements having the values "", then if data was later assigned to elements 5, 7, 9; could i test if elements 0-3 were empty, or rather contained an empty string ""?
array.Skip(startIndex).Take(count).All(x => string.IsNullOrEmpty(x));
So, if you are trying to check elements 0-3:
array.Skip(0).Take(4).All(x => string.IsNullOrEmpty(x));
For clarity, I left Skip in there.
Edit: made it Take(4) instead of 3 as per Jonathan's comment in the other answer (and now Guffa's comment in mine. ;) ).
Edit 2: According to comments below, the OP wanted to see if any of the elements matched:
array.Skip(0).Take(4).Any(x => string.IsNullOrEmpty(x));
So changed All to Any.
bool is0to3empty = myArrayOfString.Skip(0).Take(4).All(i => string.IsNullOrEmpty(i));
The most straight forward and efficient would be to simply loop through that part of the array:
bool empty = true;..
for (int i = 0; i <= 3; i++) {
if (!String.IsNullOrEmpty(theArray[i])) {
empty = false;
break;
}
}
if (empty) {
// items 0..3 are empty
}
Another alternative would be to use extension methods to do the looping:
bool empty = theArray.Take(4).All(String.IsNullOrEmpty);
Create this extension class and you can call it from any string array:
public static class IsEmptyInRangeExtension
{
public static bool IsEmptyInRange(this IEnumerable<string> strings, int startIndex, int endIndex)
{
return strings.Skip(startIndex).TakeWhile((x, index) => string.IsNullOrEmpty(x) && index <= endIndex).Count() > 0;
}
}

Categories

Resources