Matching lisboxes items and creating result - c#

I am creating an Exam system in c#. I am creating result, i have answers in a listbox1 and correct answers in another listbox2, my issue is values in the listboxes should be compared and result should be generated on its base. If half the values match student is pass otherwise fail.
My code for this is following but it does not work.
for(int intCount = 0; intCount < listBoxSanswers.Items.Count;intCount++)
{
for (int intSubCount = 0; intSubCount < listBoxActAnswers.Items.Count; intSubCount++)
{
if (listBoxActAnswers.Items[intCount].ToString() == listBoxActAnswers.Items[intSubCount].ToString())
{
listBox3.Items.Add(listBoxActAnswers.Items[intCount].ToString());
}
}
}

If you want to use your approach, than you have to change one of the two lists to listBoxSanswers
If you want a shorter way, without the loops, you can try this line:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<string>().ToList().Intersect(listBoxSanswers.Items.Cast<string>().ToList()).ToArray());
EDIT:
Oh okay, so you have a DataTable as a DataSource.
Than you can do it this way:
listBox3.Items.AddRange(listBoxActAnswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList().Intersect(listBoxSanswers.Items.Cast<DataRowView>().Select(r => r[0]).ToList()).ToArray());
Maybe you should adapt Select(r => r[0]) to the right column which is your DisplayMember.

Related

DataRow.SetField() gives a null ref exception when adding data to a column I previously deleted then added back

UPDATE
I think I have found what is causing the issue here https://stackoverflow.com/a/5665600/19393524
I believe my issue lies with my use of .DefaultView. The post thinks when you do a sort on it it is technically a write operation to the DataTable object and might not propagate changes made properly or entirely. It is an interesting read and seems to answer my question of why passing valid data to a DataRow is throwing this exception AFTER I make changes to the datatable
UPDATE:
Let me be crystal clear. I have already solved my problem. I would just like to know why it is throwing an error. In my view the code should work and it does.. the first run through.
AFTER I have already deleted the column then added it back (run this code once)
When I debug my code line by line in Visiual studio and stop at the line:
data.Rows[i].SetField(sortColumnNames[k], value);
the row exists
the column exisits
value is not null
sortColumnNames[k] is not null and contains the correct column name
i is 0
Yet it still throws an exception. I would like to know why. What am I missing?
Sorry for the long explanation but this one needs some context unfortunately.
So my problem is this, I have code that sorts data in a DataTable object by column. The user picks the column they want to sort by and then my code sorts it.
I ran into an issue where I needed numbers to sort as numbers not strings (all data in the table is strings). eg (string sorting would result in 1000 coming before 500)
So my solution was to create a temporary column that uses the correct datatype so that numbers get sorted properly and the original string data of the number remains unchanged but is now sorted properly. This worked perfectly. I could sort string numeric data as numeric data without changing the formatting of the number or data type.
I delete the column I used to sort afterwards because I use defaultview to sort and copy data to another DataTable object.
That part all works fine the first time.
The issue is when the user needs to do a different sort on the same column. My code adds back the column. (same name) then tries to add values to the column but then I get a null reference exception "Object not set to an instance of an object"
Here is what I've tried:
I've tried using AcceptChanges() after deleting a column but this did nothing.
I've tried using column index, name, and column object returned by DataTable.Columns.Add() in the first parameter of SetField() in case it was somehow referencing the "old" column object I deleted (this is what I think the problem is more than likely)
I've tried changing the value of the .ItemArray[] directly but this does not work even the first time
Here is the code:
This is the how the column names are passed:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
data.DefaultView.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = data.DefaultView.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
This is where the temp columns are added:
private string[] AddSortColumns(string[] items)//adds columns to data that will be used to sort
//(ensures numbers are sorted as numbers and strings are sorted as strings)
{
string[] sortColumnNames = new string[items.Length];
for (int k = 0; k < items.Length; k++)
{
int indexOfOrginialColumn = Array.IndexOf(columns, items[k]);
Type datatype = CheckDataType(indexOfOrginialColumn);
if (datatype == typeof(double))
{
sortColumnNames[k] = items[k] + "Sort";
data.Columns.Add(sortColumnNames[k], typeof(double));
for (int i = 0; i < data.Rows.Count; i++)
{
//these three lines add the values in the original column to the column used to sort formated to the proper datatype
NumberStyles styles = NumberStyles.Any;
double value = double.Parse(data.Rows[i].Field<string>(indexOfOrginialColumn), styles);
bool test = data.Columns.Contains("QtySort");
data.Rows[i].SetField(sortColumnNames[k], value);//this is line that throws a null ref exception
}
}
else
{
sortColumnNames[k] = items[k];
}
}
return sortColumnNames;
}
This is the code that deletes the columns afterward:
private void RemoveSortColumns(string[] columnsToRemove)
{
for (int i = 0; i < columnsToRemove.Length; i++)
{
if (columnsToRemove[i].Contains("Sort"))
{
sortedData.Columns.Remove(columnsToRemove[i]);
}
}
}
NOTE:
I've been able to fix the problem by just keeping the column in data and just deleting the column from sortedData as I use .Clear() on the sorted table which seems to ensure the exception is not thrown.
I would still like an answer though as to why this is throwing an exception. If I use .Contains() on the line right before the one where the exception is thrown is says the column exists and returns true and in case anyone is wondering the params sortColumnNames[k] and value are never null either.
Your problem is probably here:
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
}
}
}
If you have 2 columns, and the first one matches the if, you will never look at the second.
This is because it will run:
i = 0
is i < columns.Count which is 2 => yes
is col[0].Contains("sort") true => yes
remove col[0]
i = 1
is i < columns.Count which is 1 => no
The solution is to readjust i after the removal
private void RemoveSortColumns()
{
for (int i = 0; i < data.Columns.Count; i++)
{
if (data.Columns[i].ColumnName.Contains("Sort"))
{
data.Columns.RemoveAt(i);
sortedData.Columns.RemoveAt(i);
i--;//removed 1 element, go back 1
}
}
}
I fixed my original issue by changing a few lines of code in my SortByColumn() method:
private void SortByColumn()
{
if (cbAscDesc.SelectedIndex != -1)//if the user has selected ASC or DESC order
{
//clears the datatable object that stores the sorted defaultview
sortedData.Clear();
//grabs column names the user has selected to sort by and copies them to a string[]
string[] lbItems = new string[lbColumnsToSortBy.Items.Count];
lbColumnsToSortBy.Items.CopyTo(lbItems, 0);
//adds temp columns to data to sort numerical strings properly
string[] itemsToSort = AddSortColumns(lbItems);
//creates parameters for defaultview sort
string columnsToSortBy = String.Join(",", itemsToSort);
string sortDirection = cbAscDesc.SelectedItem.ToString();
DataView userSelectedSort = data.AsDataView();
userSelectedSort.Sort = columnsToSortBy + " " + sortDirection;
//copies the defaultview to the sorted table object
sortedData = userSelectedSort.ToTable();
RemoveSortColumns(itemsToSort);//removes temp sorting columns
}
}
Instead of sorting on data.DefaultView I create a new DataView object and pass data.AsDataView() as it's value then sort on that. Completely gets rid of the issue in my original code. For anyone wondering I still believe it is bug with .DefaultView in the .NET framework that Microsoft will probably never fix. I hope this will help someone with a similar issue in the future.
Here is the link again to where I figured out a solution to my problem.
https://stackoverflow.com/a/5665600

