Comparing objects in arrays to find event clashes in a timetable - ios

I have 4 Event objects that would be displayed on a school timetable. I need to retrieve an array of the Events that clash with each other i.e. the start time of one Event is in between the start and end time of another. For the sake of getting the algorithm right first i have used int for the times instead of NSDate.
Event *event = [[Event alloc] init];
event.courseName = #"Maths";
event.room = #"405";
event.startTime = [NSNumber numberWithInt:8];
event.endTime = [NSNumber numberWithInt:10];
[eventsStore addObject:event];
Event *event2 = [[Event alloc] init];
event2.courseName = #"English";
event2.room = #"510";
event2.startTime = [NSNumber numberWithInt:10];
event2.endTime = [NSNumber numberWithInt:12];
[eventsStore addObject:event2];
Event *event3 = [[Event alloc] init];
event3.courseName = #"Computing";
event3.room = #"220";
event3.startTime = [NSNumber numberWithInt:11];
event3.endTime = [NSNumber numberWithInt:14];
[eventsStore addObject:event3];
Event *event4 = [[Event alloc] init];
event4.courseName = #"Sports";
event4.room = #"000";
event4.startTime = [NSNumber numberWithInt:13];
event4.endTime = [NSNumber numberWithInt:15];
[eventsStore addObject:event4];
Here is what i have at the moment to find the clashes:
int clashCounter = 0;
Event *curEv = nil, *otherEv = nil;
for(int i = 0; i < [eventsStore count]; i++)
{
curEv = [eventsStore objectAtIndex:i];
for (int j = 0; j < [eventsStore count]; j++)
{
if (j!=i && ![curEv.clashList containsObject:otherEv])
{
otherEv = [eventsStore objectAtIndex:j];
if (curEv.startTime < otherEv.endTime && otherEv.startTime < curEv.endTime)
{
clashCounter++;
NSLog(#"Clash: %# clashes with %#", curEv.courseName, otherEv.courseName);
[curEv.clashList addObject:otherEv];
}
}
}
}
Starts by creating two empty Event objects. Will loop through each Event in the eventsStore array, sets curEv to the objectAtIndex. Then loops through each of the eventsStore array objects that are not the curEv and does not have otherEv in its array. (each Event object contains an array called clashList for the Events it clashes with). If theres a clash then add otherEv to curEv.
When i run it i get:
ClashTest[3393:134117] Clash: English clashes with Computing
ClashTest[3393:134117] Clash: Computing clashes with English
ClashTest[3393:134117] Clash: Sports clashes with Computing
This is showing that the Event clashes are picked up but its storing a duplicate in the first instance(the English clashing with Computing goes both ways). But works as it is supposed to when it comes to the Sports and Computing clash because it only shows this once.
How can i get this working properly?
Apologies if this is deemed to specific, i've been messing around with this for a couple of weeks and haven't had much like no matter what ever way i try.

You could change the second loop to start with j = i+1 (and the first loop to stop at i < [eventsStore count] - 1). You would then go only over elements that are after the one checked.

I believe you could use a hash map to store the clashes, define this before your first for loop
NSMutableDictionary* clashes = [[NSMutableDictionary alloc] init];
Now when you find the clash (where you are logging it), check if the clash already exists both ways and if not, add it to the clashes dictionary like this (but you should have some unique id for the event, I'll assume it's an int e.g.)
NSString* firstHashKey = [NSString stringWithFormat:#"%d", curEv.Id];
NSString* secondHashKey = [NSString stringWithFormat:#"%d", otherEv.Id];
if (([clashes valueForKey:firstHashKey] == nil) && ([clashes valueForKey:secondHashKey] == nil)) {
clashes[firstHashKey] = #[ curEv, otherEv ];
}
else {
// It is already added to the clashes dictionary so no need to do it
}
Now when you want to print all the clashes you can simply do the following
for (NSString* key in clashes.allKeys) {
NSArray* value = clashes[key];
Event* firstEvent = value[0];
Event* secondEvent = value[1];
NSLog(#"Clash: %# clashes with %#", firstEvent.name, secondEvent.name);
}

The problem in the code is the initialization of otherEvent inside the inner loop...
for (int j = 0; j < [eventsStore count]; j++)
{
if (j!=i && ![curEv.clashList containsObject:otherEv]) // <-- look
{
// this happens too late to properly check the condition above
otherEv = [eventsStore objectAtIndex:j];
Change it like this...
for (int j = 0; j < [eventsStore count]; j++)
{
otherEv = [eventsStore objectAtIndex:j];
if (j!=i && ![curEv.clashList containsObject:otherEv])
{
and your logic will work. (Though there are quite a few opportunities for improvement, including shortening this inner loop as suggested above).

Related

Set values of NSMutableDictionary to values in another

I did post request for retrieving data from the server. Now, I have NSDictionary with certain data. I need to display it in a chart, so I have to convert data to appropriate. Here's data which I get from the server:
dates = (
"01.01.2016",
"01.01.2016",
"01.01.2016"
...)
closes = {
0 = (
1,
1,
1,
...)
}
I need to transform it to this format:
{
"01.01.2016" = 1;
"01.01.2016" = 1;
"01.01.2016" = 1;
...
}
How can I do this?
NSArray *dates = ...
NSArray *closes = ...
NSMutableDictionary *result = [NSMutableDictionary new];
for (int i = 0; i < dates.count; i++) {
result[dates[i]] = closes[i];
}
NSLog(#"%#", result)
Note that dates and closes arrays must have same length
Assuming the number of 'dates' and closes elements are equal:
NSMutableDictionary dataOut = [NSMutableDictionary dictionary];
for (int index = 0; index < dataIn[#"dates"].count; index++)
{
dataOut[dataIn[#"dates"][index]] = dataIn[#"closes"][index];
}
Depending on how certain you are and on how fool proof you need this code to be, you may want to add checks (and error handling) to cover the above assumption.
If you have to 2 array and you need to create dictionary then
_productArray2 & _productArray1
NSDictionary * productDictionary = [NSMutableDictionary dictionary];
for (NSUInteger index =0; index < _productArray1.count; index ++) {
[productDictionary setValue:_productArray1[index] forKey:_productArray2[index]];
}
and if you already have a dictionary want to replace of set value to it
NSArray * allKeys = [productDictionary allKeys];
for (NSUInteger index =0; index < allKeys.count; index ++) {
[productDictionary setValue:_productArray[index] forKey:allKeys [index]];
}

UITextfield text in array will not change a label's text in another array

Please help. What am I doing wrong? textfield.text goes into one array and is suppose to change a label that belongs to another array.
I have multiple textfields. Im trying to save the text of each field to an array and then setAlpha to 0. Then I have an equal amount of labels that I want to change to the textfield's text. I have tried using mutable and immutable arrays. I keep getting errors.
I've tried multiple ways and its got to be something simply dumb I'm missing. I've greatly reduced these arrays just to post here.
_namesText = [NSMutableArray arrayWithObjects:_nameLabel1.text, _nameLabel2.text, nil];
_names = [NSMutableArray arrayWithObjects:_nameLabel1, _nameLabel2, nil];
_nameInputs = [NSMutableArray arrayWithObjects:_p1NameTextField, _p2NameTextField, nil];
_playerNameText = [NSArray arrayWithObjects:_p1NameTextField.text, _p2NameTextField.text, nil];
enter code here
- (IBAction)enterNamesButton:(id)sender {
//These don't work.
for (int i = 0; i < _numberOfPlayers; i++) {
[_names[i] setAlpha:1];
NSString *tempString = [[NSString alloc]initWithString:[_nameInputs[i] text]];
[_lastTry addObject:tempString];
}
//Then tried this. This is after trying for 2.5 hours and different coding.
for (int i = 0; i < _numberOfPlayers; i++) {
_namesText[i] = _lastTry[i];
UILabel *cell = [[UILabel alloc] init];
cell.text = _lastTry[i];
//[[_namesText[i] textLabel] text] = #"Idiot";
_namesText[i] = cell;
NSLog(#"%#", _namesText[i]);
// This works (but bad practice) and I want to loop through arrays that each UI element belongs to instead of typing it all out.
// _nameLabel1.text = _p1NameTextField.text;
// _nameLabel2.text = _p2NameTextField.text;
I expect this to work but NOPE!!!!
Assuming the following:
Array _fromTextField is the textfield you want to copy from and set Alpha to 0
Array _toLabelField is the label you want the text to appear in
Array _toStoreStrings is where you keep all the strings, for later maybe ?
NSMutableArray *_fromTextField = [[NSMutableArray alloc] init]; // Put the Text Fields in Here
NSMutableArray *_toLabelField = [[NSMutableArray alloc] init]; // Put the Labels in Here
NSMutableArray *_toStoreStrings = [[NSMutableArray alloc] init];
int count = 0;
int overRun = [_toLabelField count];
for (UITextField *tf in _fromTextField)
{
tf.alpha = 0;
NSString *tempString = [[NSString alloc]initWithString:tf.text];
[_toStoreStrings addObject:tempString];
if (count < overRun) // Check just in case Array sizes not same
{
UILabel *lb = [_toLabelField ObjectAtIndex:count];
lb.text = tempString;
}
else
break;
count++;
}
The issue your code faces is you have an array of NSObject. The compiler has no idea they are UITextField or UILabel unless you cast them with (type_cast *) or point a new typed variable at them. Hence the crashes you were seeing.
I would do it this way:
for (int i = 0; i < _numberOfPlayers; i++) {
_namesText[i] = _lastTry[i];
}
I cannot see why this won't work... does it?

Merge sort in Objective-C

I'm trying to figure out what's wrong with this merge sort implementation. I've narrowed it down to when I concatenate what remains of the left and right arrays. In the third loop of the recursion, something goes wrong.
-(NSArray *)mergeSort:(NSArray *)unsortedArray
{
//unsortedArray is 4,2,6,5,3,9
if ([unsortedArray count] < 2)
{
return unsortedArray;
}
int middle = ([unsortedArray count]/2);
NSRange left = NSMakeRange(0, middle);
NSRange right = NSMakeRange(middle, ([unsortedArray count] - middle));
NSArray *rightArr = [unsortedArray subarrayWithRange:right];
NSArray *leftArr = [unsortedArray subarrayWithRange:left];
return [self merge:[self mergeSort:leftArr] andRight:[self mergeSort:rightArr]];
}
-(NSArray *)merge:(NSArray *)leftArr andRight:(NSArray *)rightArr
{
NSMutableArray *result = [[NSMutableArray alloc]init];
int right = 0;
int left = 0;
while (left < [leftArr count] && right < [rightArr count])
{
if ([leftArr objectAtIndex:left] < [rightArr objectAtIndex:right])
{
[result addObject:[leftArr objectAtIndex:left++]];
}
else
{
[result addObject:[rightArr objectAtIndex:right++]];
}
}
NSRange leftRange = NSMakeRange(left, ([leftArr count] - left));
NSRange rightRange = NSMakeRange(right, ([rightArr count] - right));
NSArray *newRight = [rightArr subarrayWithRange:rightRange];
NSArray *newLeft = [leftArr subarrayWithRange:leftRange];
newLeft = [result arrayByAddingObjectsFromArray:newLeft];
return [newLeft arrayByAddingObjectsFromArray:newRight];
}
BTW, this is not homework. I'm a self-taught programmer trying to learn a little CS. Thanks everyone.
You can't use the < (less than) operator to compare two objects. Use the compare: method:
Replace:
if ([leftArr objectAtIndex:left] < [rightArr objectAtIndex:right])
with:
NSComparsionResult result = [leftArr[left] compare:rightArr[right]];
if (result == NSOrderedAscending) // equivalent to <
As "rob" points out, it would be even better to use:
if (result != NSOrderedDescending) // equivalent to <=
BTW - using < with two objects is causing problems because you are comparing the pointer addresses of the two objects. So you end up sorting the objects based on their location in memory and not by their value.
And of course using the compare: method assumes the objects in the array actually implement the compare: method. This is true for things like NSString, NSNumber, and NSDate. If these are custom objects you need to implement an equivalent method.
The issue certainly is the comparison:
Replacing:
if ([leftArr objectAtIndex:left] < [rightArr objectAtIndex:right])
for:
if ([[leftArr objectAtIndex:left] intValue] < [[rightArr objectAtIndex:right] intValue])
will also work.

My method won't work

I'm a total noob and need help with a method I'm writing.
The method creates a deck of cards (using NSMutableArray). I'm first experimenting with loading the array with numbers 1-13 randomly (each number appearing once).
When I run a simple test program to print the values in the array, I get a "Build Successful" but an error once the program starts. The error says "[__NSArrayM insertObject:atIndex:]: object cannot be nil".
Once I understand what I'm doing wrong, I can then expand on the method properly. Thanks!
NB: This is my first post. Is this type of question ok?
- (void) createDeck {
int r;
BOOL same;
deck = [[NSMutableArray alloc]init];
NSNumber *randNum;// = nil;
randNum = [[NSNumber alloc]init];
[randNum initWithInt: (arc4random()%13)+1];
[deck addObject: randNum]; // First card added to deck
same = FALSE;
while (!same) {
for (int i=1; i<13; i++) {
same = FALSE;
for (r=0; r<=i; r++) {
[randNum initWithInt: (arc4random()%13)+1];
if ([deck objectAtIndex:r] == [deck objectAtIndex:i]) {
same = TRUE;
}
[deck addObject: randNum]; // Next card added to deck
}
}
}
}
you cannot re-init randNum:
NSNumber *randNum;// = nil;
randNum = [[NSNumber alloc]init];
[randNum initWithInt: (arc4random()%13)+1];
and the third line is missing the assignment anyway. just do:
NSNumber *randNum = [[NSNumber alloc] initWithInt:(arc4random()%13)+1];
and put that inside the inner for loop, like this:
BOOL same = FALSE;
NSMutableArray *deck = [[NSMutableArray alloc] initWithCapacity:13];
[deck addObject:[[NSNumber alloc] initWithInt:(arc4random()%13)+1]]; // First card added to deck
while (!same) {
for (int i = 1; i < 13; i++) {
same = FALSE;
for (int r = 0; r <= i; r++) {
NSNumber *randNum = [randNum initWithInt:(arc4random()%13)+1]; // modern ObjC will assign a register for this outside the while loop, but restrict the variable's scope to the inner-most loop
if ([deck objectAtIndex:r] == [deck objectAtIndex:i])
same = TRUE;
[deck addObject: randNum]; // Next card added to deck
}
}
Note that I haven't thought through the logic of what you are trying to do here, I've only attempted to resolve the NULL object reference. The error was referring to the first line [deck addObject: randNum] outside of the loop.
Try to use this line of code where you using NSNumber
NSNumber * randNum = [NSNumber numberWithInt: (arc4random%13)+1];
Instead of
[[NSNumber alloc] initWithInt: ]

How do I update an object in NSMutableArray?

I am trying to update an object in NSMutableArray.
Product *message = (Product*)[notification object];
Product *prod = nil;
for(int i = 0; i < ProductList.count; i++)
{
prod = [ProductList objectAtIndex:i];
if([message.ProductNumber isEqualToString:prod.ProductNumber])
{
prod.Status = #"NotAvaiable";
prod.Quantity = 0;
[ProductList removeObjectAtIndex:i];
[ProductList insertObject:prod atIndex:i];
break;
}
}
Is there any better way to do this?
Remove lines:
[ProductList removeObjectAtIndex:i];
[ProductList insertObject:prod atIndex:i];
and that will be ok!
For updating, use
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject
But it is not needed in this case, since you are modifying the same object.
You could start by using fast enumeration, which is faster and easier to read. Also, you don't need to remove and insert the object, you can just edit it in line. Like this:
Product *message = (Product*)[notification object];
for(Product *prod in ProductList)
{
if([message.ProductNumber isEqualToString:prod.ProductNumber])
{
prod.Status = #"NotAvailable";
prod.Quantity = 0;
break;
}
}
(Is ProductList an object? If it is, it should start with a lowercase letter: productList. Capitalized names are for classes. Also, Status and Quantity are properties and should too start with a lowercase letter. I highly suggest you follow the Cocoa naming conventions.)
Use -insertObject:atIndex: or replaceObjectAtIndex:withObject:.
There are two approaches
Create a new object and replace the old object with the new object
for(int i = 0; i < ProductList.count; i++)
{
prod = [ProductList objectAtIndex:i];
if([message.ProductNumber isEqualToString:prod.ProductNumber])
{
newObj = [[Product alloc] autorelease];
newObj.Status = #"NotAvaiable";
newObj.Quantity = 0;
[ProductList replaceObjectAtIndex:i withObject:newObj];
break;
}
}
Update the existing object:
for(int i = 0; i < ProductList.count; i++)
{
prod = [ProductList objectAtIndex:i];
if([message.ProductNumber isEqualToString:prod.ProductNumber])
{
prod.Status = #"NotAvaiable";
prod.Quantity = 0;
break;
}
}

Resources