Sort negative number string in iOS with NSStringCompareOptions - ios

I am trying to sort some list of strings. And names of elements could be almost anything, from real names, strings, dates,... and numbers.
I found NSStringCompareOptions with NSNumericSearch, wich work fast enough and it work nice so :
[1,2,21,3,55,6] --> [1,2,3,6,21,55]
But my problems are negative numbers
[1,2,3,-1,-4,-5] --> [-1,-4,-5,1,2,3]
What it is not right.
I know that Apple stays :
Numeric comparison only applies to the numerals in the string,
not other characters that would have meaning in a numeric representation
such as a negative sign, a comma, or a decimal point.
But my question is how to achieve this, because I know I am not only who do this.
EDIT :
Thanks to Narendra Pandey, but my real case is a little bit complicated, so his answer can't be used here.
So let say I have some dictionary with numbers as keys and strings as values :
dic = {#1:#"123", #2:#"-123", #5:"MyName",...};
then I have array of object with ids.
array = #[{object with id 5}, {object with id 2},...];
and I need sorted array of object by name of properties.
NSStringCompareOptions comparisonOption = NSCaseInsensitiveSearch | NSNumericSearch;
array = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString * name1 = [dic objectForKey:obj1.someId];
NSString * name2 = [dic objectForKey:obj2.someId];
return [name1 compare:name2 options:comparisonOption];;
}];
EDIT 2:
Maybe I should state that I have solution, but it is 4 times slower that sorting with NSStringCompareOptions
// CHECK IF IT IS NUMBER
NSNumber * number1 = [numberFormatter numberFromString:string1];
NSNumber * number2 = [numberFormatter numberFromString:string2];
//
// NSLog(#"NUMBERS : %#, %#", number1, number2);
if (number1 && number2) {
return [number1 compare:number2];
}
return [string1 compare:string2 options:comparisonOption];

