Better way to get line ranges in a UITextView - objective-c

Right now, I am able to get the text ranges of each line in a UITextView containing n lines by using cycling through the tokenizer with paragraph granularity. Unfortunately, that means my search algorithm for the m-th line in the text is of order n. Is there any easier way for me to find the range other than making my algorithm log n? The following is how I find my text range for now:
- (UITextRange *)textRangeOfLineAtIndex:(NSUInteger)index {
UITextPosition *position = self.beginningOfDocument;
NSUInteger lineCount = 0;
while([self comparePosition:self.endOfDocument toPosition:position] == NSOrderedDescending && lineCount < index) {
position = [self.tokenizer positionFromPosition:position toBoundary:UITextGranularityParagraph inDirection:UITextStorageDirectionForward];
++lineCount;
}
return [self rangeEnclosingPosition:position withGranularity:UITextGranularityParagraph inDirection:UITextStorageDirectionForward];
}

Related

Looping over an NSmutatable Array starting from a certain index

I have a quick question how can I loop over an NSMutable array starting from a certain index.
For Example I have these double loops I want k to start from the same index as l.
for (Line *l in L)
{
for (Line *k in L)
{
............
}
}
To elaborate further, lets say L has 10 object so l start from 0-10 and k from 0 -10. What I want is if l is equal 1 k should start from 1-10 rather than 0 - 10 and when l is equal 2 k should start from 2- 10 rather than 0. Any help is Appreciated
Objective-C is an extension of C, lookup the C for loop and you'll have your answer. HTH
Addendum
I was going to let you benefit from the learning experience of looking up the C for yourself, however at the time of writing all other answers since added give the code but it is not complete, so here is what you need to produce the l and k values in the order you wish:
for(NSInteger lIndex = 0; lIndex < L.count; lIndex++)
{
Line *l = L[lIndex]; // index into your array to get the element
for(NSInteger kIndex = lIndex; kIndex < L.count; kIndex++)
{
Line *k = L[kIndex];
// process your l and k
}
}
As you can see the for has three sub-parts which are the initialisation, condition, and increment. The initialisation is performed first, then the condition to determine whether to execute the for body, and the increment is executed after the statements in the body and before the condition is tested to determine if another iteration should be performed. A for loop is roughly (there are some differences that are unimportant here) to the while loop:
initialisation;
while(condition)
{
body statements;
increment;
}
You simply need to modify for-statement.
NSInteger indexYouNeed;
NSInteger iterationCount;
for (int i = indexYouNeed; i < iterationCount; i++) {
/* Your code here */
}
You may find this link helpfulll.
You have to use an indexed (ordinary) for loop instead of fast enumeration (for-in):
int l;
for (l=startValue; l<=endValue; l++)
{
int i;
for (int i=l; i<=endValue; i++)
{
…
}
}

Objective C Math - Geometric sequence results

For my app (OSX, not IOS) i have a geometric sequence (stored in container array) generated like this:
- (void)initContainerFor:(NSInteger)maxRows
{
self.container = [[NSMutableArray alloc] init];
NSInteger start = [self.firstTextFieldValue integerValue];
NSInteger ratio = [self.secondTextFieldValue integerValue];
// ASCENDING
for (NSInteger i = 1; i < (maxRows +1 ); i++)
{
NSInteger currentValue = start * pow(ratio,i-1);
[self.container addObject:currentValue];
}
}
User can enter the "start" and "ratio" integer. I want to give a feedback if limit (MAX_INT) is exceeded. I wrote this function:
- (BOOL)maxCheck
{
if ([self.container lastObject] > INT_MAX)
return false;
return true;
}
But this seems not to work. If i enter 2 for start and 200 for ratio i have this container content:
Container: (
2,
400,
80000,
16000000,
"-1094967296",
49872896,
1384644608,
2051014656,
"-2113929216",
0
)
Please help to understand what i see in the container (strings??) and how to get a correct check for the maximum value.
As you can see from log of array, you actually exceed INT_MAX limit twice, when next element become negative. So you can just add check to initContainer: method - if element is less then the previous, INT_MAX limit is reached.
TIP: INT_MAX is a signed value.
You can not compare the value after overflow with INT_MAX, as the overflow already happened. Or put differently, by its very definition and semantics, no integer can be bigger than INT_MAX.
What you can test is
[self.container lastObject] > INT_MAX/ratio
to find the sequence element that would cause overflow in the next step.

