I've been attempting to solve Problem 4 on Project Euler which is:
A palindromic number reads the same both ways. The largest palindrome >made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.
I've been doing this using Objective-C and I thought I had come up with an solution but for some reason I get a Thread 1: signal SIGABRT-error a bit in and I was hoping that you guys could help me figure out what's wrong with my code:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSMutableArray *palindromes = [[NSMutableArray alloc] init];
for (int i = 1; i < 1000; i++) {
for (int j = 1; j < 1000; j++) {
int k = i * j;
NSString *number = [NSString stringWithFormat:#"%d", k];
NSUInteger length = [number length];
NSUInteger half = (length / 2);
NSString *part1 = [number substringWithRange:NSMakeRange(0, half)];
//It's at the line below that I get the error
NSString *part2 = [number substringWithRange:NSMakeRange(half, [number length])];
if (part1 == part2) {
[palindromes addObject:number];
}
}
}
for (int l = 0; l < [palindromes count]; l++) {
NSLog(#"%d", l);
}
}
return 0;
}
What I want my code to do, is to multiply first 1 with 1, 2, 3, ... 999 and then do the same with 2, 3, 4 and so on up until 999. For each iteration the number of the current multiplication will be stored in k. k is then converted to a NSString named number. The length and half the length of number is then stored in the variables length respectively half. The first half of the current number is then stored in the NSString part1 and the second half in the NSString part2. I then compare these two substrings and if they are equal they get added to the palindrome-array and the array is later printed out.
However, when I try to run this in Xcode it stops when it gets to the line I have marked in my code and throws me a Thread 1: signal SIGABRT-error. The output in the console looks like this:
2015-12-30 17:26:41.522 Euler4[2075:167976] *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[NSTaggedPointerString substringWithRange:]: Range {1, 2} out of bounds; string length 2'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff8e6a9ae2 __exceptionPreprocess + 178
1 libobjc.A.dylib 0x00007fff8a202f7e objc_exception_throw + 48
2 CoreFoundation 0x00007fff8e6a998d +[NSException raise:format:] + 205
3 CoreFoundation 0x00007fff8e644cfa -[NSTaggedPointerString substringWithRange:] + 394
4 Euler4 0x00000001000013fb main + 507
5 libdyld.dylib 0x00007fff946b85ad start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
I do get that the problem is that the range for part2 is out of bounds but I don't get how it can be that, since both the upper and lower limit should be dynamic and tailored for each and every number, right?
Any help will be greatly appreciated!
You should do :
NSString *part2 = [number substringWithRange:NSMakeRange(half, half)];
Since you start from half and you need to read till next half(then length).
However your code to check the palindrome is not correct.
The corrected code should be as:
NSMutableArray *palindromes = [[NSMutableArray alloc] init];
for (int i = 1; i < 1000; i++) {
for (int j = 1; j < 1000; j++) {
int k = i * j;
NSString *number = [NSString stringWithFormat:#"%d", k];
NSUInteger length = [number length];
NSUInteger half = (length / 2);
NSString *part1 = [number substringWithRange:NSMakeRange(0, half)];
NSString *part2 = [number substringWithRange:NSMakeRange(half, half)];
NSMutableString *reverseStringPart2 = [NSMutableString stringWithCapacity:[part2 length]];
//following blocks reverses the part2 and store it in reverseStringPart2
[part2 enumerateSubstringsInRange:NSMakeRange(0,[part2 length])
options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences)
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[reverseStringPart2 appendString:substring];
}];
//note == is not used since you needed to compare two strings value not their addresses
if ([part1 isEqualToString:reverseStringPart2]) {
[palindromes addObject:number];
}
}
}
Edit:
Since you want to find the largest palindrome number formed by mulitplication of two 3-digits number. This can be done as simple as this:
for (int k = 999*999; k > 0; k--) {
NSString *number = [NSString stringWithFormat:#"%d", k];
NSUInteger length = [number length];
NSUInteger half = (length / 2);
NSString *part1 = [number substringWithRange:NSMakeRange(0, half)];
NSString *part2 = [number substringWithRange:NSMakeRange(half, half)];
NSMutableString *reverseStringPart2 = [NSMutableString stringWithCapacity:[part2 length]];
[part2 enumerateSubstringsInRange:NSMakeRange(0,[part2 length])
options:(NSStringEnumerationReverse | NSStringEnumerationByComposedCharacterSequences)
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
[reverseStringPart2 appendString:substring];
}];
if ([part1 isEqualToString:reverseStringPart2]) {
NSLog(#"The largest palindrome of multiplicatin of two 3-digits number is: %#", number);
break;
}
}
*Note: You can find the above without creating a string and doing manipulations. Simply create an integer and with mathematical logic you can check if it is palindrome or not. Start from the 999*999 and move towards 1. The first one you find is the result, then break the loop.*
The arguments to NSMakeRange are location and length. If you set location to half, then the largest legal value for length is number.length - half. But if you change to use that, then for an odd-length string, you'll get the middle character in part2 and not in part1. You actually want to create your second NSRange as NSMakeRange(number.length - half, half).
Your next problem will be that you never find a palindrome, because you are testing for string equality with part1 == part2, but you need to test with [part1 isEqualToString:part2].
After you fix that, you'll find that your palindrome test is wrong. Your test will pass for 123123, but 123123 is not a palindrome. You need to test that [number isEqualToString: number.reversed]. (You don't have to mess with NSMakeRange at all!) But there is no reversed property on NSString, so you'll have to write your own code to reverse the string.
Incidentally, you might as well start your loops at 100 instead of at 1, because the problem says “the product of two 3-digit numbers”, and numbers in the range 1…99 are not 3-digit numbers.
The length member of the struct NSRange represents the length of the substring starting from location, the first member.
If the string is 1234 and half is 2 then NSRange(2, 4) is character 2 - 6, that's always out of bounds.
Calculate the remaining characters for example
NSMakeRange(half, length - half)
I think you might be misinterpreting NSRange and NSMakeRange. The two parameters of NSMakeRange are the start location and length of the range, not the start location and end location. If you make this change:
//NSString *part2 = [number substringWithRange:NSMakeRange(half, [number length])];//old line
NSString *part2 = [number substringWithRange:NSMakeRange(half, [number length] - half)];//new line
Then you should eliminate out of bounds errors.
As a side note, your palindrome checking logic has a few problems. You have to mirror the second part of the string before checking for equality (be careful how you do this with strings). You also have to be a little more sophisticated to handle numbers with an odd number of digits.
Related
I'm trying to figure out the best approach to a problem. I have an essentially random alphanumeric string that I'm generating on the fly:
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
You can see that there are no special characters, just numbers and letters, and all the letters are lowercase. Changing all the letters in this string to uppercase is easy:
[string capitalizedString];
The hard part is that I want to capitalize random characters in this string, not all of them. For example, this could be the output on one execution:
E04325cA24CF20ac6bD6eBF73C376b20Ac57192DAD83b22602264e92daC076611b51142AE12D2D92022Eb2C77F
This could be the output on another, since it's random:
e04325ca24cf20aC6bd6eBF73C376B20Ac57192DAd83b22602264E92dAC076611B51142AE12D2d92022EB2c77f
In case it makes this easier, let's say I have two variables as well:
int charsToUppercase = 12;//hardcoded value for how many characters to uppercase here
int totalChars = 90;//total string length
In this instance it would mean that 12 random characters out of the 90 in this string would be uppercased. What I've figured out so far is that I can loop through each char in the string relatively easily:
NSUInteger len = [string length];
unichar buffer[len+1];
[string getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(#"loop through each char");
for(int i = 0; i < len; i++) {
NSLog(#"%C", buffer[i]);
}
Still stuck with selecting random chars in this loop to uppercase, so not all are uppercased. I'm guessing a condition in the for loop could do the trick well, given that it's random enough.
Here's one way, not particularly concerned with efficiency, but not silly efficiency-wise either: create an array characters in the original string, building an index of which ones are letters along the way...
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
NSMutableArray *chars = [#[] mutableCopy];
NSMutableArray *letterIndexes = [#[] mutableCopy];
for (int i=0; i<string.length; i++) {
unichar ch = [string characterAtIndex:i];
// add each char as a string to a chars collection
[chars addObject:[NSString stringWithFormat:#"%c", ch]];
// record the index of letters
if ([[NSCharacterSet letterCharacterSet] characterIsMember:ch]) {
[letterIndexes addObject:#(i)];
}
}
Now, select randomly from the letterIndexes (removing them as we go) to determine which letters shall be upper case. Convert the member of the chars array at that index to uppercase...
int charsToUppercase = 12;
for (int i=0; i<charsToUppercase && letterIndexes.count; i++) {
NSInteger randomLetterIndex = arc4random_uniform((u_int32_t)(letterIndexes.count));
NSInteger indexToUpdate = [letterIndexes[randomLetterIndex] intValue];
[letterIndexes removeObjectAtIndex:randomLetterIndex];
[chars replaceObjectAtIndex:indexToUpdate withObject:[chars[indexToUpdate] uppercaseString]];
}
Notice the && check on letterIndexes.count. This guards against the condition where charsToUppercase exceeds the number of chars. The upper bound of conversions to uppercase is all of the letters in the original string.
Now all that's left is to join the chars array into a string...
NSString *result = [chars componentsJoinedByString:#""];
NSLog(#"%#", result);
EDIT Looking discussion in OP comments, you could, instead of acharsToUppercase input parameter, be given a probability of uppercase change as an input. That would compress this idea into a single loop with a little less data transformation...
NSString *string = #"e04325ca24cf20ac6bd6ebf73c376b20ac57192dad83b22602264e92dac076611b51142ae12d2d92022eb2c77f";
float upperCaseProbability = 0.5;
NSMutableString *result = [#"" mutableCopy];
for (int i=0; i<string.length; i++) {
NSString *chString = [string substringWithRange:NSMakeRange(i, 1)];
BOOL toUppercase = arc4random_uniform(1000) / 1000.0 < upperCaseProbability;
if (toUppercase) {
chString = [chString uppercaseString];
}
[result appendString:chString];
}
NSLog(#"%#", result);
However this assumes a given uppercase probability for any character, not any letter, so it won't result in a predetermined number of letters changing case.
I have an NSMutableString that contains a lot of data. A short example of it is:
MP3: name length album
MP3: name length album
MP3: name length album
MP3: name length album
Now, what I'm trying to accomplish is to parse the string to contain only the first 3 MP3 data sets. So once the fourth instance of "MP3:" is found, stop parsing.
I've tried multiple thing to accomplish this, but I've been staring at it too long and am starting to go goofy. If it was an array it would be simple but unfortunately it's a string. Does anyone know how to accomplish the logic behind this?
To add more clarity: I would still like the first the MP3's to appear. They will be different every time so I can't do a subStringToIndex.
To do this efficiently, assuming the input string is very long (containing thousands of lines after the first three), you need to avoid using componentsSeparatedByString:.
Instead, find the first three newlines in the string, by using rangeOfString: repeatedly.
NSString *input = #"MP3: line1\nMP3: line2\nMP3: line3\nMP3: line4\nMP3: line5\n";
NSUInteger lineStartLocation = 0;
NSUInteger inputLength = input.length;
for (int i = 0; i < 3; ++i) {
NSRange searchRange = NSMakeRange(lineStartLocation, inputLength - lineStartLocation);
NSRange newlineRange = [input rangeOfString:#"\n" options:0 range:searchRange];
if (newlineRange.location == NSNotFound) {
// Not enough lines in input!
break;
} else {
lineStartLocation = newlineRange.location + 1;
}
}
NSString *top3 = [input substringToIndex:lineStartLocation];
NSLog(#"top3 = %#", top3);
NSArray *lines = [data componentsSeparatedByString:#"\n"];
for (NSUInteger i = 0, count = 0; i < lines.count && count < 3; i++) {
NSString *line = lines[i];
NSRange range = [line rangeOfString:#"MP3:"];
if (range.location == 0 && range.length == #"MP3:".length) {
// parsing
count++;
}
}
NSString * stringToParse = #"MP3: hola MP3: pfff MP3: cosas MP3: hello";
NSArray * arrayStringParsed = [stringToParse componentsSeparatedByString:#"MP3:"];
And the results is and array:
<__NSArrayM 0x15fb2f80>(
,
hola ,
pfff ,
cosas ,
hello
)
First element doesn't have anything but the other 4 are parsed and you can work with them.
I have a NSString containing a unicode character bigger than U+FFFF, like the MUSICAL SYMBOL G CLEF symbol '𝄞'. I can create the NSString and display it.
NSString *s = #"A\U0001d11eB"; // "A𝄞B"
NSLog(#"String = \"%#\"", s);
The log is correct and displays the 3 characters. This tells me the NSString is well done and there is no encoding problem.
String = "A𝄞B"
But when I try to loop through all characters using the method
- (unichar)characterAtIndex:(NSUInteger)index
everything goes wrong.
The type unichar is 16 bits so I expect to get the wrong character for the musical symbol. But the length of the string is also incorrect!
NSLog(#"Length = %d", [s length]);
for (int i=0; i<[s length]; i++)
{
NSLog(#" Character %d = %c", i, [s characterAtIndex:i]);
}
displays
Length = 4
Character 0 = A
Character 1 = 4
Character 2 = .
Character 3 = B
What methods should I use to correctly parse my NSString and get my 3 unicode characters?
Ideally the right method should return a type like wchar_t in place of unichar.
Thank you
NSString *s = #"A\U0001d11eB";
NSData *data = [s dataUsingEncoding:NSUTF32LittleEndianStringEncoding];
const wchar_t *wcs = [data bytes];
for (int i = 0; i < [data length]/4; i++) {
NSLog(#"%#010x", wcs[i]);
}
Output:
0x00000041
0x0001d11e
0x00000042
(The code assumes that wchar_t has a size of 4 bytes and little-endian encoding.)
length and charAtIndex: do not give the expected result because \U0001d11e
is internally stored as UTF-16 "surrogate pair".
Another useful method for general Unicode strings is
[s enumerateSubstringsInRange:NSMakeRange(0, [s length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
NSLog(#"%#", substring);
}];
Output:
A
𝄞
B
I'd like to have a function that removes a random set of characters from a string and replaces them with '_'. eg. to create a fill in the blanks type of situation. The way I have it now works, but its not smart. Also I don't want to replace spaces with blanks (as you can see in the while loop). Any suggestions on a more efficient way to do this?
blankItem = #"Remove Some Characters";
for(int j=0;j<totalRemove;j++)
{
replaceLocation=arc4random() % blankItem.length;
while ([blankItem characterAtIndex:replaceLocation] == '_' || [blankItem characterAtIndex:replaceLocation] == ' ') {
replaceLocation=arc4random() % blankItem.length;
}
blankItem= [blankItem stringByReplacingCharactersInRange:NSMakeRange(replaceLocation, 1) withString:#"_"];
}
My issue is with the for and while loops in terms of efficiency. But, maybe efficiency isn't of the essence in something this small?
If the number of characters to remove/replace is small compared to the length of the
string, then your solution is good, because the probability of a "collision" in the
while-loop is small. You can improve the method by using a single mutable string instead of
allocating a new string in each step:
NSString *string = #"Remove Some Characters";
int totalRemove = 5;
NSMutableString *result = [string mutableCopy];
for (int j=0; j < totalRemove; j++) {
int replaceLocation;
do {
replaceLocation = arc4random_uniform((int)[result length]);
} while ([result characterAtIndex:replaceLocation] == '_' || [result characterAtIndex:replaceLocation] == ' ');
[result replaceCharactersInRange:NSMakeRange(replaceLocation, 1) withString:#"_"];
}
If the number of characters to remove/replace is about the same magnitude as the
length of the string, then a different algorithm might be better.
The following code uses the ideas from Unique random numbers in an integer array in the C programming language to replace characters
at random positions with a single loop over all characters of the string.
An additional (first) pass is necessary because of your requirement that space characters
are not replaced.
NSString *string = #"Remove Some Characters";
int totalRemove = 5;
// First pass: Determine number of non-space characters:
__block int count = 0;
[string enumerateSubstringsInRange:NSMakeRange(0, [string length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (![substring isEqualToString:#" "]) {
count++;
}
}];
// Second pass: Replace characters at random positions:
__block int c = count; // Number of remaining non-space characters
__block int r = totalRemove; // Number of remaining characters to replace
NSMutableString *result = [string mutableCopy];
[result enumerateSubstringsInRange:NSMakeRange(0, [result length])
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
if (![substring isEqualToString:#" "]) {
// Replace this character with probability r/c:
if (arc4random_uniform(c) < r) {
[result replaceCharactersInRange:substringRange withString:#"_"];
r--;
if (r == 0) *stop = YES; // Stop enumeration, nothing more to do.
}
c--;
}
}];
Another advantage of this solution is that it handles surrogate pairs (e.g. Emojis) and composed character sequences correctly, even if these are stores as two separate characters in the string.
What is the smallest code I can use to count the number of occurrences of the newline character in a file with objective-c / cocoa touch?
Thanks!
Both of the other answers are correct, but with the caveat that they require loading the entire file into memory to work.
The way around that is to load the file incrementally using an NSFileHandle. Something like this:
NSFileHandle * file = [NSFileHandle fileHandleForReadingAtPath:pathToFile];
NSUInteger chunkSize = 1024;
NSData * chunk = [file readDataOfLength:chunkSize];
NSUInteger numberOfNewlines = 0;
while ([chunk length] > 0) {
const unichar * bytes = (const unichar *)[chunk bytes];
for (int index = 0; index < [chunk length]; ++index) {
unichar character = (unichar)bytes[index];
if ([[NSCharacterSet newlineCharacterSet] characterIsMember:character]) {
numberOfNewlines++;
}
}
chunk = [file readDataOfLength:chunkSize];
}
This should get you going:
NSString *fileContents = [NSString stringWithContentsOfFile:file encoding:encoding error:&error];
NSUInteger newlineCount = [fileContents numberOfOccurrencesOfString:#"\n"];
#interface NSString ()
- (NSUInteger)numberOfOccurrencesOfString:(NSString *)aString;
- (NSUInteger)numberOfOccurrencesOfChar:(char)aChar;
#end
#implementation NSString ()
- (NSUInteger)numberOfOccurrencesOfString:(NSString *)aString {
NSRange range = [self rangeOfString:aString];
NSUInteger length = [self length];
NSUInteger count = 0;
while (range.location != NSNotFound) {
range = [self rangeOfString:aString options:0 range:NSMakeRange(range.location + range.length, length - range.location - range.length)];
count++;
}
return count;
}
- (NSUInteger)numberOfOccurrencesOfChar:(char)aChar {
const char *cString = [self cStringUsingEncoding:NSUTF8StringEncoding];
NSUInteger stringLength = strlen(cString);
NSUInteger count = 0;
for (int i = 0; i < stringLength; i++) {
if (cString[i] == aChar) {
count++;
}
}
return count;
}
#end
While "numberOfOccurrencesOfString:" allocates no additional memory and supports string needles,
"numberOfOccurrencesOfChar:" allocates an autoreleased c-string copy of the NSString and searches for a single char. ""
As you were asking for a count of newlines (hence single chars) I figured a quick benchmark might be good for this particular purpose:
So I took a test string of length 2486813 containing total of 78312 '\n'. (I basically took a variation of OSX's words file) and…
…ran [testString numberOfOccurrencesOfString:#"\n"] 100 times: 19.35s
…ran [testString numberOfOccurrencesOfChar:'\n'] 100 times: 6.91s
(Setup: 2.2GHz Core 2 Duo MacBook Pro, run on a single thread)
[Edit: small bug fixed; made second snippet into category string method.]
You can scan through the string using SubstringWithRange:
Count up the number of times \n appears.
smallest you say? That automatically turns this question into code golf
FILE*f=fopen(path,"r");
int i,c;
while(1+c)i+=(c=fgetc(f))==10;
printf("%i",i);
(please don't ever actually use this code)
If you want to stay within Cocoa/CocoaTouch, you can use NSRegularExpression for this:
NSString *theString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:#"\\n" options:NSRegularExpressionCaseInsensitive error:&error];
NSUInteger numLines = [regex numberOfMatchesInString:theString options:0 range:NSMakeRange(0, [theString length])] + 1;