Truncate an NSString to width - objective-c

I'm trying to convert the code found here to a binary algorithm instead, since the linear method is terribly slow. I have a working implementation, but theres seems to be some thing I'm missing, since the results I'm getting are not precise (that is, if I add or subtract 20 or so pixels from the width, I get the same truncated string..it should be a little more accurate and have a closer cut):
-(NSString*)stringByTruncatingToWidth:(CGFloat)width withFont:(UIFont*)font addQuotes:(BOOL)addQuotes
{
int min = 0, max = self.length, mid;
while (min < max) {
mid = (min+max)/2;
NSString *currentString = [self substringWithRange:NSMakeRange(min, mid - min)];
CGSize currentSize = [currentString sizeWithFont:font];
if (currentSize.width < width){
min = mid + 1;
} else if (currentSize.width > width) {
max = mid - 1;
} else {
min = mid;
break;
}
}
return [self substringWithRange:NSMakeRange(0, min)];
}
Can anyone see off the bat what could be wrong with that? (This is a category method, so self is an NSString.

I'm pretty sure this line is wrong:
[self substringWithRange:NSMakeRange(min, mid - min)];
This means that every pass, you're only examining the size of the string from min to mid -- that's not the string that you're going to end up displaying, though, because min is marching up the string as you search. You should be always be testing starting from the beginning of the string:
[self substringWithRange:NSMakeRange(0, mid)];
which is what you're going to return when your search is done.

Related

Metal compute values isnt the same as the CPU values

I'm trying to implement a length of a vector of 3DPoints and when I compare the values retrieved by the GPU with the CPU they aren't entirely the same, usually having a large number of differences.
I initially used packed_float3 and it present a bit more differences, so I started to use float3 and improved a little bit but there are still differences that I would like to fix.
The values don't differ a lot, on average they differ by -0.00000000048358334004, but when I run operations like summing and subtracting two arrays the difference doesn't occur and I would like that that it would happen the same.
Here is a part of the Code
main.m
- (void) lenght_function:(NSArray*) array {
_buffer[0] = [_mDevice newBufferWithLength:_sp_size_alloc options:MTLResourceStorageModeShared];
_buffer[1] = [_mDevice newBufferWithLength:_sp_size_alloc options:MTLResourceStorageModeShared];
float3 *datapt = [_buffer[0] contents];
for (unsigned long index = 0 ; index< _sp_lenght ; index++) {
datapt[index].x = (float)[array[index] getX];
datapt[index].y = (float)[array[index] getY];
datapt[index].z = (float)[array[index] getZ];
}
commandBuffer = [_mCommandQueue commandBuffer];
assert(commandBuffer != nil);
id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
assert(computeEncoder != nil);
[computeEncoder setComputePipelineState:_mLenghtFunctionPSO];
[computeEncoder setBuffer:_buffer[0] offset:0 atIndex:0];
[computeEncoder setBuffer:_buffer[1] offset:0 atIndex:1];
//[array1 makeData];
MTLSize gridSize = MTLSizeMake(_sp_lenght, 1, 1);
NSUInteger threadGroupSize = _mLenghtFunctionPSO.maxTotalThreadsPerThreadgroup;
if(threadGroupSize > _sp_lenght){
threadGroupSize = _sp_lenght;
}
MTLSize threadgroupsize = MTLSizeMake(threadGroupSize, 1, 1);
[computeEncoder dispatchThreads:gridSize threadsPerThreadgroup:threadgroupsize];
[computeEncoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilCompleted];
float3 *arr1 = _buffer[0].contents;
float* result = _buffer[1].contents;
unsigned long counter = 0;
for (unsigned long index = 0; index < _sp_lenght; index++)
{
if (result[index] != sqrtf(arr1[index].x*arr1[index].x + arr1[index].y*arr1[index].y + arr1[index].z*arr1[index].z)){
counter++;;
}
}
NSLog(#"ERROR counter %lu\n",counter);
}
kernel.metal
kernel void lenght(const device float3 *arr1,
device float *result,
uint index[[thread_position_in_grid]]){
result[index] = precise::sqrt(precise::pow(arr1[index].x,2) + precise::pow(arr1[index].y,2) + precise::pow(arr1[index].z,2));
}
32-bit precision is only about 7 decimal places and the difference you show is around 9-10 decimal places. So what you show is actually a bit better than one can expect a 32-bit float for precision. It sounds like you want 64-bit double precision, but that is not a built-in Metal datatype.
It may help if you multiply the values by say 100 or 1000 to move the decimal place up, then after your values have been added divide by that number.
Another possibility is to normalize your values first, so they are all in a range of say 0 to 1. Then you might even be able to use half precision.

Count number of zero(0) using objective-c

I want to count how many number of zero(0) before numeric number. Because I need to save those number which is present before numeric.
Exam:- suppose I have a number 0000102. So I want to calculate how many zero(0) before numeric start. In this exam we are see here is 4 zero's(0) are present. It is possible to calculate this?
for (i=0;i<string.length;i++)
{
if ([[string characterAtIndex:i] intValue] <= 9 || [[string characterAtIndex:i] intValue] > 0 )
{
i++;
}
else
{
numerOfZeros++;
}
}
int count = 0;
NSString *strr = #"0000102";
unichar findC;
for (int i = 0; i<strr.length; i++)
{
findC = [strr characterAtIndex:i];
if (findC == '0')
{
count++;
}
else
break;
}
NSLog(#"%d",count);
Recursive approach for a one liner:
#implementation NSString (category)
- (NSUInteger)zeroPrefixCount
{
return [string hasPrefix:#"0"] ? 1 + [[string substringFromIndex:1] zeroPrefixCount] : 0;
}
#end
This is not an optimal solution, performance-wise, but it's typical of your first days at programming classes.
usage
// x will be 4
NSUInteger x = [#"0000102" zeroPrefixCount];
I recommend you to save this kind of numbers as String itself and no need to further evaluate how many zeros are there rather do a string comparison if needed.
If you really want to count zeros in your number then you can consider converting it to a string and use NSRange and NSString helper methods to get what you want. Similar situation is answered here.
search if NSString contains value

Efficiently determine how much text can fit in a UILabel in IOS

I have an NSString and I want to know how much of that string will fit into a UILabel.
My code builds a test string by adding one character at a time from my original string. Each time I add a character I test the new string to see if it will fit into my label:
CGRect cutTextRect = [cutText boundingRectWithSize:maximumLabelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:stringAttributes context:nil];
Then I compare the height of that rect to the height of my label to see if the string overflowed.
This works, but instruments shows me that the loop is taking up all my cpu time.
Can anyone think of or know of a faster way to do this?
Thanks!
While not the prettiest:
- (NSString *)stringForWidth:(CGFloat)width fullString:(NSString *)fullString
{
NSDictionary *attributes = #{NSFontAttributeName: label.font};
if ([fullString sizeWithAttributes:attributes].width <= width)
{
return fullString;
}
// Might be worth researching more regarding 'average' char size
CGFloat approxCharWidth = [#"N" sizeWithAttributes:attributes].width;
NSInteger approxNumOfChars = (NSInteger)(width / approxCharWidth);
NSMutableString *resultingString = [NSMutableString stringWithString:[fullString substringToIndex:approxNumOfChars]];
CGFloat currentWidth = [resultingString sizeWithAttributes:attributes].width;
if (currentWidth < width)
{
// Try to 'sqeeze' another char.
while (currentWidth < width && approxNumOfChars < fullString.length)
{
approxNumOfChars++;
[resultingString appendString:[fullString substringWithRange:NSMakeRange(approxNumOfChars - 1, 1)]];
currentWidth = [resultingString sizeWithAttributes:attributes].width;
}
}
// String might be oversized
if (currentWidth > width)
{
while (currentWidth > width)
{
[resultingString deleteCharactersInRange:NSMakeRange(resultingString.length - 1, 1)];
currentWidth = [resultingString sizeWithAttributes:attributes].width;
}
}
// If dealing with UILabel, it's safer to have a smaller string than 'equal',
// 'clipping wise'. Otherwise, just use '<=' or '>=' instead of '<' or '>'
return [NSString stringWithString:resultingString];
}
There are a couple of loops, but each one is a 'fine tuning' and should only run a small number of times.
One way to improve efficiency is to get a better starting point than calculating be how many N's can fit in a given width.
I'm open to suggestions about that.
-Edit:
Regarding multiline label, once I know a given text for width, I can expect the following text (if any) will go to the next line.
In other words, getting 'text for width' is the tricky part, 'width for text' we get for free.

bad access playing back user recording synchronised with animation based on recording volume with array

I am trying to store an array based on audio input and then play animation frames corresponding to the input while the recording is played back.
The code is working up to now except after a while it crashes in the simulator and highlights
"CCLOG(#"adding image: %#", characterImageString);";
with this:
EXC_BAD_ACCESS (code=1, address=0xd686be8)
which is memory management I know but I am absolutely stumped.
if(isRecording){
int myInt;
NSString * characterImageString;
//get a number based on the volume input
float f = audioMonitorResults * 200; //convert max(0.06) to 12
f=((f/12)*10);
NSNumber *myNumber = [NSNumber numberWithDouble:(f+0.5)];
myInt = [myNumber intValue] + 1;
//create the image file name from the intiger we
//created from the audiomonitor results
if(myInt < 10){
characterImageString = [NSString stringWithFormat:#"fungus000%i.png",myInt];
} else if (myInt == 10){
characterImageString = [NSString stringWithFormat:#"fungus00%i.png",myInt];
}
CCLOG(#"adding image: %#", characterImageString);
//add each frame
[animationSequence addObject:characterImageString];
// print array contents
NSLog(#"animationSequence Array: %#", animationSequence);
// print array size
NSLog(#"animationSequence Number of Objects in Array: %u", [animationSequence count]); }
This is the code that plays as the audio is playing back:
-(void) updateAnimation:(ccTime) delta{
myFrame ++;
NSString *imageToDisplay;
imageToDisplay = animationSequence[myFrame];
CCTexture2D *currentTextureToDisplay = [[CCTextureCache sharedTextureCache] addImage:imageToDisplay];
[character setTexture:currentTextureToDisplay];
CCLOG(#"current texture to display: %#", currentTextureToDisplay);
if (myFrame >= [animationSequence count]) {
[self unschedule:#selector(updateAnimation:)];
}
Your characterImageString is nil if myInt > 10
The exception is thrown, because you're trying to print a variable, which hasn't been initialized.
You could try changing your code to something like this:
if(myInt < 10)
{
characterImageString = [NSString stringWithFormat:#"fungus000%i.png",myInt];
}
else if (myInt >= 10 && myInt < 100)
{
characterImageString = [NSString stringWithFormat:#"fungus00%i.png",myInt];
}
else if (myInt >= 100 && myInt < 1000)
{
characterImageString = [NSString stringWithFormat:#"fungus0%i.png",myInt];
}
else
{
characterImageString = [NSString stringWithFormat:#"fungus%i.png",myInt];
}
Obviously small debugging goes a long way. Could you add control printout for myInt before the line
if(myInt < 10){
to see the value of myInt before the crash?
if myInt is <= 0 your program has no protection for such case so resulting picture will not exist.
And for myInt > 10 the program will crash since NSString * characterImageString; is an automatic uninitialized variable of the random value.
hmmm ... some motherhood and apple pie , hard with the info available. Not certain what the initial float value is, so declare somewhere your min and max image numbers (say, kMinFrameNumber and kMaxFrameNumber). Since the float value could be anything at the start of your algorithm, add the following 'defensive' lines after computing myInt:
myInt=MAX(kMinFrameNumber,myInt);
myInt=MIN(kMaxFrameNumber,myInt);
then formatting :
characterImageString = [NSString stringWithFormat:#"fungus%04i.png",myInt];
finally, i doubt the exception is thrown at the highlighted line (that is where it is detected).
a. How did you declare the array animationSequence (is it retained?). If not, it could get autoreleased under your feet at some random interval, and you would be trying to send a message to a deallocated instance.
b. You should also check for bounds before addressing animationSequence
if(myFrame<[animationSequence count]-1) {
imageToDisplay = animationSequence[myFrame];
} else {
CCLOGERROR(#"Yelp ! addressing out of bounds!");
// terminate neatly here ! as in unschedule and return
}
c. check if your texture is nil before setting a sprite (it will accept a nil texture in cocos2d version 2.0) but, you are in the dark about the state of your code.

Positive/Negative button not displaying properly

I am trying to add a positive/negative button onto a numerical input in a UItextfield, but I cannot get it to function properly. What I want it to do is just add or remove a negative sign from the front of the numerical input. I am able to do that, however I cannot find a method to maintain the original number of decimal places. This is what I have tried:
- (IBAction) negsign
{
float input = [userinput.text floatValue];
float result = ((input * (-1)));
negstring = [NSString stringWithFormat:
#"%f", result];
userinput.text = negstring;
}
With this I get just a string of zeros after, like -23.0000000. I've tried limiting the decimal places by changing to #"%.2f" but I dont want extra zeros for whole integers, or rounding more than 2 decimals places. I just want it to take something like 34.658939 or 23 and make it -34.658939 or -23. Does anyone have a method to do this?
What would work best in your case is the following code:
float input = [userinput.text floatValue];
float result = ((input * (-1)));
NSNumber *resultNum = [NSNumber numberWithFloat:result];
NSString *resultString = [resultObj stringValue];
userinput.text = resultString;
If you're trying to make the number negative instead of reversing the sign, it'd be better if you replace float result = ((input * (-1))); with float result = -ABS(input);
Really, the best way to handle this would be to never convert it from a string in the first place. Just replace the first character as needed like this:
- (IBAction) negsign
{
unichar firstCharacter = [userinput.text characterAtIndex:0];
if (firstCharacter == '-') {
// Change the first character to a + sign.
userinput.text = [userinput.text stringByReplacingCharactersInRange:NSMakeRange(0, 1)
withString:#"+"];
} else if (firstCharacter == '+') {
// Change the first character to a - sign.
userinput.text = [userinput.text stringByReplacingCharactersInRange:NSMakeRange(0, 1)
withString:#"-"];
} else {
// There is no sign so we assume that it is positive.
// Insert the - at the beginning.
userinput.text = [userinput.text stringByReplacingCharactersInRange:NSMakeRange(0, 0)
withString:#"-"];
}
}