NSRange with incorrect paremeters? - ios

I am building this code to reverse a string, and all I need from you is why the parameters of the NSRange aren't working.
CODE:
for (int i = 0; i <= oldString.length; i++) {
NSRange range = NSMakeRange(i, oldString.length);
}

First of all your code is not sufficient, also if it work, doesn't do nothing.
Moreover, your app crash because NSRange takes 2 parameters: location and length.
So until location is 0, length can be oldString.length (i.e. all the string), otherwise, when location is > 0 and set as length oldString.length, it goes out of bounds.
Use this function where i use properly NSMakeRange as well:
- (NSString *)reverseString:(NSString *)string {
NSMutableString *newString = [NSMutableString new];
for (int i = (int)[string length]-1; i >= 0; i--) {
[newString appendString:[string substringWithRange:NSMakeRange(i, 1)]];
}
return [newString copy];
}

Your code is this:
for (int i = 0; i <= oldString.length; i++) {
NSRange range = NSMakeRange(i, oldString.length);
}
Now first of all, as Matteo has told you, it has no effect. All it does is make a bunch of ranges without using them.
But also let's look at those ranges! Just consider the successive values of range. Study your own code. Suppose our string oldString is 12345. Then its length is 5. Then the ranges would cover these strings in succession:
12345
2345[and one too far]
345[and two too far]
45[and three too far]
5[and four too far]
Those are pretty silly ranges!

Related

What would be a way to get all combinations of a NSString in Objective-C in a specific pattern?