c# collections and re-numbering not working as expected

Hi i'm trying to setup simple test data.
I simply want to take a collection which is smallish and make it bigger by add itself multiple times.
After I;ve added them together i want to re-number the property LineNumber
so that there are no duplicates and that it goes in order. 1,2,3,4....
no matter what i try it doesn't seem to work and i cant see the mistake.
var sampleTemplateLine = dataContext.TemplateFileLines.ToList();
*//tired this doesnt work either*
//List<TemplateFileLine> lineRange = new List<TemplateFileLine>();
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
//lineRange.AddRange(sampleTemplateLine);
var allProducts = sampleTemplateLine
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.Concat(sampleTemplateLine)
.ToList();
int i = 1;
foreach (var item in allProducts)
{
item.LineNumber = i;
i++;
}
this doesnt seem to work either
//re-number the line number
var total = allProducts.Count();
for (int i =0; i < total; i++)
{
allProducts[i].LineNumber = i+1;
}
PROBLEM: below RETURN 4 when i'm expecting 1
var itemthing = allProducts.Where(x => x.LineNumber == 17312).ToList();
You are adding the same objects multiple times. You wold have to add new objects or clone the ones you have.
The problem is they are pointing the same object. So if you change a property it changes all the pointed objects at the same
You can use Clone method if it exist, if not you can create your own Clone method like in this question.

Set table values equal to value in other table

I have one table which functions as a viewModel. From this, it is supposed to be the source of my binding in my view. My object is to update this table/viewModel based on two other tables. I have one property from each of the table checklistTable and AnswerTable that is to be set in my viewModel checkViewModel.
At the moment I am querying the current elements I need from each of the tables, and trying to update the viewModel.
CodeBehind:
//Curent descriptions to be set in the viewModel
var currentDescription = (_check.Where(s => currentElements.Contains(s.defineId)).Select(s=> s.Description).ToList());
//Current colors to be set in the viewModel
var currentColors = (from current in _answer
where current.questionId == currentCheckId &&
current.buildingId == currentBuildingId
orderby current.dateReported descending
select current.backgroundColor).ToList();
After retrieving theese values, i try to update my viewModel, and this is where things go wrong:
for (int i =0 ; i < currentDescription.Count() - 1; i++)
{
currentViewTable.Description = currentDescription[i];
}
for (int i =0 ; i < currentColors.Count() - 1; i++)
{
currentViewTable.backgroundColor.Insert(i,currentColors[i]);
}
I get the error: reference not set to an instance of an object. Are there better ways to update my viewModel, of or any tips on what I am doing wrong?
I would first recommend to use a foreach loop instead of a regular for loop. I find it much easier to use foreach when working with lists, this will eliminate any clear "off by one" errors usually.
foreach (var description in currentDescription)
{
//do something given each element in the list
}
This seems to be a data integrity issue from what I can gather just looking at your code and given what the error is stating. I would recommend going into debug mode and looking in the Lists that you have generated to see if there are any indexes where the objects in your two lists are null, and handle these instances accordingly.

