Remove duplicates and put the list into a listbox - c#

This is an uni assignment and I am having problem with part of it. This is the code;
namespace Assignment_1
{
public partial class Classifier : System.Web.UI.Page // We are using a web form as stated
{
protected void Page_Load(object sender, EventArgs e) // No variables are initiated for the beginning
{
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (this.TextBox1.Text != "")
{
List<string> numbersText = this.TextBox1.Text.Split(',').ToList<string>();
foreach (var item in numbersText)
{
int num = int.Parse(item);
if (RadioButtonList1.SelectedValue == "Both")
{
if (num % 2 == 0)
{
if (CheckBoxDuplicate.Checked == true)
{
List<int> evenNumbers = new List<int>();
evenNumbers.Add(num);
List<int> distinctEvenNumbers = evenNumbers.Distinct().ToList();
ListBoxEvenNumbers.DataSource = distinctEvenNumbers;
}
else
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
else
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Even")
{
if (num % 2 == 0)
{
//Put the results into the respective boxes
ListBoxEvenNumbers.Items.Add(num.ToString());
}
}
if (RadioButtonList1.SelectedValue == "Odd")
{
if (num % 2 == 1)
{
//Put the results into the respective boxes
ListBoxOddNumbers.Items.Add(num.ToString());
}
}
Let me explain the question and what I have done. User inserts list of numbers into a text box and then has 3 options (radiolistbutton). He can list even, odd or both type of numbers. They display in even and odd listboxes(2 listboxes). I have done up to this part.
There is a checkbox to remove duplicates and the user can check it if he wishes to. If the button is checked, the code should remove the duplicates. I tried to do this part in the 4th "if-else" "if (CheckBoxDuplicate.Checked == true)". The way I understand it, I check if the number is even and then check the CheckboxDuplicate button. if it is checked I put the values in a new list and then delete repeated values. Then put into EvenNumbers listbox. For some reason, this part doesn't work.
If you would like to help me, please don't post just your answer. This is my first project in C# and it is difficult for me to understand an elegant solution yet. If you have time, please check my code and let me know where I made a mistake. Thanks for your time in advance.

Sprinkle a bit of linq magic on it, and you're done.
var my_list = new List<int>{1,2,3,4,5,5,6,7};
var pair = my_list.Where (n => n%2 ==0); // 2,4,6
var odd = my_list.Where (n => n%2 ==1); // 1,3,5,5,7
var unique_pair = pair.Distinct(); // stays the same
var unique_odd = odd.Distinct(); // 1,3,5,7
From here is just adding it to your appropriate calls and GUI containers
From your comment, here are a couple of things:
Change the if to if - else if, since only one will apply.
you can do it the way you do, but it's not the most efficient. If you go that way, you'll have to figure out which numbers you've added in order to not have duplicates.
alternatively, you can simply create the lists like I've done in the code above, and then assign them at the end. It'll save you time and code.
Here's some more help, with no code, since I believe i covered it.
Step 1: get the user input, and create a list of ints. (call it: input_list).
Step 2: According to what he chose (even, odd, both), you want to assign to each listbox, a list of numbers. Look at my above code, it'll do that bit for you.
Step 3: If user choses unique, you pass to those listboxes the Distinct list, again, look at my above code for that.
You can apply the unique on the event of the checkbox being selected if you prefer.
Notes:
Keep the list of ints (the input_list) as a variable, so you don't need to parse it whenever he changes his selection.

public enum PairOddEnum
{
Evens,
Odds,
Both
}
public void BindControl(PairOddEnum type)
{
if (this.textBox1.Text != "")
{
List<string> numbersText = this.textBox1.Text.Split(',').ToList<string>();
var evens = numbersText.Where(t => int.Parse(t) % 2 == 0).Distinct();
var odds = numbersText.Where(t => int.Parse(t) % 2 == 1).Distinct();
if (type == PairOddEnum.Evens)
{
ListBoxEvenNumbers.DataSource = evens.ToList();
}
else if (type == PairOddEnum.Odds)
{
ListBoxOddNumbers.DataSource = odds.ToList();
}
else
{
ListBoxEvenNumbers.DataSource = evens.ToList();
ListBoxOddNumbers.DataSource = odds.ToList();
}
}
}
protected void ButtonClassify_Click(object sender, EventArgs e)
{
if (RadioButtonList1.SelectedValue == "Both")
{
BindControl(PairOddEnum.Both);
}
if (RadioButtonList1.SelectedValue == "Even")
{
BindControl(PairOddEnum.Evens);
}
if (RadioButtonList1.SelectedValue == "Odd")
{
BindControl(PairOddEnum.Odds);
}
}

Related

Best way to compare variables between instances of the same class?

This is a game based on certain elemental combos, I'm trying to find a way to have two elements compare to each other in order to reduce hard coding. I do have a working solution provided at the end, but I'm trying to learn any ways that would be more simple, straight forward, and readable.
//Wizard is a class
Wizard player1;
Wizard player2;
player1.health = 3;
player2.health = 3;
//Elements is an enum that allows fire/water/air to be selected
player1.elementSelected = Elements.fire;
player2.elementSelected = Elements.water;
Wizard[] bothPlayers = { player1, player2 };
I want to search for the active element in bothPlayers so I can effect the Player's health. I know this doesn't work, but I was wondering if I could do something like:
if (bothPlayers.Contains(Wizard.elementSelected.Elements.fire) && bothPlayers.Contains(Wizard.elementSelected.Elements.water))
Alternatively I was thinking of just setting it to a new array but that wont let me call back to effect player health unless I set them up to a new variable like:
Elements[] bothSelectedElements = { player1.elementSelected, player2.elementSelected };
if (bothSelectedElements.Contains(Elements.fire) && bothSelectedElements.Contains(Elements.water))
{
Wizard playerWithFire; // = player who selected fire. Can't set without hardcoding
Wizard playerWithWater; // = player who selected water. Can't set without hardcoding
playerWithFire.health--;
playerWithWater.waterStrength++;
}
//CURRENT WORKING SOLUTION
//set each Wizard container to null at the start of each check
Wizard fire = null;
Wizard water = null;
Wizard air = null;
//add check to make sure same elements aren't selected. Then assign the players to the containers
foreach (Wizard player in bothPlayers)
{
if (player.elementSelected == Elements.fire)
{
fire = player;
}
if (player.elementSelected == Elements.water)
{
water = player;
}
if (player.elementSelected == Elements.air)
{
air = player;
}
}
//then do the actual check
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
//repeat with other if statement comparisons
This is a fiddly problem, but thankfully you are working with a language that has all the tools you need to hide the complexity away.
var elementWizards = wizards.GroupBy(w => w.elementSelected).ToDictionary(g => g.Key);
var elements = elementWizards.Keys.ToHashSet(); // gives us access to SetEquals
if (elements.SetEquals(new[] { Elements.Fire, Elements.Water }))
{
foreach (var wizard in elementWizards[Elements.Fire]) wizard.health--;
foreach (var wizard in elementWizards[Elements.Water]) wizard.waterStrength++;
}
else if (elements.SetEquals(new[] { Elements.Earth, Elements.Fire }))
{
// more effects...
}
Note that SetEquals doesn't care about the order of items, so you don't need to worry about handling water/fire instead of fire/water.
Footnote: in the real world I would define some static HashSet<Element> objects and call if(foo.SetEquals(elementWizards.Keys)), but I kept things simple for this answer.
Solution with Linq:
Check if there are water and fire wizard (with Linq)
Foreach to search and add health etc...
if (bothPlayers.Count(wizard => wizard.elementSelected == Elements.fire) > 0 &&
bothPlayers.Count(wizard => wizard.elementSelected == Elements.water) > 0)
{
bothPlayers.ForEach(wizard =>
{
var _ = wizard.elementSelected == Elements.water ? wizard.waterStrength++ :
wizard.elementSelected == Elements.fire ? wizard.health-- : 1;
});
}
you can change the wizards you are searching for and the properties that depends on it
There are multiple ways of finding item in a collection. For example :
// using Array.Find method
Wizard fire = Array.Find(bothPlayers, player => player.elementSelected == Elements.fire);
// or using System.Linq FirstOrDefault extension
Wizard water = bothPlayers.FirstOrDefault(player => player.elementSelected == Elements.water);
if (fire != null && water != null)
{
fire.health--;
water.waterStrength++;
}
For more than two players, a Lookup can be used to separate the players into groups :
var lookup = bothPlayers.ToLookup(player => player.elementSelected);
if (lookup.Contains(Elements.fire) && lookup.Contains(Elements.water))
{
foreach (Wizard fire in lookup[Elements.fire] ) fire.health--;
foreach (Wizard water in lookup[Elements.water]) water.waterStrength++;
}

How to create a method where multiple controls are false in an "if" statement for C#?

What is the best way to simplify this (via method creation or otherwise):
if ((radioButton1.Checked == false) && (radioButton2.Checked == false) && (radioButton1.Checked == false) && ...more similar controls... && ((radioButton99.Checked == false))
{
MessageBox.Show("Please select an option!);
}
Thank you for your consideration. Apologies for any inconvenience or grievances caused.
You could put all those controls in a List and then check whether any of the controls in the list is checked. This can be done in several ways. Below examples of two of those.
Example using loop:
bool optionSelected = false;
foreach(var control in controls) // the List is in this case called controls
{
if(control.Checked)
{
optionSelected = true;
}
}
// Check the boolean
Example using System.Linq:
if(!controls.Any(c => c.Checked))
{
MessageBox.Show("Please select an option!);
}
You need to add your controls into a public collection to simply iterate them.
if you have a bunch of the same type controls, it's better to put them into a array in your form's constructor :
CheckBox[] MyBoxes = new CheckBox[]{ check01, check02 , ... }
// MyBoxes is filled at Form_Load and it's usable in throughout of the form
bool result = true;
for(int i=0; i<MyBoxes.Length; i++)
{
if (MyBoxes[i].Checked == false)
{ result = false; break; }
}
another solution is to iterate whole controls on the form:
bool result = true;
for(int i=0; i<this.Controls.Count; i++)
{
if (this.Controls[i] is CheckBox)
{
if ((this.Controls[i] as CheckBox).Checked == false)
{ result = false; break; }
}
}
If you have lots of controls (in this case - RadioButtons), then you can use the following trick - Tag property.
To summarize:
1) Set Tag property with the some string to differentiate from other controls (in particular, there may be the case when not all the controls of one type must be processed).
2) Collect these controls with defined string in Tag and process them.
In your particular case, you can set the string ToCheck in Tag and then check whether all RadioButtons are checked:
// Collect controls with defined string in Tag property
var radioButtons = this.Controls
.OfType<RadioButton>() //Filter controls by type
.Where(rb => ((string)rb.Tag) == "ToCheck"); //Get controls with defined string in Tag
// Check whether all RadioButtons are checked
bool allChecked = radioButtons.All(rb => rb.Checked);
The possible approach could be to introduce variable for condition validation
bool optionIsSelected = ((radioButton1.Checked == true) || (radioButton2.Checked == true)...;
And then use it in if:
if (!optionIsSelected)
{
MessageBox.Show("Please select an option!);
}
private bool AreAllFalse(params bool[] thingsToCheck)
{
return things.All(t => !t);
}
Usage
if (AreAllFalse(radioButton1.Checked,radiobutton2.Checked, etc)
{
//Do what you want
}
The benefit of using the params key word is that it creates the array for you when you call the method. It also allows you to pass in just one thing to check if you want. While that would be quite pointless here, it just keeps things felxible and is something worthwhile knowing

A function that gets two variables and their order doesn't matter

Sorry that I couldn't explain better in the question header. I'll try to define my question better here.
That's what I am doing - in my game, there is an option to use items on one another. For each pair of items, the game performs an action.
Currently, there are two variables that game uses for this: "ItemUsed" and "ItemUsedOn". First, the game chooses first item - its id goes to "ItemUsed", then he chooses second item, it's id goes to "ItemUsedOn". Then, there is a void that defines a specific action.
A short example of code:
if (ItemUsed == "itm_cable")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "To achieve what?";
else if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage it";
else if (ItemUsedOn == "itm_glass")
SubMain.ItemsMergedText = "I don't need to cut the cable";
}
if (ItemUsed == "itm_book_electronics")
{
if (ItemUsedOn == "itm_towel")
SubMain.ItemsMergedText = "Why?";
if (ItemUsedOn == "itm_wet_towel")
SubMain.ItemsMergedText = "No, water will damage the book";
else if (ItemUsedOn == "itm_soap")
SubMain.ItemsMergedText = "Wrong plan";
else if (ItemUsedOn == "itm_hair")
SubMain.ItemsMergedText = "It won't help";
}
And there are many such pairs.
However, there is a problem with this approach. When two items are combined, their order doesn't matter, for example "itm_toolbox" can be "ItemUsed" and "itm_cable" can be "ItemUsedOn", but also can be the other way around, the result will be the same. How can this be achieved?
I did try using this in every "larger" if:
else
CombineItems(ItemUsedOn, "itm_book_edda");
But this doesn't always work, and I couldn't find why.
So, what I am looking for is function that gets 2 variables:
void CombineItems(string Item1, string Item2)
And then give same result in those cases:
if (Item1="Tomato")&&(Item2="Cucumber")
Item3="Salad"
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
My question: is there an easier way for this, without using so many "if's"?
Thank you in advance,
Evgenie
I'd recommend that you create yourself an eg UnorderedPair type, which overrides the behaviour of .Equals and/or ==, such that:
new UnorderedPair("Tomato", "Cumcumber") == new UnorderedPair("Cucumber", "Tomato");
Then you can reduce all of your if statements down to a simple dictionary:
combinedItems = new Dictionary<UnderedPair, string>
{
[new UnorderedPair("Tomato", "Cumcumber")] = "Salad",
[new UnorderedPair("Bread", "Filling")] = "Sandwich",
...
};
and your code for determining the text can then just be:
SubMain.ItemsMergedText = combinedItems[new UnorderedPair(ItemUsed, ItemUsedOn)];
You can use params(to treat them as array) and LINQ. Store the salad-items in an array too:
private string[] SaladItems = new string[] { "Tomato", "Cucumber" };
string CombineItems(params string[] Items)
{
bool isSalad = SaladItems.Length == Items.Length && !SaladItems.Except(Items).Any();
if (isSalad) return "Salad";
// other types ...
return null; // no match, exception?
}
Side-Note: if you want to accept "tomato"(so ignore the case) use:
!SaladItems.Except(Items, StringComparer.InvariantCultureIgnoreCase).Any()
You could make this endless list of comparisons a lot smaller if you structure the logic into separate parts.
First, I would create a list or so to keep the items and their result:
var items = new[] { new { Item1 = "tomato", Item2 = "Cucumber", Result = "Salad" }
, new { Item1 = "tomato2", Item2 = "Cucumber2", Result = "Salad2" }
};
Then find the matching item:
var match = items.FirstOrDefault
(itm => (itm.Item1 == Item1 && itm.Item2 == Item2)
|| (itm.Item1 == Item2 && itm.Item2 == Item1)
);
There are two approaches that can keep down the amount of duplication throughout the code:
Sort the items so they're alphabetical order:
if(Item1 > Item2) {
var tmp = Item2;
Item2 = Item1;
Item1 = tmp;
}
now all of your remaining code can assume that Item1 values will always be earlier alphabetically than Item2, so you'd only write:
if (Item1="Cucumber")&&(Item2="Tomato")
Item3="Salad"
Or, you can, after exhausting all of your options (again, written only once), call your method recursively with the parameters swapped:
void TakeAction(Item1, Item2) {
.
.
.
/* No matches */
else {
TakeAction(Item2,Item1);
}
}
var items= new HashSet<string>();
items.Add("tomato");
items.Add("Cucumber");
if(items.Contains("tomato") && items.Contains("Cucumber")) //Order does **NOT** matter
Item3="Salad";

Adding to columns based on length

I have a ListView with two columns, Boxes and Files. I'm adding items to a list of strings, and then populating the ListView with that list of strings. I want to make it so all items that are 8 characters long go into the Boxes column and all items that are 9 characters go into the Files column. So far, I've tried to iterate through using a for loop and utilize an if else statement to add the items, but I seem to be doing something wrong. Here's my current code:
public void PopulateItemsList()
{
BoxAndFileList.Items.Clear();
ScanIdBox.Text = string.Empty;
for (int i = 0; i < BoxNumberRepository._boxAndFileList.Count; i++)
{
var item = BoxNumberRepository._boxAndFileList.Item[i];
if (item.Length == 8)
{
BoxAndFileList.Items.Insert(0, item);
}
else
{
BoxAndFileList.Items.Insert(1, item);
}
}
}
I'm iterating through my list (_boxAndFileList) and trying to utilize Insert() to insert items into the specific index of the columns (Boxes is 0, Files is 1). I can clearly see that Item is a legitimate property of a string list, yet VS keeps saying that list contains no definition of it. How can I go about doing this? And also, I haven't received outside feedback on this way of doing things yet, so if there's a better way, please let me know.
Edit: BoxNumberRepository is a class that news up a list called _boxAndFileList. Code below:
public class BoxNumberRepository : Scan_Form
{
public static List<string> _boxAndFileList = new List<string>();
public void AddItem(string item)
{
_boxAndFileList.Add(item);
}
public void Delete(string item)
{
_boxAndFileList.Remove(item);
}
public IEnumerable<string> GetAllItems()
{
return _boxAndFileList;
}
}
Thanks to Alessandro D'Andria for that suggestion. That was correct. However, all the items are still just adding to the first column, even if they're 9 characters. How can I get 9 character items to add to the second column?
The problem that you are having is that you have to add both the box and file to the list item at the same time.
EDIT: Changed cartesian product to a left outer join.
EDIT: Added comments and fixed a syntax bug
private List<string> _boxAndFileList = new List<string> { "12345678", "123456789", "1234", "123456778" };
public void PopulateItemsList()
{
//clear the list
BoxAndFileList.Items.Clear();
//add the labels to the top of the listbox
BoxAndFileList.Columns.Add("Boxes");
BoxAndFileList.Columns.Add("Files");
//set the view of the list to a details view (important if you try to display images)
BoxAndFileList.View = View.Details;
//clear scan id box
ScanIdBox.Text = string.Empty;
//get all the items whos length are 8 as well as a unique id (index)
var boxes = _boxAndFileList.Where(b => b.Length == 8).Select((b, index) => new { index, b }).ToList();
//get all the items whos length are NOT 8 as well as a unique id (index)
var files = _boxAndFileList.Where(f => f.Length != 8).Select((f, index) => new { index, f }).ToList();
//join them together on their unique ids so that you get info on both sides.
var interim = (from f in files
join b in boxes on f.index equals b.index into bf
from x in bf.DefaultIfEmpty()
select new { box = (x == null ? String.Empty : x.b), file = f.f });
//the real trick here is that you have to add
//to the listviewitem of type string[] in order to populate the second, third, or more column.
//I'm just doing this in linq, but var x = new ListViewItem(new[]{"myBox", "myFile"}) would work the same
var fileboxes = interim.Select(x => new ListViewItem(new []{ x.box, x.file})).ToArray();
//add the array to the listbox
BoxAndFileList.Items.AddRange(fileboxes);
//refresh the listbox
BoxAndFileList.Refresh();
}
Your _boxAndFileList is a List<string> so you should be declare item as string type instead var type:
string item = BoxNumberRepository._boxAndFileList.Item[i];
All your code should be like this:
public void PopulateItemsList()
{
BoxAndFileList.Items.Clear();
ScanIdBox.Text = string.Empty;
for (int i = 0; i < BoxNumberRepository._boxAndFileList.Count; i++)
{
string item = BoxNumberRepository._boxAndFileList.Item[i];
if (item.Length == 8)
{
BoxAndFileList.Items.Insert(0, item);
}
else
{
BoxAndFileList.Items.Insert(1, item);
}
}
}

How do I compare a string to all items in a listbox?

I'm creating an application that allows the user to store information about their classes. There is a button in my application that allows the user to enter information and if it matches any item in the listBox then it should display information about it.
I can only get it to work if I specify a particular item by position (e.g. Items[0]) of the listBox and then convert it to a string. My aim is to compare all items in the listBox.
private void button3_Click(object sender, EventArgs e)
{
if (listBox2.Items[0].ToString() == "PersonalInfo")
{
label.Text = "test";
}
}
You need to loop through all of the items in the list. Try something like this:
foreach(var item in listBox2.Items)
{
if(item.ToString() == stringToMatch)
{
label.Text = "Found a match";
}
}
An alternate, simpler implementation (which will stop if/when it finds a match instead of continuing to check every item) would be
if(listBox2.Items.Any(item => item.ToString() == stringToMatch))
{
label.Text = "Found a match";
}
Write a loop to check each item
foreach(var item in listBox2.Items)
{
if (item.ToString()== "PersonalInfo")
{
label.Text = "test";
break; // we don't want to run the loop any more. let's go out
}
}
Well you could use LINQ... something like this:
if (listBox2.Items
.Cast<object>()
.Select(x => x.ToString())
.Contains("PersonalInfo"))
{
label.Text = "test";
}
Or if you want to get the details of the first match:
var match = listBox2.Items
.Cast<object>()
.FirstOrDefault(x => x.ToString() == "PersonalInfo");
if (match != null)
{
// Use match here
}

Categories

Resources