I have a NSString *strName = #"JonnySmith";
What I want to do is get an NSArray of NSStrings with all possible combinations of a name, omitting certain characters. For example:
#"J";
#"Jo";
#"Jon";
but also combinations like:
#"JSmith";
#"JonSmith"
#"JonnSm";
#"JonSmt";
#"Smith";
#"th";
But they need to be in the order of the original name (the characters can't be out of order, just omitted). Basically traversing left to right in a loop, over and over again, until all possible combos are made.
What is the most efficient way to do this in Objective-C without make a mess?
Let's see if we can give you some pointers, everything here is abstract/pseudocode.
There are 2^n paths to follow, where n is the number of characters, as at each character you either add it or do not.
Taking your example after the first character you might produce #"" and #"J", then to each of these you either add the second character or not, giving: #"", #"J" (add nothing), #"o", "#Jo". Observe that if you have repeated characters anywhere in your input, in your sample you have two n's, this process may produce duplicates. You can deal with duplicates by using a set to collect your results.
How long is a character? Characters may consist of sequences of unicode code points (e.g. πŸ‡§πŸ‡ͺ - Belgium flag, if it prints in SO! Letters can be similarly composed), and you must not split these composed sequences while producing your strings. NSString helps you here as you can enumerate the composed sequences invoking a block for each one in order.
The above give you the pseudocode:
results <- empty set
for each composed character in input do block:
add to results a copy of each of its members with the composed character appended
You cannot modify a collection at the same time you enumerate it. So "add to results" can be done by enumerating the set creating a new collection of strings to add, then adding them all at once after the enumeration:
new items <- empty collection
for every item in results
add to new items (item appending composed character)
results union new items
Optimising it slightly maybe: in (2) we had the empty string and in (4) we append to the empty string. Maybe you could not add the empty string to start and initialise new items to the composed character?
Hint: why did I write the non-specific collection in (4)?
Have fun. If you code something up and get stuck ask a new question, describe your algorithm, show what you've written, explain the issue etc. That will (a) avoid down/close votes and (b) help people to help you.
One possibility is to consider every combination to be a mask of bits, where 1 means the character is there and 0 means the character is missing, for example:
100010000 for JonnySmith will mean JS
000000001 for JonnySmith will mean h
It's simple to generate such masks because we can just iterate from 1 (or 000000001) to 111111111.
Then we only have to map that mask into characters.
Of course, some duplicates are generated because 1110... and 1101... will both be mapped to Jon....
Sample implementation:
NSString *string = #"JonnySmith";
// split the string into characters (every character represented by a string)
NSMutableArray<NSString *> *characters = [NSMutableArray array];
[string enumerateSubstringsInRange:NSMakeRange(0, string.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
[characters addObject:substring];
}];
// let's iterate over all masks
// start with zero if you want empty string to be included
NSUInteger min = 1;
NSUInteger max = (1 << characters.count) - 1;
NSMutableString *buffer = [[NSMutableString alloc] initWithCapacity:characters.count];
NSMutableSet *set = [NSMutableSet set];
for (NSUInteger mask = min; mask <= max; mask++) {
[buffer setString:#""];
// iterate over all bits in the generated mask, map it to string
for (NSInteger charIndex = 0; charIndex < characters.count; charIndex++) {
if ((mask & (1 << (characters.count - charIndex - 1))) != 0) {
[buffer appendString:[characters objectAtIndex:charIndex]];
}
}
// add the resulting string to Set, will handle duplicates
[set addObject:[buffer copy]];
}
NSLog(#"Count: %#", #(set.count)); // 767
The size for NSUInteger will give us the maximum number of characters we can use using this method.
Noticed the question is old but no answer is accepted. I think you can generate all permutations and then omit results which don't match your criteria (or tweak this code per your needs)
#interface NSString (Permute)
- (NSSet *)permutations;
#end
#implementation NSString (Permute)
- (NSSet *)permutations {
if ([self length] <= 1) {
return [NSSet setWithObject:self];
}
NSMutableSet *s = [NSMutableSet new];
[s addObject:[self substringToIndex:1]];
for (int i = 1; i < self.length; i++) {
char c = [self characterAtIndex:i];
s = [self words:s insertingLetterAtAllPositions:[NSString stringWithFormat:#"%C",c]];
}
return [s copy];
}
- (NSMutableSet *)words:(NSSet *)words insertingLetterAtAllPositions:(NSString *)letter {
NSMutableSet *collector = [NSMutableSet new];
for (NSString *word in words) {
[collector unionSet:[word allInsertionsOfLetterAtAllPositions:letter]];
}
return collector;
}
- (NSMutableSet *)allInsertionsOfLetterAtAllPositions:(NSString *)letter {
NSMutableSet *collector = [NSMutableSet new];
for (int i = 0; i < [self length] + 1; i++) {
NSMutableString *mut = [self mutableCopy];
[mut insertString:letter atIndex:i];
[collector addObject:[mut copy]];
}
return collector;
}
#end
// usage
[#"abc" permutations];
You can do it quite easily with a little recursion. It works like this:
Check if the length is 1, then return an array of 2 elements, the empty string and the string.
Call recursively with input string minus the first character and assign to sub-result.
Duplicate the sub-result, adding the first character to each string.
Return the result.
Remember to not call for empty string. If you want to omit the empty result string just remove the first element. Also, if you use the same letter several times, you will get some result strings several times. Those can be removed afterwards.
- (void)combinations:(NSString *)string result:(NSMutableArray *)result {
if (string.length == 1) {
[result addObjectsFromArray:#[ #"", string ]];
} else {
[self combinations:[string substringFromIndex:1] result:result];
for (NSInteger i = result.count - 1; i >= 0; --i)
[result addObject:[[string substringToIndex:1] stringByAppendingString:result[i]]];
}
}
// Call like this, for speed only one mutable array is allocated
NSString *test = #"0123456789";
NSMutableArray *result = [NSMutableArray arrayWithCapacity:1 << test.length];
[self combinations:test result:result];

How to shorten an NSString that might include e.g. Emojis to the maximal length allowed for a HFS+ filename

Apples documentation says:
[...] current file systems such as HFS+ (used by Mac OS X) allow you to create filenames with a 255-character limit [...] symbols may actually take up to the equivalent of nine English characters to store [...] This should be considered when attempting to create longer names.
How do I limit the length of a NSString in a way that it is truly shorter than 255 characters, even when it includes symbols that might take more than one character to store?
I add my current implementation below. If i add e.g. emojis to the string, while length answers the resulting string would be by far smaller than 255, it is still too long to be accepted by a NSSavePanel as file name.
NSRange stringRange = {0, MIN([fileName length], 255)};
stringRange = [fileName rangeOfComposedCharacterSequencesForRange:stringRange];
fileName = [fileName substringWithRange:stringRange];
As suggested by #JoshCaswell, I did modify this answer to a similar question. It apparently does work (I wrote several tests), but it seems strange to me. Such an obvious task cannot be so complicated to achieve?
// filename contains the NSString that should be shortened
NSMutableString *truncatedString = [NSMutableString string];
NSUInteger bytesRead = 0;
NSUInteger charIdx = 0;
while (bytesRead < 250 && charIdx < [fileName length])
{
NSRange range = [fileName rangeOfComposedCharacterSequencesForRange:NSMakeRange(charIdx, 1)];
NSString *character = [fileName substringWithRange:NSMakeRange(charIdx, range.length)];
bytesRead += [character lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
charIdx = charIdx + range.length;
if (bytesRead <= 250)
[truncatedString appendString:character];
}
rangeOfComposedCharacterSequencesForRange: is basically doing the opposite of what you want: you give it a range that counts 255 composed characters, and it gives you the byte range that encompasses those, which might end up being much more than you want.
Unfortunately to do the reverse, you have to count the bytes manually. This isn't too hard, however, with enumerateSubstringsInRange:options:usingBlock:. Passing NSStringEnumerationByComposedCharacterSequences for the options gives you exactly what it says: each composed character in turn. You can then count the size of each with lengthOfBytesUsingEncoding:, passing the final encoding you'll be using (presumably UTF-8). Add up the bytes, keeping track of the character-based index, and stop when you've seen too many.
NSString * s = /* String containing multibyte characters */;
NSUInteger maxBytes = ...;
__block NSUInteger seenBytes = 0;
__block NSUInteger truncLength = 0;
NSRange fullLength = (NSRange){0, [s length]};
[s enumerateSubstringsInRange:fullLength
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:
^(NSString *substring, NSRange substringRange,
NSRange _, BOOL *stop)
{
seenBytes += [substring lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if( seenBytes > maxBytes ){
*stop = YES;
return;
}
else {
truncLength += substringRange.length;
}
}];
NSString * truncS = [s substringToIndex:truncLength];

Find substring range of NSString with unicode characters

If I have a string like this.
NSString *string = #"πŸ˜€1πŸ˜€3πŸ˜€5πŸ˜€7πŸ˜€"
To get a substring like #"3πŸ˜€5" you have to account for the fact the smiley face character take two bytes.
NSString *substring = [string substringWithRange:NSMakeRange(5, 4)];
Is there a way to get the same substring by using the actual character index so NSMakeRange(3, 3) in this case?
Thanks to #Joe's link I was able to create a solution that works.
This still seems like a lot of work for just trying to create a substring at unicode character ranges for an NSString. Please post if you have a simpler solution.
#implementation NSString (UTF)
- (NSString *)substringWithRangeOfComposedCharacterSequences:(NSRange)range
{
NSUInteger codeUnit = 0;
NSRange result;
NSUInteger start = range.location;
NSUInteger i = 0;
while(i <= start)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
NSRange substringRange;
substringRange.location = result.location;
NSUInteger end = range.location + range.length;
while(i <= end)
{
result = [self rangeOfComposedCharacterSequenceAtIndex:codeUnit];
codeUnit += result.length;
i++;
}
substringRange.length = result.location - substringRange.location;
return [self substringWithRange:substringRange];
}
#end
Example:
NSString *string = #"πŸ˜€1πŸ˜€3πŸ˜€5πŸ˜€7πŸ˜€";
NSString *result = [string substringWithRangeOfComposedCharacterSequences:NSMakeRange(3, 3)];
NSLog(#"%#", result); // 3πŸ˜€5
Make a swift extension of NSString and use new swift String struct. Has a beautifull String.Index that uses glyphs for counting characters and range selecting. Very usefull is cases like yours with emojis envolved

How to Split NSString to multiple Strings after certain number of characters

I am developing an iOS app using Xcode 4.6.2.
My app receives from the server lets say for example 1000 characters which is then stored in NSString.
What I want to do is: split the 1000 characters to multiple strings. Each string must be MAX 100 characters only.
The next question is how to check when the last word finished before the 100 characters so I don't perform the split in the middle of the word?
A regex-based solution:
NSString *string = // ... your 1000-character input
NSString *pattern = #"(?ws).{1,100}\\b";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern: pattern options: 0 error: &error];
NSArray *matches = [regex matchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSMutableArray *result = [NSMutableArray array];
for (NSTextCheckingResult *match in matches) {
[result addObject: [string substringWithRange: match.range]];
}
The code for the regex and the matches part is taken directly from the docs, so the only difference is the pattern.
The pattern basically matches anything from 1 to 100 characters up to a word boundary. Being a greedy pattern, it will give the longest string possible while still ending with a whole word. This ensures that it won't split any words in the middle.
The (?ws) makes the word recognition work with Unicode's definition of word breaks (the w flag) and treat a line end as any other character (the s flag).
Notice that the algorithm doesn't handle "words" with more than 100 characters well - it will give you the last 100 characters and drop the first part, but that should be a corner case.
(assuming your words are separated by a single space, otherwise use rangeOfCharacterFromSet:options:range:)
Use NSString -- (NSRange)rangeOfString:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)aRange with:
aString as #" "
mask as NSBackwardsSearch
Then you need a loop, where you check that you haven't already got to the end of the string, then create a range (for use as aRange) so that you start 100 characters along the string and search backwards looking for the space. Once you find the space, the returned range will allow you to get the string with substringWithRange:.
(written freehand)
NSRange testRange = NSMakeRange(0, MIN(100, sourceString.length));
BOOL complete = NO;
NSMutableArray *lines = [NSMutableArray array];
while (!complete && (testRange.location + testRange.length) < sourceString.length) {
NSRange hitRange = [sourceString rangeOfString:#"" options:NSBackwardsSearch range:testRange];
if (hitRange.location != NSNotFound) {
[lines addObject:[sourceString substringWithRange:hitRange];
} else {
complete = YES;
}
NSInteger index = hitRange.location + hitRange.length;
testRange = NSMakeRange(index, MIN(100, sourceString.length - index));
}
This can help
- (NSArray *)chunksForString(NSString *)str {
NSMutableArray *chunks = [[NSMutableArray alloc] init];
double sizeChunk = 100.0; // or whatever you want
int length = 0;
int loopSize = ceil([str length]/sizeChunk);
for (int index = 0; index < loopSize; index++) {
NSInteger newRangeEndLimit = ([str length] - length) > sizeChunk ? sizeChunk : ([str length] - length);
[chunks addObject:[str substringWithRange:NSMakeRange(length, newRangeEndLimit)];
length += 99; // Minus 1 from the sizeChunk as indexing starts from 0
}
return chunks;
}
use NSArray *words = [stringFromServer componentsSeparatedBy:#" "];
this will give you words.
if you really need to make it nearest to 100 characters, start appending strings maintaining the total length of the appended strings and check that it should stay < 100.

What does NSMakeRange(i, 1) mean?

I just start to learn iOS.
What does "NSMakeRange(i, 1)" mean?
for (int i = 0; i < [name length]; i++)
{
NSRange range = NSMakeRange(i, 1);
NSString *subString = [name substringWithRange:range];
const char *cString = [subString UTF8String];
if (strlen(cString) != 3)
{
return NO;
}
}
Where is it used?
NSMakeRange(i, 1) creates a range with location i and length 1. See the documentation for NSMakeRange and NSString substringWithRange for further information and related functions.
Alt-click the function name in Xcode, you’ll get a reference. The function creates a range that starts at i and has length of 1. In essence, you’re picking individual characters from the string.

Resources