Checking for duplicates in an object array? - c#

I have the following object array, created from a custom class Room. Before I add a new Room to the array I want to verify that the Roomname is not already in the array. Here is a sample of what I am trying:
private void btnAddRm_Click(object sender, EventArgs e)
{
Room[] roomArray = new Room[20];
test = txtName.text;
for (int i = 0; i < roomArray.length; i++)
{
if(test != roomArray[i].getRoomName())
{
addRoom();
}
}
}
GetRoomName() is the accessor that retrieves the RoomName from the class. I guess my question is why is this code not working?
Thank you

Your current code will add the room as soon as the room's name doesn't equal the new room.
Instead, first check that none of the rooms have the existing name and only then add it:
private void btnAddRm_Click(object sender, EventArgs e)
{
Room[] roomArray = new Room[20];
test = txtName.text;
bool exists = false;
for (int i = 0; i < roomArray.length; i++)
{
if (test == roomArray[i].getRoomName())
{
exists = true;
break;
}
}
if (!exists)
{
addRoom();
}
}

Your code fires addRoom() every time there is non-matching room in current array.
I think, you're looking for something like that:
if(!roomArray.Any(r => r.getRoomName() == test))
addRoom();
or using All method:
if(roomArray.All(r => r.getRoomName() != test))
addRoom();

You should finish looping through the entire array prior to adding the new room. With what you have if you have a new room name that doesn't match any existing rooms it'll call addRoom() 20 times.
Room[] roomArray = new Room[20];
test = txtName.text;
bool doesRoomExist = false;
for (int i = 0; i < roomArray.length; i++)
{
if (test == roomArray[i].getRoomName())
{
doesRoomExist = true;
break;
}
}
if (!doesRoomExist)
addRoom();
You can also condense you're for loop by using the Any extension method, you will need using System.Linq to do so.
if (!roomArray.Any(room => room.GetRoomName() == test))
addRoom();

Like others already stated your code does not make any sense. You create an array of rooms leaving all elements uninitialized (null). Then you check for existence of a room within that array. Also in your loop you "add" the new room each time an element has a different name. I have no clue how you want to add something to an array that is outside of the scope of your addRoom method. I hope this is not your actual code!
So first of all the room array should be created as a field of your class outside the scope of the btnAddRm_Click method.
Since you want to add rooms you should not use an array. Use a List<Room> instead. Otherwise you would need to know how many elements of your array are already initialized with a room. Also you would need to grow your array if the number exceeds the initial size of the array. This is exactly what List<Room> can do for you.
Your btnAddRm_Click event handler can then use LINQ to check for the existence of any room with the same name.
Please notice that you should use String.Compare(name1, name2) instead of name1 == name2.
private List<Room> _theRooms = new List<Room>();
private void btnAddRm_Click(object sender, EventArgs e)
{
if (!_theRooms.Any(r => string.Compare(r.Name, txtName.Text, StringComparison.CurrentCultureIgnoreCase) == 0))
{
addRoom();
}
}
Assuming that the room name is some kind of unique key for rooms you can also use a HashSet<Room> and then add the room anyway. HashSet will check for existence of the room. Your Room class however needs to overwrite Equals and GetHashCode then with Equals checking for equality of the room names.

Related

How to "return" multiple times with for loop?

