I have a sorted array of times like so
[0.0, 1.2, 4.3, 5.9, 7.2, 8.0]
While an audio file plays, I want to be able to take the current time and find what the nearest, lower number is in the array.
My approach would be to traverse the array, possible in reverse order as it feels like it should be faster. Is there a better way?
The playback SHOULD be linear, but might be fast-forwarded/rewound, so I would like to come up with a solution that takes that into account, but I'm not really sure how else to approach the problem.
The method you are looking for is -[NSArray indexOfObject:inSortedRange:options:usingComparator:]. It performs a binary search. With the options:NSBinarySearchingInsertionIndex option, if the value isn't found exactly, it returns the index where the object would be inserted, which is the index of the least larger element, or the count of items in the array.
NSTimeInterval currentTime = ...;
NSUInteger index = [times indexOfObject:#(currentTime)
inSortedRange:NSMakeRange(0, times.count)
options:NSBinarySearchingInsertionIndex
usingComparator:^(id object0, id object1) {
NSTimeInterval time0 = [object0 doubleValue];
NSTimeInterval time1 = [object1 doubleValue];
if (time0 < time1) return NSOrderedAscending;
else if (time0 > time1) return NSOrderedDescending;
else return NSOrderedSame;
}];
// If currentTime was not found exactly, then index is the next larger element
// or array count..
if (index == times.count || [times[index] doubleValue] > currentTime) {
--index;
}
The fastest* way to find something in a sorted array is binary search: if there are n items, check the element at index n/2. If that element is greater than what you're looking for, check the element at index n/4; otherwise, if it's less than what you're looking for, check the element at index 3n/4. Continue subdividing in this fashion until you've found what you want, i.e. the position where the current time should be. Then you can pick the preceding element, as that's the closest element that's less than the current time.
However, once you've done that once, you can keep track of where you are in the list. As the user plays through the file, keep checking to see if the time has passed the next element and so on. In other words, remember where you were, and use that when you check again. If the user rewinds, check the preceding elements.
*Arguably, this isn't strictly true -- there are surely faster ways if you can make a good guess as to the probable location of the element in question. But if you don't know anything other than that the element appears somewhere in the array, it's usually the right approach.
I'm not sure if it's the best approach, but I think it'll get the job done (assuming your array is always ascending order).
- (NSNumber *) incrementalClosestLowestNumberForNumber:(NSNumber *)aNumber inArray:(NSArray *)array {
for (int i = 0; i < array.count; i++) {
if ([array[i] floatValue] == [aNumber floatValue]) {
return aNumber;
}
else if ([array[i] floatValue] > [aNumber floatValue]) {
int index = (i > 0) ? i - 1 : 0;
return array[index];
}
}
return #0;
}
Then call it like this:
NSArray * numbArray = #[#0.0, #1.2, #4.3, #5.9, #7.2, #8.0];
NSNumber * closestNumber = [self closestLowestNumberForNumber:#2.4 inArray:numbArray];
NSLog(#"closest number: %#", closestNumber);
I'm not sure if someone else knows a special way that is much faster.
Based on some of the other answers / comments, I came up with this, perhaps one of them can point out if a whole is somewhere.
- (NSNumber *) quartalClosestLowestNumberForNumber:(NSNumber *)compareNumber inArray:(NSArray *)array {
int low = 0;
int high = array.count - 1;
NSNumber * lastNumber;
int currentIndex = 0;
for (currentIndex = low + (high - low) / 2; low <= high; currentIndex = low + (high - low) / 2) {
NSNumber * numb = array[currentIndex];
if (numb.floatValue < compareNumber.floatValue) {
low = currentIndex + 1;
}
else if (numb.floatValue > compareNumber.floatValue) {
high = currentIndex - 1;
}
else if (numb.floatValue == compareNumber.floatValue) {
return numb;
}
lastNumber = numb;
}
if (lastNumber.floatValue > compareNumber.floatValue && currentIndex != 0) {
lastNumber = array[currentIndex - 1];
}
return lastNumber;
}
I'm really bored right now, so I'm trying to test the fastest method. Here's how I did it.
NSMutableArray * numbersArray = [NSMutableArray new];
for (int i = 0; i < 100000; i++) {
float floater = i / 100.0;
[numbersArray addObject: #(floater)];
}
// courtesy #RobMayoff
NSDate * binaryDate = [NSDate date];
NSNumber * closestNumberBinary = [self binaryClosestLowestNumberForNumber:#4.4 inArray:numbersArray];
NSLog(#"Found closest number binary: %# in: %f seconds", closestNumberBinary, -[binaryDate timeIntervalSinceNow]);
// The Quartal Version
NSDate * quartalDate = [NSDate date];
NSNumber * closestNumberQuartal = [self quartalClosestLowestNumberForNumber:#4.4 inArray:numbersArray];
NSLog(#"Found closest number quartal: %# in: %f seconds", closestNumberQuartal, -[quartalDate timeIntervalSinceNow]);
// The incremental version
NSDate * incrementalDate = [NSDate date];
NSNumber * closestNumberIncremental = [self incrementalClosestLowestNumberForNumber:#4.4 inArray:numbersArray];
NSLog(#"Found closest number incremental: %# in: %f seconds", closestNumberIncremental, -[incrementalDate timeIntervalSinceNow]);
And here's the output:
Found closest number binary: 4.4 in: 0.000030 seconds
Found closest number quartal: 4.4 in: 0.000015 seconds
Found closest number incremental: 4.4 in: 0.000092 seconds
And another test case:
Found closest number binary: 751.48 in: 0.000030 seconds
Found closest number quartal: 751.48 in: 0.000016 seconds
Found closest number incremental: 751.48 in: 0.013042 seconds
Related
I am making a game that requires me to use very large numbers. I believe I am able to store very large numbers with NSDecimal. However, when displaying the numbers to users I would like to be able to convert the large number to a succinct string that uses characters to signify the value eg. 100,000 -> 100k 1,000,000 -> 1.00M 4,200,000,000 -> 4.20B and so forth going up to extremely large numbers. Is there any built in method for doing so or would I have to use a bunch of
NSDecimalCompare statements to determine the size of the number and convert?
I am hoping to use objective c for the application.
I know that I can use NSString *string = NSDecimalString(&NSDecimal, _usLocale); to convert to a string could I then do some type of comparison on this string to get the result I'm looking for?
Use this method to convert your number into a smaller format just as you need:
-(NSString*) suffixNumber:(NSNumber*)number
{
if (!number)
return #"";
long long num = [number longLongValue];
int s = ( (num < 0) ? -1 : (num > 0) ? 1 : 0 );
NSString* sign = (s == -1 ? #"-" : #"" );
num = llabs(num);
if (num < 1000)
return [NSString stringWithFormat:#"%#%lld",sign,num];
int exp = (int) (log(num) / 3.f); //log(1000));
NSArray* units = #[#"K",#"M",#"G",#"T",#"P",#"E"];
return [NSString stringWithFormat:#"%#%.1f%#",sign, (num / pow(1000, exp)), [units objectAtIndex:(exp-1)]];
}
Some sample examples:
NSLog(#"%#",[self suffixNumber:#99999]); // 100.0K
NSLog(#"%#",[self suffixNumber:#5109999]); // 5.1M
Source
Solved my issue: Can only be used if you know that your NSDecimal that you are trying to format will only be a whole number without decimals so make sure you round when doing any math on the NSDecimals.
-(NSString *)returnFormattedString:(NSDecimal)nsDecimalToFormat{
NSMutableArray *formatArray = [NSMutableArray arrayWithObjects:#"%.2f",#"%.1f",#"%.0f",nil];
NSMutableArray *suffixes = [NSMutableArray arrayWithObjects:#"k",#"M",#"B",#"T",#"Qa",#"Qi",#"Sx",#"Sp",#"Oc",#"No",#"De",#"Ud",#"Dud",#"Tde",#"Qde",#"Qid",#"Sxd",#"Spd",#"Ocd",#"Nvd",#"Vi",#"Uvi",#"Dvi",#"Tvi", nil];
int dick = [suffixes count];
NSLog(#"count %i",dick);
NSString *string = NSDecimalString(&nsDecimalToFormat, _usLocale);
NSString *formatedString;
NSUInteger characterCount = [string length];
if (characterCount > 3) {
NSString *trimmedString=[string substringToIndex:3];
float a;
a = 100.00/(pow(10, (characterCount - 4)%3));
int remainder = (characterCount-4)%3;
int suffixIndex = (characterCount + 3 - 1)/3 - 2;
NSLog(#"%i",suffixIndex);
if(suffixIndex < [suffixes count]){
NSString *formatSpecifier = [formatArray[remainder] stringByAppendingString:suffixes[suffixIndex]];
formatedString= [NSString stringWithFormat:formatSpecifier, [trimmedString floatValue] / a];
}
else {
formatedString = #"too Big";
}
}
else{
formatedString = string;
}
return formatedString;
}
I am trying to return the lowest number in an array.
Parameter: arrayOfNumbers - An array of NSNumbers.
Return: The lowest number in the array as an NSInteger.
The code I have thus far doesn't give me any errors, but does not pass the unit tests. What am I doing wrong?
- (NSInteger) lowestNumberInArray:(NSArray *)arrayOfNumbers {
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
for (NSInteger i = 0; i < arrayOfNumbers.count; i++) {
if (arrayOfNumbers[i] < smallest) {
smallest = arrayOfNumbers[i];
}
}
NSInteger smallestValue = [smallest integerValue];
return smallestValue;
}
This is the unit test:
- (void) testThatLowestNumberIsReturned {
NSInteger lowestNumber = [self.handler lowestNumberInArray:#[#3, #8, #-4, #0]];
XCTAssertEqual(lowestNumber, -4, #"Lowest number should be -4.");
lowestNumber = [self.handler lowestNumberInArray:#[#83, #124, #422, #953, #1004, #9532, #-1000]];
XCTAssertEqual(lowestNumber, -1000, #"Lowest number should be -1000.");
}
This method
NSNumber* smallest = [arrayOfNumbers valueForKeyPath:#"#min.self"];
will already determine the smallest number in the array, so the loop inside the method is superfluous (on top of being plain wrong, as #vikingosegundo notices).
you are comparing objects with c types, resulting im pointer addresses being compared with an int.
Beside the fact your smallest is already the smallest, as you used the KVC collection operator #min.self (see Glorfindel answer), the following code shows you correct comparison
if (arrayOfNumbers[i] < smallest)
should be
if ([arrayOfNumbers[i] compare:smallest] == NSOrderingAscending)
or
if ([arrayOfNumbers[i] integerValue] < [smallest integerValue])
Consider this code:
NSNumber* interchangeId = dict[#"interchangeMarkerLogId"];
long long llValue = [interchangeId longLongValue];
double dValue = [interchangeId doubleValue];
NSNumber* doubleId = [NSNumber numberWithDouble:dValue];
long long llDouble = [doubleId longLongValue];
if (llValue > 1000000) {
NSLog(#"Have Marker iD = %#, interchangeId = %#, long long value = %lld, doubleNumber = %#, doubleAsLL = %lld, CType = %s, longlong = %s", self.iD, interchangeId, llValue, doubleId, llDouble, [interchangeId objCType], #encode(long long));
}
The results:
Have Marker iD = (null), interchangeId = 635168520811866143,
long long value = 635168520811866143, doubleNumber = 6.351685208118661e+17,
doubleAsLL = 635168520811866112, CType = d, longlong = q
dict is coming from NSJSONSerialization, and the original JSON source data is "interchangeId":635168520811866143. It appears that all 18 digits of the value have been captured in the NSNumber, so it could not possibly have been accumulated by NSJSONSerialization as a double (which is limited to 16 decimal digits). Yet, objCType is reporting that it's a double.
We find this in the documentation for NSNumber: "The returned type does not necessarily match the method the receiver was created with." So apparently this is a "feechure" (i.e., documented bug).
So how can I determine that this value originated as an integer and not a floating point value, so I can extract it correctly, with all the available precision? (Keep in mind that I have some other values that are legitimately floating-point, and I need to extract those accurately as well.)
I've come up with two solutions so far:
The first, which does not make use of knowledge of NSDecimalNumber --
NSString* numberString = [obj stringValue];
BOOL fixed = YES;
for (int i = 0; i < numberString.length; i++) {
unichar theChar = [numberString characterAtIndex:i];
if (theChar != '-' && (theChar < '0' || theChar > '9')) {
fixed = NO;
break;
}
}
The second, which assumes that we only need worry about NSDecimalNumber objects, and can trust the CType results from regular NSNumbers --
if ([obj isKindOfClass:[NSDecimalNumber class]]) {
// Need to determine if integer or floating-point. NSDecimalNumber is a subclass of NSNumber, but it always reports it's type as double.
NSDecimal decimalStruct = [obj decimalValue];
// The decimal value is usually "compact", so may have a positive exponent even if integer (due to trailing zeros). "Length" is expressed in terms of 4-digit halfwords.
if (decimalStruct._exponent >= 0 && decimalStruct._exponent + 4 * decimalStruct._length < 20) {
sqlite3_bind_int64(pStmt, idx, [obj longLongValue]);
}
else {
sqlite3_bind_double(pStmt, idx, [obj doubleValue]);
}
}
else ... handle regular NSNumber by testing CType.
The second should be more efficient, especially since it does not need to create a new object, but is slightly worrisome in that it depends on "undocumented behavior/interface" of NSDecimal -- the meanings of the fields are not documented anywhere (that I can find) and are said to be "private".
Both appear to work.
Though on thinking about it a bit -- The second approach has some "boundary" problems, since one can't readily adjust the limits to assure that the maximum possible 64-bit binary int will "pass" without risking loss of a slightly larger number.
Rather unbelievably, this scheme fails in some cases:
BOOL fixed = NO;
long long llValue = [obj longLongValue];
NSNumber* testNumber = [[NSNumber alloc] initWithLongLong:llValue];
if ([testNumber isEqualToNumber:obj]) {
fixed = YES;
}
I didn't save the value, but there is one for which the NSNumber will essentially be unequal to itself -- the values both display the same but do not register as equal (and it is certain that the value originated as an integer).
This appears to work, so far:
BOOL fixed = NO;
if ([obj isKindOfClass:[NSNumber class]]) {
long long llValue = [obj longLongValue];
NSNumber* testNumber = [[[obj class] alloc] initWithLongLong:llValue];
if ([testNumber isEqualToNumber:obj]) {
fixed = YES;
}
}
Apparently isEqualToNumber does not work reliably between an NSNumber and an NSDecimalNumber.
(But the bounty is still open, for the best suggestion or improvement.)
As documented in NSDecimalNumber.h, NSDecimalNumber always returns "d" for it's return type. This is expected behavior.
- (const char *)objCType NS_RETURNS_INNER_POINTER;
// return 'd' for double
And also in the Developer Docs:
Returns a C string containing the Objective-C type of the data contained in the
receiver, which for an NSDecimalNumber object is always ādā (for double).
CFNumberGetValue is documented to return false if the conversion was lossy. In the event of a lossy conversion, or when you encounter an NSDecimalNumber, you will want to fall back to using the stringValue and then use sqlite3_bind_text to bind it (and use sqlite's column affinity).
Something like this:
NSNumber *number = ...
BOOL ok = NO;
if (![number isKindOfClass:[NSDecimalNumber class]]) {
CFNumberType numberType = CFNumberGetType(number);
if (numberType == kCFNumberFloat32Type ||
numberType == kCFNumberFloat64Type ||
numberType == kCFNumberCGFloatType)
{
double value;
ok = CFNumberGetValue(number, kCFNumberFloat64Type, &value);
if (ok) {
ok = (sqlite3_bind_double(pStmt, idx, value) == SQLITE_OK);
}
} else {
SInt64 value;
ok = CFNumberGetValue(number, kCFNumberSInt64Type, &value);
if (ok) {
ok = (sqlite3_bind_int64(pStmt, idx, value) == SQLITE_OK);
}
}
}
// We had an NSDecimalNumber, or the conversion via CFNumberGetValue() was lossy.
if (!ok) {
NSString *stringValue = [number stringValue];
ok = (sqlite3_bind_text(pStmt, idx, [stringValue UTF8String], -1, SQLITE_TRANSIENT) == SQLITE_OK);
}
Simple answer: You can't.
In order to do what you're asking, you'll need to keep track of the exact type on your own. NSNumber is more of a "dumb" wrapper in that it helps you use standard numbers in a more objective way (as Obj-C objects). Using solely NSNumber, -objCType is your only way. If you want another way, you'd have to do it on your own.
Here are some other discussions that may be of help:
get type of NSNumber
What's the largest value an NSNumber can store?
Why is longLongValue returning the incorrect value
NSJSONSerialization unboxes NSNumber?
NSJSONSerializer returns:
an integer NSNumber for integers up to 18 digits
an NSDecimalNumber for integers with 19 or more digits
a double NSNumber for numbers with decimals or exponent
a BOOL NSNumber for true and false.
Compare directly with the global variables kCFBooleanFalse and kCFBooleanTrue (spelling might be wrong) to find booleans. Check isKindOfClass:[NSDecimalNumber class] for decimal numbers; these are actually integers. Test
strcmp (number.objCType, #encode (double)) == 0
for double NSNumbers. This will unfortunately match NSDecimalNumber as well, so test that first.
Ok--It's not 100% ideal, but you add a little bit of code to SBJSON to achieve what you want.
1. First, add NSNumber+SBJson to the SBJSON project:
NSNumber+SBJson.h
#interface NSNumber (SBJson)
#property ( nonatomic ) BOOL isDouble ;
#end
NSNumber+SBJson.m
#import "NSNumber+SBJSON.h"
#import <objc/runtime.h>
#implementation NSNumber (SBJson)
static const char * kIsDoubleKey = "kIsDoubleKey" ;
-(void)setIsDouble:(BOOL)b
{
objc_setAssociatedObject( self, kIsDoubleKey, [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ;
}
-(BOOL)isDouble
{
return [ objc_getAssociatedObject( self, kIsDoubleKey ) boolValue ] ;
}
#end
2. Now, find the line in SBJson4StreamParser.m where sbjson4_token_real is handled. Change the code as follows:
case sbjson4_token_real: {
NSNumber * number = #(strtod(token, NULL)) ;
number.isDouble = YES ;
[_delegate parserFoundNumber:number ];
[_state parser:self shouldTransitionTo:tok];
break;
}
note the bold line... this will mark a number created from a JSON real as a double.
3. Finally, you can check the isDouble property on your number objects decoded via SBJSON
HTH
edit:
(Of course you could generalize this and replace the added isDouble with a generic type indicator if you like)
if ([data isKindOfClass: [NSNumber class]]) {
NSNumber *num = (NSNumber *)data;
if (strcmp([data objCType], #encode(float)) == 0) {
return [NSString stringWithFormat:#"%0.1f} ",num.floatValue];
} else if (strcmp([data objCType], #encode(double)) == 0) {
return [NSString stringWithFormat:#"%0.1f} ",num.doubleValue];
} else if (strcmp([data objCType], #encode(int)) == 0) {
return [NSString stringWithFormat:#"%d} ",num.intValue];
} else if (strcmp([data objCType], #encode(BOOL)) == 0) {
return num.boolValue ? #"Yes} " : #"No} ";
} else if (strcmp([data objCType], #encode(long)) == 0) {
return [NSString stringWithFormat:#"%ld} ",num.longValue];
}
}
How would I get the nearest float in my array to a float of my choice? Here is my array:
[1.20, 1.50, 1.75, 1.95, 2.10]
For example, if my float was 1.60, I would like to produce the float 1.50.
Any ideas? Thanks in advance!
You can do it by sorting the array and finding the nearest one.
For this you can use sortDescriptors and then your algorithm will go.
Even you can loop through, by assuming first as the required value and store the minimum absolute (abs()) difference, if next difference is lesser than hold that value.
The working sample, however you need to handle other conditions like two similar values or your value is just between two value like 2 lies between 1 and 3.
NSArray *array = #[#1.20, #1.50, #1.75, #1.95, #2.10];
double my = 1.7;
NSNumber *nearest = array[0];
double diff = fabs(my - [array[0] doubleValue]);
for (NSNumber *num in array) {
double d = [num doubleValue];
if (diff > fabs(my - d) ) {
nearest = num;
diff = my - d;
}
}
NSLog(#"%#", nearest);
I have an NSArray that contains multiple objects which in itself have date ranges NSDate *start and NSDate *end.
What I want to do is to iterate through this array to find the shortest range (between Start and End), based on the current date. Something like this:
Date range Start 1 >----------< Date range End 1
Date range Start 2 >-< Date range End 2
|
Current date
In the example above I would like to get the object that contains Date range start 2 and Date range End 2.
Any ideas and suggestions on how to achieve this?
Update
With based on current date I mean that the current date should be somewhere inside the range. I don't want a range that has an end date that is before the current date, nor a range that has a start date that is in the future.
There are a couple options, depending on how in depth you want the results to be. The first (and easiest) way would be to sort the array by smallest date range:
array = array1 sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
double a = [obj1.endDate timeIntervalSinceDate:obj1.startDate];
double b = [obj2.endDate timeIntervalSinceDate:obj2.startDate];
if ( a < b ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( a > b ) {
return (NSComparisonResult)NSOrderedDescending;
} else {
return (NSComparisonResult)NSOrderedSame;
}
}
Then when you need to check for the shortest one containing today's date you can just start at the beginning of the array and check if the startdate is before now and the enddate is after now. The first object that matches that criteria is your smallest range around today.
NSDate *date = [NSDate date];
Object *foundObject;
for(Object *obj in array)
{
if([obj.startDate timeIntervalSinceDate:date] <= 0 && [obj.endDate timeIntervalSinceDate:date] >= 0)
{
foundObject = obj;
break;
}
}
If you want to go more in depth you can use a predicate to get a filtered array of all objects that surround today's date. then you can get the shortest range and every other range that includes the date you are looking for.
NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
if([evaluatedObject.startDate timeIntervalSinceDate:date] <= 0 && [evaluatedObject.endDate timeIntervalSinceDate:date] >= 0)
{
return true;
}
return false;
}];
NSArray *array = [array1 filteredArrayUsingPredicate:pred];
I think, this should be a good start point, but I can't provide working code since there is no clear definition, so:
// Result with minimum range
// Assuming that DateRangeObject has start and end properties of NSDate
DateRangeObject *result = nil;
// Array containing DateRangeObject objects
NSArray *arr /* = TODO: provide the content for an array */;
if (arr.count) { /* only if the array contains something */
// Initial result
result = arr[0];
double minTS = DBL_MAX; // max double value, not zero ;) lol
for (DateRangeObject *dro in arr) {
// Get the timestamps for start and end;
double tss = [dro.start timeIntervalSince1970];
double tse = [dro.end timeIntervalSince1970];
// Difference between end and start (positive value)
double diff = tse - tsa;
if (minTS < diff) {
minTS = diff;
// Current minimum
result = dro;
}
}
}