additional logic to this exercise missing

Writing a basic program to count the number of words in a string. I've changed my original code to account for multiple spaces between words. By setting one variable to the current index and one variable to the previous index and comparing them, I can say "if this current index is a space, but the previous index contains something other than a space (basically saying a character), then increase the word count".
int main(int argc, const char * argv[]) {
#autoreleasepool {
//establishing the string that we'll be parsing through.
NSString * paragraph = #"This is a test paragraph and we will be testing out a string counter.";
//we're setting our counter that tracks the # of words to 0
int wordCount = 0;
/*by setting current to a blank space ABOVE the for loop, when the if statement first runs, it's comparing [paragraph characterAtIndex:i to a blank space. Once the loop runs through for the first time, the next value that current will have is characterAtIndex:0, while the if statement in the FOR loop will hold a value of characterAtIndex:1*/
char current = ' ';
for (int i=0; i< paragraph.length; i++) {
if ([paragraph characterAtIndex:i] == ' ' && (current != ' ')) {
wordCount++;
}
current = [paragraph characterAtIndex:i];
//after one iteration, current will be T and it will be comparing it to paragraph[1] which is h.
}
wordCount ++;
NSLog(#"%i", wordCount);
}
return 0;
}
I tried adding "or" statements to account for delimiters such as ";" "," and "." instead of just looking at a space. It didn't work...any idea what I can do, logically speaking, to account for anything that isn't a letter (but preferably just limiting it to these four delimiters - . , ; and space.
A standard way to solve these types of problems is to build a finite state machine, your code isn't quite one but its close.
Instead of thinking about comparing the previous and current characters think in terms of states - you can start with just two, in a word and not in a word.
Now for each state you consider what the current character implies in terms of actions and changes to the state. For example, if the state is not in a word and the current character is a letter then the action is increment word count and the next state is in a word.
In (Objective-)C you can build a simple finite state machine using an enum to give the states names and a case statement inside a loop. In pseudo-code this is something like:
typedef enum { NotInWord, InWord } State;
State currentState = NotInWord;
NSUInteger wordCount = 0;
for currentChar in sourceString
case currentState of
NotInWord:
if currentChar is word start character -- e.g. a letter
then
increment wordCount;
currentState = InWord;
InWord:
if currentChar is not a word character -- e.g. a letter
then
currentState = NotInWord;
end case
end for
The above is just a step from your original algorithm - recasting it in terms of states rather than the previous character.
Now if you want to get smarter you can add more states. For example how many words are there in "Karan's question"? Two. So you might want to allow a single apostrophe in a word. To handle that you can add a state AfterApostrophe whose logic is the same as the current InWord; and modify InWord logic to include if the current character is an apostrophe the next state is AfterApostrophe - that would allow one apostrophe in a word (or its end, which is also valid). Next you might want to consider hyphenated words, etc...
To test if a character is a particular type you have two easy choices:
If this is just an exercise and you are happy to stick with the ASCII range of characters there are functions such as isdigit(), isletter() etc.
If you want to handle full Unicode you can use the NSCharacterSet type with its pre-defined sets for letters, digits, etc.
See the documentation for both of the above choices.
HTH
I don't understand, You should be able to add or statements....
int main(void) {
char paragraph[] = "This is a test paragraph,EXTRAWORDHERE and we will be testing out a string.";
char current = ' ';
int i;
int wordCount = 0;
for (i = 0; i < sizeof(paragraph); i++){
if ((paragraph[i] == 32 || paragraph[i] == 44) && !(current == 32 || current == 44)){ //32 = ascii for space, 44 for comma
wordCount++;
}
current = paragraph[i];
}
wordCount++;
printf("%d\n",wordCount);
return 0;
}
I suppose it would be better to change the comparison of current from a not equal to into an equal to. Hopefully that helps.

NSString constrainedToSize method?

Not to get confused with the NSString sizeWithFont method that returns a CGSize, what I'm looking for is a method that returns an NSString constrained to a certain CGSize. The reason I want to do this is so that when drawing text with Core Text, I can get append an ellipses (...) to the end of the string. I know NSString's drawInRect method does this for me, but I'm using Core Text, and kCTLineBreakByTruncatingTail truncates the end of each line rather than the end of the string.
There's this method that I found that truncates a string to a certain width, and it's not that hard to change it to make it work for a CGSize, but the algorithm is unbelievably slow for long strings, and is practically unusable. (It took over 10 seconds to truncate a long string). There has to be a more "computer science"/mathematical algorithm way to do this faster. Anyone daring enough to try to come up with a faster implementation?
Edit: I've managed to make this in to a binary algorithm:
-(NSString*)getStringByTruncatingToSize:(CGSize)size string:(NSString*)string withFont:(UIFont*)font
{
int min = 0, max = string.length, mid;
while (min < max) {
mid = (min+max)/2;
NSString *currentString = [string substringWithRange:NSMakeRange(min, mid - min)];
CGSize currentSize = [currentString sizeWithFont:font constrainedToSize:CGSizeMake(size.width, MAXFLOAT)];
if (currentSize.height < size.height){
min = mid + 1;
} else if (currentSize.height > size.height) {
max = mid - 1;
} else {
break;
}
}
NSMutableString *finalString = [[string substringWithRange:NSMakeRange(0, min)] mutableCopy];
if(finalString.length < self.length)
[finalString replaceCharactersInRange:NSMakeRange(finalString.length - 3, 3) withString:#"..."];
return finalString;
}
The problem is that this sometimes cuts the string too short when it has room to spare. I think this is where that last condition comes in to play. How do I make sure it doesn't cut off too much?
Good news! There is a "computer science/mathematical way" to do this faster.
The example you link to does a linear search: it just chops one character at a time from the end of the string until it's short enough. So, the amount of time it takes will scale linearly with the length of the string, and with long strings it will be really slow, as you've discovered.
However, you can easily apply a binary search technique to the string. Instead of starting at the end and dropping off one character at a time, you start in the middle:
THIS IS THE STRING THAT YOU WANT TO TRUNCATE
^
You compute the width of "THIS IS THE STRING THAT". If it is too wide, you move your test point to the midpoint of the space on the left. Like this:
THIS IS THE STRING THAT YOU WANT TO TRUNCATE
^ |
On the other hand, if it isn't wide enough, you move the test point to the midpoint of the other half:
THIS IS THE STRING THAT YOU WANT TO TRUNCATE
| ^
You repeat this until you find the point that is just under your width limit. Because you're dividing your search area in half each time, you'll never need to compute the width more than log2 N times (where N is the length of the string) which doesn't grow very fast, even for very long strings.
To put it another way, if you double the length of your input string, that's only one additional width computation.
Starting with Wikipedia's binary search sample, here's an example. Note that since we're not looking for an exact match (you want largest that will fit) the logic is slightly different.
int binary_search(NSString *A, float max_width, int imin, int imax)
{
// continue searching while [imin,imax] is not empty
while (imax >= imin)
{
/* calculate the midpoint for roughly equal partition */
int imid = (imin + imax) / 2;
// determine which subarray to search
float width = ComputeWidthOfString([A substringToIndex:imid]);
if (width < max_width)
// change min index to search upper subarray
imin = imid + 1;
else if (width > max_width )
// change max index to search lower subarray
imax = imid - 1;
else
// exact match found at index imid
return imid;
}
// Normally, this is the "not found" case, but we're just looking for
// the best fit, so we return something here.
return imin;
}
You need to do some math or testing to figure out what's the right index at the bottom, but it's definitely imin or imax, plus or minus one.

Objective C - Core Text, number of characters in a line?

Given an index for current character, how can I determine the number of line that the selected character is at?
Given a CTLine how can I determine the number of characters in it?
For the first one:
int currentCharacterIndex = 12; // You define this.
CFArrayRef lines = CTFrameGetLines(frame);
int currentLine = 0;
for (CTLineRef line in lines) {
currentLine++;
CFRange range = CTLineGetStringRange(line);
if (currentCharacterIndex > range.location)
break;
}
// Current line is now the line that the currentCharacterIndex resides at
For the second one:
CFRange range = CTLineGetStringRange(line);
CFIndex length = range.length; // Number of characters
Can't be sure these work as I haven't tested them but it's worth a go.