How to do something with every second object in a NSArray? (iPhone) - iphone

I have a NSArray with many NSDictionaries. I'm using for to loop through it:
for (int i = 0; i < [myArray count]; i++) {
}
Now I want to do something with every second object of the array. Let's say the array has an count of 5. Than it will do something with the 2nd, 4th and 5th object.
How can I do this?
for (int i = 0; i < [myArray count]; i++) {
if //check if it is the second object
}
EDIT: Every second object and the last object if one will remain..

You can either increment by two during your loop increment or multiply by two when accessing.
i.e.:
for (int i = 0; i < [myArray count]; i += 2) {
doStuff([myArray objectAtIndex:i]);
}
or
for (int i = 0; i < [myArray count]/2; i++) {
doStuff([myArray objectAtIndex:i*2]);
}

It might be better to use fast enumeration if you don't need the index:
BOOL odd = YES;
for (id obj in ary) {
odd = !odd;
if (odd) continue;
// do stuff
}
When odd is initialized, before the loop, consider the implicit index (i in a traditional for loop) to be -1. (Which is an odd number.) On the first iteration of the loop, the index is incremented to 0, and the odd flag is flipped to even (NO). The flag is not odd, so we run this iteration (index 0, 1st element). The next iteration increments the index to 1 (2nd element), and the flag is inverted to odd. Since the flag is odd, we skip this iteration. Execution continues like this until the end of the array.
Edit: The traditional for loop with an index is cleaner if you always want to operate on the last element. To do that here, you would need to track the index manually or repeat the loop contents after the loop.

Use the modulo operator:
if (i % 2 || (i == [myArray count] - 1)) {
// do stuff
}
The modulo returns the remainder of the division i/2.
So if i is 0 (first item), it is 0 (==false).
If i is 1 (second item), it is 1 (==true).
To get your last element I added the (i == [myArray count] - 1) condition.

Related

Script is taking 11 - 20 seconds to lookup up an item in an 18,000 row data set