NSArray * numbers = #[#1, #2, #3, #-4, #-5, #6, #9];
NSPredicate * predicateForPositive = [NSPredicate predicateWithFormat:#"integerValue >= 0"];
NSArray * positiveNumbers = [numbers filteredArrayUsingPredicate:predicateForPositive];
NSPredicate * predicateForNegative = [NSPredicate predicateWithFormat:#"integerValue <= 0"];
NSArray * NegativeNumber = [numbers filteredArrayUsingPredicate:predicateForNegative];
NSLog(#"Negative number %#",NegativeNumber);
NSLog(#"Positive number %#",positiveNumbers);
Output:
Negative number (
"-4",
"-5"
)
Positive number (
1,
2,
3,
6,
9
)
Now sort both and concate both Array.

Thanks to Narendra Pandey I found solution for me.
Let me state first that Narendra Pandey solution works, but it is slower, even 3 times.
(in my case 0.014s with NSStringCompareOptions, and 0,042 Narendra Pandey solution).
But if I use his idea and change it a little bit :
array = [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
NSString * name1 = somehow get string1;
NSString * name2 = somehow get string2;
if ([string1 integerValue] <0 && [string2 integerValue]<0) {
return - [string1 compare:string2 options:comparisonOption];
}
return [string1 compare:string2 options:comparisonOption];
}];
this method is faster, in my case 0,015s which is comparable with NSStringCompareOptions.
In this way you avoid to go through whole array at beginning to separate negative and positive numbers, and then sort them.

This function get any array and convert it to sorted Double array
var numberArray: [Any] = [4, 3.9, -23,3, 7.6, -51, 75.3, "0", "-(22)"]
/// This function get any array and convert it to sorted _Double_ array
/// - Note: Non-numerical elements are automatically removed from the array
/// - Parameters:
/// - numbers: array of anything
/// - Returns: sorted _Double_ array
func sortAnyNumbers(_ numbers: [Any]) -> [Double] {
var _numbers: [Double] = []
numbers.forEach { number in
// Delete non-numeric characters
if let numb = Double("\(number)".components(separatedBy: CharacterSet(charactersIn: "-01234567890.").inverted).joined()) {
_numbers.append(numb)
}
}
return _numbers.sorted()
}
print(sortAnyNumbers(numberArray)) //[-51.0, -23.0, -22.0, 0.0, 3.0, 3.9, 4.0, 7.6, 75.3]

Related

Sort NSArray based on a list of values? [duplicate]

This question already has answers here:
Sort NSArray of custom objects based on sorting of another NSArray of strings
(5 answers)
Closed 5 years ago.
How to sort an NSArray based on a list of values ? For example:
An object of the NSArray has a property :
NSString* language; //- may contain value #"FR", #"NL", #"SP", #"EN", and others languages
How can we sort based on the list of values like:
NSArray* sortingByList = #[#"FR", #"NL", #"SP", #"EN"];
The expected results should look like this:
- FR
- FR
- NL
- NL
- NL
- SP
- SP
- EN
- EN
- EN
- other languages
...
- (NSArray*) sortArray:(NSArray *)language withOrder:(NSArray *)sortingByList
{
return [language sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSUInteger pos1 = [sortingByList indexOfObject:obj1];
NSUInteger pos2 = [sortingByList indexOfObject:obj2];
if (pos1 < pos2)
return NSOrderedAscending;
else if (pos1 == pos2)
return NSOrderedSame;
else
return NSOrderedDescending;
}];
}
The best answer to the question "how do I sort based on some particular interesting order" is invariably to use one of the methods which takes a block. Some sorts can be achieved using NSPredicate-based sorts, but block-based are more flexible and often easier.
The block these methods take will be passed two elements and must return a value based on the ordering of those two elements. For example using NSArray's sortedArrayUsingComparator:
NSArray *sorted
= [someArray
sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull x, id _Nonnull y)
{
// return NSOrderedAscending,
// NSOrderedSame or
// NSOrderedDescending based
// on the order of x and y
}
];
In your case you have an array, sortingByList, which gives the desired ordering of the elements. In outline an algorithm is:
Get languageX and languageY from x and y respectively
Get the indexes (using a standard NSArray method), indexX and indexY, of languageX and languageY in sortingByList. If either don't occur in the array set the index to one greater than the maximum index.
Compare indexX and indexY and return their ordering as the result.
If step (3) ranks x and y as equal you might want to do a secondary sort based on some other property of the objects. A benefit of using block-based sorting is you can easily sort based on any number of keys.
If you get stuck implementing a block that orders your objects correctly ask a new question, showing your code, and explaining where you got stuck. Someone will undoubtedly help you again at the point.
HTH
Use below code to sort array which contains string elements.
unsortedStrings is NSArray which contains unsorted string elements.
NSArray *sortedStrings =
[unsortedStrings sortedArrayUsingSelector:#selector(compare:)];
NSArray *array = #[#"FR", #"NL", #"SP",#"NL", #"EN",#"FR"];
NSArray *sortedArray = [array sortedArrayUsingSelector:
#selector(localizedCaseInsensitiveCompare:)];
NSLog(#"sortedArray is --%#",sortedArray);
Out put is
SortedArray is --(
EN,
FR,
FR,
NL,
NL,
SP
)

Retrieve value from the Object

I have the following object which contains an array of NSDictionary. I wonder how could I able to get the sum of Quantity from this object.
In Objective-C there is a very convenient way using KVC Collection Operators:
NSNumber *sum = [sharedData.orderItems valueForKeyPath:#"#sum.Quantity.integerValue"];
NSInteger integerSum = sum.integerValue;
The other simple operators are:
#count - count of objects
#avg - average value
#max - maximum value
#min - minimum value
int sum=0;
for(NSDictionary *item in sharedData.orderitems){
sum = sum + [[item objectForKey:#"Quantity"] intValue];
}
NSLog(#"Sum = %d",sum);

Find nearest float in array

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);

NSScanner to scan until a character or number is Hit

My problem is that I want to extract the data from the string. The strings prints 1000 lines with some random data fetched from web. The string is like this:
Level
1
2
3
4
5
6
7
8
Score
0
0
0
0
0
0
0
0
I need NSScanner to save data in array like this
int *extractedLevelData = {1, 2, 3, 4, 5, 6, 7, 8}
int *extractedScoreData = {0, 0, 0, 0, 0, 0, 0, 0}
The problem is that the row of number in level and score is dynamic, it goes from 1 then new line then 2 then new line then 3 then new line and so on. The challenge is that sometimes it can be 1 to 5 or sometimes it can be 1 only and sometimes maximum is 1 to 8. They show up in the same style as shown above followed by new line character. Same with the "Score".
I've tried this but the saved data returns null in NSLog, it has been 7 days since I'm learning Objective C and I'm almost finished with the app until this problem came.
Here is what I've tried:
NSString *extractedData;
NSCharacterSet *tripleNewLine = [NSCharacterSet characterSetWithCharactersInString:#"\n\n\n"];
[firstScanner scanString:#"Level" intoString:NULL];
[firstScanner setScanLocation:[firstScanner scanLocation]];
[firstScanner scanUpToCharactersFromSet:tripleNewLine intoString:&extractedData];
NSLog(#"%#", extractedData);
Note that this is just a code snippet and the real problem is really complex but if someone smart enough to solve this problem then my problem will be solved! The logic can be: tell the NSScanner to scan from "Level" text with numbers until it hit any character.
So if you don't really want to use NSScanner another solution can be the following:
Split the whole string by Score this will give you two strings one with the values from score and one with the values from level:
`NSArray *components = [serverString componentsSeparatedByString:#"Score"];`
The above line will split your server string in two strings contained by components array. One will have all the values from Level including Level, and the other one will have the values from Score without Score value.
Now you can split those two strings by \n character, this will result in two arrays with all the values from a row.
if(components.count > 1) { //we better check if we have both substrings
NSArray *levels = [[components objectAtIndex:0] componentsSeparatedByString:#"\n"];
NSArray *scores = [[components objectAtIndex:1] componentsSeparatedByString:#"\n"];
}
Now after you have the all the values we should create a method that will check if a value is an actual number, if not we will remove it from the array.
-(NSArray *)checkAndRemoveNonNumbersFromArray:(NSArray *)checkedArray {
NSCharacterSet *alphaNums = [NSCharacterSet decimalDigitCharacterSet];
checkedArray = [checkedArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *evaluatedObject, NSDictionary *bindings) {
NSCharacterSet *inStringSet = [NSCharacterSet characterSetWithCharactersInString:evaluatedObject];
return [alphaNums isSupersetOfSet:inStringSet];
}]];
return checkedArray;
}
Now that we have the method created, the code from point 2 will become:
if(components.count > 1) { //we better check if we have both substrings
NSArray *levels = [[components objectAtIndex:0] componentsSeparatedByString:#"\n"];
NSArray *scores = [[components objectAtIndex:1] componentsSeparatedByString:#"\n"];
levels = [self checkAndRemoveNonNumbersFromArray:levels];
scores = [self checkAndRemoveNonNumbersFromArray:scores];
}
Now levels & scores will have only numerical values from those two arrays. Note, this is not the best solution, is more a solution to show you how you can play with strings, after you understand this implementation I think you can find one that uses NSScaner.
I see that you are passing #"Level" as a string input rather than a variable, which is not correct. I assume Level is the NSString object in this case. You can use this solution. This will print all the data from Level
NSScanner *scanner = [NSScanner scannerWithString:Level];
while([scanner isAtEnd] == NO) {
[scanner setScanLocation:[scanner scanLocation]];
[scanner scanUpToCharactersFromSet:tripleNewLine intoString:&extractedData];
NSLog(#"%#", extractedData);
}

Objective-c: Most efficient way to get the shortest range from a collection (NSArray) with ranges

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;
}
}
}

Resources