Hopefully this post gives more clarity as to what I am trying to achieve.
Objective: I want to spawn 20 apples(that have an attached button) from a list at runtime. When the apples are clicked they will spawn a popup with information pertaining to the apple that was clicked.
What I'm doing currently: I am using a for loop to run through the list to spawn the apples. I currently have the following code:
public class AppleInventory : MonoBehaviour
{
[SerializeField] private ApplesScript applPrefab;
[SerializeField] private Transform applParent;
public ApplesScript CreateApples()
{
var appl = Instantiate(applPrefab, applParent);
for (int i = 0; i < apples.Count; i++)
{
appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
return appl;
}
}
The Problem: The problem is that when I use the for loop and click on the button,it returns the following error: ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection. The popup information also does not update.
Code without for loop: The code works to spawn one apple when I remove the for loop and set the int i = to a specific number, like below. It will give the correct popup info for any number that "i" is set to. This lets me know that it is not the rest of the code that is the issue. This leads me to believe it is the "return" line along with the for loop that is the issue. It seems I may need to "return" for each iteration but I am unsure of how to go about doing this.
public ApplesScript CreateApples()
{
int i = 7;
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
return appl;
}
Thank you,
-
UPDATE
The fix was so simple. I just ended up creating a new method specifically for the for loop and it worked the way I wanted. My code now looks like this:
public void StarterOfApplesCreation()
{
for (int i = 0; i < apples.Count; i++)
{
CreateApples(i);
}
}
public void CreateApples(int i)
{
var appl = Instantiate(applPrefab, applParent);
appl.InitAppleVisualization(apples[i].GetAppleSprite());
appl.AssignAppleButtonCallback(() => CreateApplePopUpInfo(i));
appl.transform.position = new Vector2(apples[i].x, apples[i].y);
}
You have two options. The conventional option is to create all the items first and then return them all in some sort of list, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static Thing[] GetThings(int count)
{
var things = new Thing[count];
for (var i = 0; i < count; i++)
{
things[i] = new Thing { Number = i };
}
return things;
}
The more modern option is to use an iterator. It actually will return one item at a time. It has the limitation that you have to use the items there and then - you won't have random access like you would an array or the like - but it also has advantages, e.g.
public static void Main()
{
foreach (var thing in GetThings(5))
{
Console.WriteLine(thing.Number);
}
Console.ReadLine();
}
public static IEnumerable<Thing> GetThings(int count)
{
for (var i = 0; i < count; i++)
{
var thing = new Thing { Number = i };
yield return thing;
}
}
The result of an iterator will usually be used as the source for a foreach loop or a LINQ query. Note that you can always call ToArray or ToList on the result of an iterator if you do want random access in specific situations, but you still have the advantages of an iterator elsewhere. For instance, let's say that your method produces 1000 items and you want to find the first one that matches some condition. Using my first example, you would have to create all 1000 items every time, even if the first one was a match. Using an iterator, because the items are processed as they are created, you can abort the process as soon as you find a match, meaning that you won't unnecessarily create the remaining items.
Note that my examples use the following class:
public class Thing
{
public int Number { get; set; }
}
You can copy and paste the code into a Console app that doesn't use top-level statements. The bones of the code will still work with top-level statements, but you'll need to make a few other modifications.
Store each separate "appl" that gets instantiated in an Array, ie appls[i]=appl
Do this within the for loop.
If you think about it, by putting the line "return appl;" outside the for loop, you are only storing that last game object, not all of them. Thats why creating an array of gameobjects and assigning them within the loop may work for you.

Does expanding arraylist of objects make a new object?

Assume we have an array list of type Employe , does expanding it's length by 1 make a new object in the list ?
is the code in else statement correct? and is it recommended?
public void ModifierEmp(int c)
{
for(int i = 0; i < Ann.Count; i++)
{
if(Ann[i].Code == c)
{
Ann[i].saisie();
} else
{
i = Ann.Count + 1; //expanding arraylist ann
Ann[i].saisie(); //saisie a method for the user to input Employe infos
}
}
}
https://imgur.com/VfFHDKu "code snippet"
i = Ann.Count + 1;
The code above is not expanding the list: it is only setting your index variable (i) to have a new value.
If you wanted to make the list bigger, you would have to tell it which object to put into that new space you create. For example:
Ann.Add(anotherItem);
Of course, this gives you the ability to decide whether to add an existing item, create a new item (e.g. Ann.Add(new Something() { Code = c })), or even add a null value to the list (which is not usually a good idea).

Generate unique list variable

I have a C# program where I have a list (List<string>) of unique strings. These strings represent the name of different cases. It is not important what is is. But they have to be unique.
cases = new List<string> { "case1", "case3", "case4" }
Sometimes I read some cases saved in a text format into my program. Sometime the a case stored on file have the same name as a case in my program.I have to rename this new case. Lets say that the name of the case I load from a file is case1.
But the trouble is. How to rename this without adding a large random string. In my case it should ideally be called case2, I do not find any good algorithm which can do that. I want to find the smalles number I can add which make it unique.
i would use a HashSet that only accepts unique values.
List<string> cases = new List<string>() { "case1", "case3", "case4" };
HashSet<string> hcases = new HashSet<string>(cases);
string Result = Enumerable.Range(1, 100).Select(x => "case" + x).First(x => hcases.Add(x));
// Result is "case2"
in this sample i try to add elements between 1 and 100 to the hashset and determine the first sucessfully Add()
If you have a list of unique strings consider to use a HashSet<string> instead. Since you want incrementing numbers that sounds as if you actually should use a custom class instead of a string. One that contains a name and a number property. Then you can increment the number and if you want the full name (or override ToString) use Name + Number.
Lets say that class is Case you could fill a HashSet<Case>. HashSet.Add returns false on duplicates. Then use a loop which increments the number until it could be added.
Something like this:
var cases = new HashSet<Case>();
// fill it ...
// later you want to add one from file:
while(!cases.Add(caseFromFile))
{
// you will get here if the set already contained one with this name+number
caseFromFile.Number++;
}
A possible implementation:
public class Case
{
public string Name { get; set; }
public int Number { get; set; }
// other properties
public override string ToString()
{
return Name + Number;
}
public override bool Equals(object obj)
{
Case other = obj as Case;
if (other == null) return false;
return other.ToString() == this.ToString();
}
public override int GetHashCode()
{
return (ToString() ?? "").GetHashCode();
}
// other methods
}
The solution is quite simple. Get the max number of case currently stored in the list, increment by one and add the new value:
var max = myList.Max(x => Convert.ToInt32(x.Substring("case".Length))) + 1;
myList.Add("case" + max);
Working fiddle.
EDIT: For filling any "holes" within your collection you may use this:
var tmp = myList;
var firstIndex = Convert.ToInt32(myList[0].Substring("case".Length));
for(int i = firstIndex; i < tmp.Count; i++) {
var curIndex = Convert.ToInt32(myList[i].Substring("case".Length));
if (curIndex != i)
{
myList.Add("case" + (curIndex + 1));
break;
}
}
It checks for every element in your list if its number behind the case is equal to its index in the list. The loop is stopped at the very first element where the condition is broken and therefor you have a hole in the list.

Using a Person class in an array

Hi i have following problem:
I'm creating a program with a array of Person objects.
aantalpersonen = int.Parse(tbArray.Text); // aantal te creƫren items in array;
if (aantalpersonen > 5 || aantalpersonen <= 1)
throw new ArgumentOutOfRangeException();
else
{
Persoon[] personenLijst = new Persoon[aantalpersonen];
foreach (Persoon persoon in personenLijst)
{
Persoon pers1 = new Persoon();
}
}
watching this in debugging gives me = a new array with the userdefined number of Persons,
an integer with the amount of persons.. In some other lines of code i get an integer wich holds the current chosen (to edit) Person.
All this works fine , but when i try to add data to my properties of each Person i get problems.
private void btnUpdateData_Click(object sender, RoutedEventArgs e)
{
personenLijst[huidigpersoon - 1].Naam = tbNaam.Text;
personenLijst[huidigpersoon - 1].Gewicht = int.Parse(tbGewicht.Text);
personenLijst[huidigpersoon - 1].Lengte = int.Parse(tbLengte.Text);
personenLijst[huidigpersoon - 1].Geboortedatum = dpGeboorte.SelectedDate.GetValueOrDefault(DateTime.Today);
}
this gives me following error :
System.NullReferenceException was unhandled
HResult=-2147467261
Message=Object reference not set to an instance of an object.
My first tought is i have to specificly declare each person by using the ammount of persons the user has chosen. But i can't figger out how i should do this and even if i would manage to , how can i make sure each instance of the personclass has a different name for example persoon1 ,persoon2 ..
You have declared the array to contain a specified number of Persoon objects, but you haven't set any of the elements of the array to be an actual Persoon instance.
So you cannot use something that is not there
aantalpersonen = int.Parse(tbArray.Text);
if (aantalpersonen > 5 || aantalpersonen <= 1)
throw new ArgumentOutOfRangeException();
else
{
Persoon[] personenLijst = new Persoon[aantalpersonen];
for( int x = 0; x < personenLijst.Length ; x++)
{
personenLijst[x] = new Persoon();
}
}
After that your array is filled with instances of Persoon class and you can change the properties of the individual instances. However, as noted by Mr Holterman in its comment below, the array is declared as a local variable in this snippet of code, so it is not available (not in scope) in the event handler where you try to change the individual properties. To fix this problem the array should be declared at the global level.
Persoon[] personenLijst = null;
and initialized with
personenLijst = new Persoon[aantalpersonen];
Said that, why do you still use arrays instead of a more versatile List<Persoon>
Your problem comes from the fact you didn't initialize your array members.
You create a bunch of "Person" but they are not stored into your array. As .NET states, an array is by default initialized with all columns set to null.
Your code should be :
Persoon[] personenLijst = new Persoon[aantalpersonen];
int i = 0;
foreach (Persoon persoon in personenLijst)
{
personenLijst[i] = new Persoon();
i++;
}
Ok this works, thanks the problem was that i did this : Persoon[] personenLijst = new Persoon[aantalpersonen]; after the else loop, which created a NEW array next to the first i declared at global level
by putting this instead personenLijst = new Persoon[aantalpersonen]; everything works fine!
Thanks for your help guys !
Appreciate it

Checking for double booking in arrays

Ok, my project to create an event organizer for a music contest. Basically I take input data such as a list of about 400 entries and I output into a few separate excel sheets by room number and type. The problem is, when assigning events to an array, (I have a separate array for each room and all the arrays are contained in a list) I need to check and fix if accompanists are double booked for the same time slot. Or essentially, if two different arrays have the same accompanist value for array[i]
Ideas? Please try to explain well, I'm self taught with only a bit of experience
private void assignEvents()
{
// assign every event in order
// check accompanists for no double-books
foreach (Event[] room in roomList)
{
int i = 0;
foreach (Event ev in eventList)
{
if (ev.Type == room[0].Type)
{
room[i] = ev;
i++;
}
}
}
You could do it with LINQ like Tejs suggests, but I would go another route since you are designing an organizer and you will probably need the extra information any way (for example, to display the schedule of an accompanist). I would have a list of all accompanists with a sorted array of all of their bookings (basically , their schedule). You can then have a function to check for every new booking whether there is a conflict (in the booking array) for each of the accompanists in the booking and if not, continue by adding the booking to the accompanists' schedule
If I understand you correctly, you must compare values per column. I suggest to create your own RoomList collection class by deriving it from your actual collection type and to add it the possibility to iterate columns
public class RoomList : List<Event[]>
{
public IEnumerable<Event> TimeSlot(int columnIndex)
{
foreach (Event[] events in this) {
if (events != null && columnIndex < events.Length) {
yield return events[columnIndex];
}
}
}
}
Then you can use LINQ to get double bookings
RoomList roomList = new RoomList();
// ...
for (int slot = 0; slot < roomList[0].Length; slot++) {
var doubleBooked = roomList.TimeSlot(slot)
.GroupBy(e => e.Type)
.Where(g => g.Count() > 1);
if (doubleBooked.Any()) {
Console.WriteLine("Doubly booked accompanists in slot {0}", slot);
foreach (var accompanistGroup in doubleBooked) {
Console.WriteLine(" {0}", accompanistGroup.Key);
}
}
}

Categories