I have two Google sheets workbooks.
One is the "master" source of lookup data with a key based on manufacturer item #, which could be anything from 1234 to A-01/234-Name_1. This sheet, referenced via SpreadsheetApp.openByUrl, has 18,000 rows and 13 columns. The key column has been converted to plain text and the sheet is sorted by this column.
The second is the "template" where people enter item #s that they need to look up against the master, typically 20 - 1500 items at a time.
The script is in the template. It is very slow and routinely times out after 30 minutes. It was written by someone else and I am new to App Script, but I think I've managed to understand what the script is doing and where the bottleneck is occurring.
It does a bunch of stuff, but this is the meat of the lookup:
var numrows = master.getDataRange().getNumRows();
var masterdata = master.getDataRange().getValues();
var itemnumberlist = template.getDataRange().getValues();
var retreiveddata = [];
// iterate through the manf item number list to find all matches in the
// master and return those matches to another sheet
for (i = 1; i < template.getDataRange().getValues().length; i++) {
for (j = 0; j < numrows; j++) {
if (masterdata[j][1].toString() === itemnumberlist[i][1].toString()) {
retreiveddata.push(data[j]);
anothersheet.appendRow(data[j]);
}
}
}
I used Logger.log() to determine that each time through the i loop is taking 11 - 19 seconds, which just seems insane.
I've been doing some google searching and I've tried a couple of different things...
First I tried moving the writing of found data out of the for loop so the script would be doing all of its reading first and then writing in one big chunk, but I couldn't get it exactly right. My two attempts are below.
var mycounter = 0;
for (i = 0; i < template.getDataRange().getValues().length; i++) {
for (j = 0; j < numrows; j++) {
if (masterdata[j][0].toString() === itemnumberlist[i][0].toString()) {
retreiveddata.push(masterdata[j]);
mycounter = mycounter + 1;
}
}
}
// Attempt 1
// var myrange = retreiveddata.length;
// for(k = 0; k < myrange; k++) {
// anothersheet.appendRow(retreiveddata.pop([k]);
// }
//Attempt 2
var myotherrange = anothersheet.getRange(2,1,myothercounter, 13)
myotherrange.setValues(retreiveddata);
I can't remember for sure, because this was on Friday, but I think both attempts resulted in the script trying to write the entire master file into "anothersheet".
So I temporarily set this aside and decided to try something else. I was trying to recreate the issue in a couple of sample spreadsheets, but I was unable to do so. The same script is getting through my 15,000 row sample "master" file in less than 1 second per lookup. The only thing I can think of is that I used a random number as my key instead of a weird text string.
That led me to think that maybe I could use a hash algorithm on both the master data and the values to be looked up, but this is presenting a whole other set of issues.
I borrowed these functions from another forum post:
function GetMD5Hash(value) {
var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5,
value);
var txtHash = '';
for (j = 0; j <rawHash.length; j++) {
var hashVal = rawHash[j];
if (hashVal < 0)
hashVal += 256;
if (hashVal.toString(16).length == 1)
txtHash += "0";
txtHash += hashVal.toString(16);
Utilities.sleep(100);
}
return txtHash;
}
function RangeGetMD5Hash(input) {
if (input.map) { // Test whether input is an array.
return input.map(GetMD5Hash); // Recurse over array if so.
Utilities.sleep(100);
} else {
return GetMD5Hash(input)
}
}
It literally took me all day to get the hash value for all 18,000 item #s in my master spreadsheet. Neither GetMD5Hash nor RangeGetMD5Hash will return a value consistently. I can only do a few rows at a time. Sometimes I get "Loading..." indefinitely. Sometimes I get "#Name" with a message about GetMD5Hash being undefined (despite the fact that it worked on the previous row). And sometimes I get "#Error" with a message about an internal error.
This method actually reduces the lookup time of each item to 2 - 3 seconds (much better, but not great). However, I can't get the hash function to consistently work on the input data.
At this point I'm so frustrated and behind on my other work that I thought I'd reach out to the smart people on these forums and hope for some sort of miracle response.
To summarize, I'm looking for suggestions on these three items:
What am I doing wrong in my attempt to move the write out of the for loop?
Is there a way to get my hash value faster or utilize a different method to accomplish the same goal?
What else can I try to help speed up the script?
Any suggestions you can offer would be greatly appreciated!
-Mandy
It sounds like you hit on the right approach with attempting to move the appendRow() call out of the loop. Anytime you are reading or writing to a spreadsheet you can expect the individual call to take 1 to 2 seconds, so this will eat up a lot of time when you get matches. Storing the matches in an array and writing them all at once is the way to go.
Another thing I notice is that your script calls getValues() in the actual for loop condition statement. The condition statement is executed each time on each iteration of the loop, so this is potentially wasting a lot of time even when you don't have matches.
A final tweak that may be helpful depending on your desired behaviour. You can stop the inner for loop after it finds the first match, which, if you only care about the first match or know there will only be one match, will save you a lot of iterations. To do this, put "break" immediately after the retreiveddata.push(masterdata[j]); line.
To fix the getValues issue, Change:
for (i = 1; i < template.getDataRange().getValues().length; i++) {
To:
for (i = 1; i < itemnumberlist.length; i++) {
And that fix along with the appendRow issue, and including the break call:
for (i = 1; i < itemnumberlist.length; i++) {
for (j = 0; j < numrows; j++) {
if (masterdata[j][0].toString() === itemnumberlist[i][0].toString()) {
retreiveddata.push(masterdata[j]);
break; //stop searching after first match, move on to next item
}
}
}
//make sure you have data to write before trying to write it.
if(retreiveddata.length > 0){
var myotherrange = anothersheet.getRange(2,1,retreiveddata.length, retreiveddata[0].length);
myotherrange.setValues(retreiveddata);
}
If you are re-using the same sheet for "anothersheet" on each execution, you may also want to call anothersheet.clear() to erase any existing data before you write your fresh results.
I would pass on the hashing approach altogether, comparing strings is comparing strings, so whether they are hashes or actual part numbers I wouldn't expect a significant difference.

Create a NSPredicate to find elements whose length is > N

I have an array of strings ( more that 5k elements), each of variable length. I can use NSPredicate to find specific strings within the array( took a bit to figure that out). Now I need to find elements whose length is > than N.
I have looked through the documentation and Length does not seem to be one of the functions available in the Predicate Programming Guide.
Thanks in advance.
Imagmast
NSArray * words= [allLinedStrings filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"length > %d",N]];
N is your integer as you mentioned in your question.
I'm not familiar with NSPredicate, so I can't give you a solution using it, but couldn't you just use some plain NSString methods:
//This is the length of the string you want to check
int threshold = 5;
//Iterates through all elements in the array
for(int index = 0; index < [stringArray count]; index++) {
//Checks if the length of the string stored at the current index is greater than N
if([[stringArray objectAtIndex:index] length] > threshold) {
//String length is greater than N
}
}
Optionally, you could add in a check to see if the array only has strings (sacrifices performance for safety) by replacing this line:
if([[stringArray objectAtIndex:index] length] > threshold)
With this:
if([[stringArray objectAtIndex:index] length] > threshold && [[stringArray objectAtIndex:index] isKindOfClass[NSString class]])
What this does is it makes sure that the object at the current index is an NSString. If it's not, you'd receive a runtime error.

NSArray not iterating

I am trying to iterate through an NSArray with a for loop. The result only returns the last value in the array even though the int variable i is printing correctly (0,1,2...).
Also, if I set iteration to say 5, I will get the 6th object in the array, which is correct. I did this to try to narrow down the scope of possible causes.
Any ideas?
int i;
int j;
Buffer *vocalBuffer;
for (i=0; i < numberOfBuffers; i++){ // loop through every vocal buffer
Buffer *mixedBuffer = [[Buffer alloc] init];
int array[sizeLoopBuff];
mixedBuffer.buffer = array;
mixedBuffer.numFrames = sizeLoopBuff;
NSLog(#"Vocal buffer number --> %i", i);
NSInteger iteration = i;
vocalBuffer = [arrayOfVocalBuffers objectAtIndex:iteration]; // grab the vocal buffer
for (j=0; j < sizeLoopBuff; j++){ // run through a beat loop cycle.
mixedBuffer.buffer[j] = loopBuffer.buffer[j]; // add the beats to return buffer.
if (j > insertPoint && j < insertPoint+ vocalBuffer.numFrames){
mixedBuffer.buffer[j] = loopBuffer.buffer[j] + vocalBuffer.buffer[j-insertPoint];
}
}
[mutArray addObject:mixedBuffer];
}
As figured out in the comments, the use of a pointer to stack storage has some problems. One is that while it's in scope, its content is overwritten by each use within a loop; individual objects with pointers to it do not have unique copies.
The other problem is that once the method returns and its stack space isn't needed (as far as the runtime is concerned), there's no predicting what will be done with the space.
The necessary behavior of having a unique buffer per object suggests that the object should allocate its own buffer dynamically when created.

Confusing arrays

Kinda new to iOS, Im also studying Android. kinda confuse on arrays.
How to convert this into iOS:
result as being the index
nextresult[x] array of indexes
for(x = 0; x < array.size; x++)
{
if(result < nextresult[x])
nextresult[x] -= 1;
}
It will iccheck all of the content of the arrays if it needs to be adjusted or not,
Might be following will give you some idea -
As you mentioned, i have considered following - result' is value andnextresult` is array.
for(x = 0; x < [array count]; x++)
{
if(result < [nextresult objectAtIndex:x]) {
[nextresult objectAtIndex:x] -= 1;
}
}
EDIT -
if you want to add integer in arrays -
[yourArray addObject:[NSNumber numberWithInt:1]]; // Objective C array store objects. so we need to convert primitive data type into object.
Read Apple's excellent developer documentation.
You can find the docs about the NSArray class here, or in your Xcode organizer.
I have downvoted your question, because it could very easily have been solved by just looking at the docs.

iPhone maths not quite adding up right

I am adding values held within an array but the sum is +1 what it actually should be.
//update totalscore
uint newTotalScore;
for (uint i=0; i< [bestscoresArray count] ; i++) {
newTotalScore += [[bestscoresArray objectAtIndex:i] intValue];
}
totalscore = newTotalScore;
//output
l1bestscore=15900, l2bestscore=7800, l3bestscore=81000, l4bestscore=81000, l5bestscore=0, l6bestscore=0, l7bestscore=0, l8bestscore=0, l9bestscore=0, l10bestscore=0, totalscore=185701
As you can see the totalscore output is 185701 but the sum of all values is 185700.
Would anyone have any ideas why this is occurring?
Thanks,
Mark
You must define newTotalScore's initial value:
uint newTotalScore = 0;
Otherwise it will be undefined. In your case it was 1 but it could have been any other value.
Not sure about this, but did you try initializing newTotalScore to zero? (See this question about variable initialization.) If that does not help, give us more code.