removing specific string values from a List

I have a List of string values and and some of the values contains xxx or XXX in front.
xxxRed
xxxYellow
xxxxCareful with that axe Eugene!
xxxxxxdedicum aceasta frumoasa Melodia
xxxxLeaders
xxxxWorking Around - titles
XXXXXNothing To Fear
xxxxAvoiding standards
xxxFirst Aid
List<string> lstTitles = new List<string>();
This is what I have tried
for (int i=0; i < lstTitles.Count; i++)
{
string title = lstTitles[i].ToLower().Trim();
if (title[0] == 'x')
{
lstTitles.Remove(lstTitles[i]);
}
}
Problem I have is that only some of the values are removed but not all of them.
Is there perhaps a better way of removing these values?
Use RemoveAll method
lstTitles.RemoveAll(s => s[0] == 'x' || s[0] == 'X');
and you may want to use StartsWith instead of comparing first char.
lstTitles.RemoveAll(s => s.StartsWith("x",StringComparison.InvariantCultureIgnoreCase));
Problem I have is that Only some of the values are removed but not all of them.
Because you're skipping items. When you call Remove(), the next item will be at index i, but you'll increase i in the next loop.
It can be solved by iterating over a copy of the list, and removing unwanted items in the original:
foreach (var item in lstTitles.ToList())
{
if (item.StartsWith("x", StringComparison.InvariantCultureIgnoreCase))
{
lstTitles.Remove(item);
}
}
Though this involves creating a copy of the list, which isn't really useful, as well as calling Remove() which itself is far from performant.
So you could invert your for-loop, to remove the last items first which doesn't change the indexing for unprocessed items:
for (int i = lstTitles.Count - 1; i > 0; i--)
{
if (lstTitles[i].StartsWith("x", StringComparison.InvariantCultureIgnoreCase))
{
lstTitles.RemoveAt(i);
}
}
But as #I4V points out, all of this logic already is in List<T>.RemoveAll(), which is nicer to read and probably optimized for some edge cases, so there's little use to hand-code it again.
That's because your skipping values.
Suppose your list contains ['xVal1', 'xVal2', 'val3', 'xVal4', 'val5']. At first your i is 0, and you look at list[0], which is 'xVal1', so you remove it.
Now your list contains ['xVal2', 'val3', 'xVal4', 'val5'], and your i is 1. So you look at list[1] which is 'val3'. You ignored xVal2 !
You can start at the back of the list and go to the front, although you will still have a potential bug in case there are identical values you remove.
A shorter way would be to use LINQ:
var newList = lstTitles.Where(title=>!title.StartsWith('xxx'))
Instead of ToLower you should use the overload of StartsWith which allows to pass a StringComparison.OrdinalIgnoreCase.
Then use List.RemoveAll which is the most readable, most efficient and shortest approach:
lstTitles.RemoveAll(s => s.TrimStart().StartsWith("x", StringComparison.OrdinalIgnoreCase));
Demo
I think, you'd better just create a new list this way
list = list
.Where(i => ! i.StartsWith("xxx", StringComparison.InvariantCultureIgnoreCase))
.ToList();
It would have a O(n) complexity whereas, trying to remove then 1 by 1 would be in O(n^2).
This could work also :
list.RemoveAll(i => i.StartsWith("xxx", StringComparison.InvariantCultureIgnoreCase));
Handles all cases and without a second list.

Alternative to nested loop for updating a collection

I am getting a list of products from my database as a BindingList. I would like to update the quantities some of the products in that list using another list of items already selected by the user.
The idea is when the user brings up a fresh list of products from the database. The list will show the quantities of products that have already been selected from a previous search.
I have come up with the following nested loop. It works but wouldn't scale well as a search in the database could yield a large list that has to be traversed. How do you guys think I could improve this?
Also, I skimmed through the class where they taught the Big-O notation. What's the complexity of the below solution?
Thanks.
for (int i = 0; i < dbProducts.Count; i++ )
{
for (int j = 0; j < GlobalVars.productList.Count; j++)
{
EposProduct selectedProduct = GlobalVars.productList.ElementAt(j);
EposProduct dbProduct = dbProducts.ElementAt(i);
if(selectedProduct.ProductID == dbProduct.ProductID)
{
dbProduct.Quantity = selectedProduct.Quantity;
}
}
}
Your current approach with two nested loops is at best O(n^2) not counting the inner ElementAt method calls. Use a dictionary instead to do this in O(n):
var gbMap = GlobalVars.productList.ToDictionary(x => x.ProductId,
x => x.Quantity);
foreach(var product in dbProducts)
{
if(gbMap.ContainsKey(product.ProductId))
product.Quantity = gbMap[product.ProductId];
}

Categories