This question already has answers here:
Sorting array alphabetically with number
(8 answers)
Closed 5 years ago.
I have an array of strings,
let array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
I would like to get the output sorted in ascending as,
let sorted = [ "1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA" ]
I tried using the sorted command but it does not work when it encounters more than 2 digits e.g.: 100, 101, 200 etc.
array.sorted { $0? < $1? }
What would be the simple way to get this?
Xcode 11.3 • Swift 5.2 or later
You can use String method localizedStandardCompare (diacritics and case insensitive):
let array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
let sorted = array.sorted {$0.localizedStandardCompare($1) == .orderedAscending}
print(sorted) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
or using the method sort(by:) on a MutableCollection:
var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.sort {$0.localizedStandardCompare($1) == .orderedAscending}
print(array) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
You can also implement your own localized standard sort method extending Collection:
public extension Sequence where Element: StringProtocol {
func localizedStandardSorted(ascending: Bool = true) -> [Element] {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sorted { $0.localizedStandardCompare($1) == result }
}
}
let array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
let sorted = array.localizedStandardSorted()
print(sorted) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
The mutating method as well extending MutableCollection:
public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
mutating func localizedStandardSort(ascending: Bool = true) {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sort { $0.localizedStandardCompare($1) == result }
}
}
var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.localizedStandardSort()
print(array) // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
If you need to sort your array numerically you can use String compare method setting the options parameter to .numeric:
public extension Sequence where Element: StringProtocol {
func sortedNumerically(ascending: Bool = true) -> [Element] {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sorted { $0.compare($1, options: .numeric) == result }
}
}
public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
mutating func sortNumerically(ascending: Bool = true) {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sort { $0.compare($1, options: .numeric) == result }
}
}
var numbers = ["1.5","0.5","1"]
let sortedNumbers = numbers.sortedNumerically()
print(sortedNumbers) // ["0.5", "1", "1.5"]
print(numbers) // ["1.5","0.5","1"]
// mutating the original collection
numbers.sortNumerically(ascending: false)
print(numbers) // "["1.5", "1", "0.5"]\n"
To sort a custom class/structure by one of its properties:
extension MutableCollection where Self: RandomAccessCollection {
public mutating func localizedStandardSort<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) {
sort {
predicate($0).localizedStandardCompare(predicate($1)) ==
(ascending ? .orderedAscending : .orderedDescending)
}
}
}
public extension Sequence {
func localizedStandardSorted<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
sorted {
predicate($0).localizedStandardCompare(predicate($1)) ==
(ascending ? .orderedAscending : .orderedDescending)
}
}
}
public extension Sequence {
func sortedNumerically<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) -> [Element] {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sorted { predicate($0).compare(predicate($1), options: .numeric) == result }
}
}
public extension MutableCollection where Element: StringProtocol, Self: RandomAccessCollection {
mutating func sortNumerically<T: StringProtocol>(_ predicate: (Element) -> T, ascending: Bool = true) {
let result: ComparisonResult = ascending ? .orderedAscending : .orderedDescending
return sort { predicate($0).compare(predicate($1), options: .numeric) == result }
}
}
Playground testing
struct Person {
let name: String
let age : Int
}
extension Person : CustomStringConvertible {
var description: String { "name: \(name), age: \(age)" }
}
var people: [Person] = [.init(name: "Éd Sheeran", age: 26),
.init(name: "phil Collins", age: 66),
.init(name: "Shakira", age: 40),
.init(name: "rihanna", age: 25),
.init(name: "Bono", age: 57)]
let sorted = people.localizedStandardSorted(\.name)
print(sorted) // [name: Bono, age: 57, name: Éd Sheeran, age: 26, name: phil Collins, age: 66, name: rihanna, age: 25, name: Shakira, age: 40]
edit/update: Xcode 12 • Swift 5.5 or later
You can use KeyPathComparator and pass localizedStandard as the Comparator:
people.sort(using: KeyPathComparator(\.name, comparator: .localizedStandard))
print(people) // [name: Bono, age: 57, name: Éd Sheeran, age: 26, name: phil Collins, age: 66, name: rihanna, age: 25, name: Shakira, age: 40]
people.sort(using: KeyPathComparator(\.name, comparator: .localizedStandard, order: .reverse))
print(people) // "[name: Shakira, age: 40, name: rihanna, age: 25, name: phil Collins, age: 66, name: Éd Sheeran, age: 26, name: Bono, age: 57]"
For sorting just an array of strings you can also use KeyPathComparator and pass self for the KeyPath:
var array = [ "10", "1", "101", "NA", "100", "20", "210", "200", "NA", "7" ]
array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard))
array // ["1", "7", "10", "20", "100", "101", "200", "210", "NA", "NA"]
array.sort(using: KeyPathComparator(\.self, comparator: .localizedStandard, order: .reverse))
array // ["NA", "NA", "210", "200", "101", "100", "20", "10", "7", "1"]
Related
MongoDB collection data with multiple arrays:
{
"_id": ObjectId("61aa6bf1742b00f59b894eb7"),
"first": ["abc", "def", "ghi"],
"last": ["rst", "uvw", "xyz"],
"numb": ["12", "34", "56"]
}
Expected output where the data in the arrays should be in this format:
{
"first": "abc",
"last": "rst",
"numb": "12"
},
{
"first": "def",
"last": "uvw",
"numb": "34"
},
{
"first": "ghi",
"last": "xyz",
"numb": "56"
}
You can make use of $zip to "transpose" multiple arrays (as many as you'd like actually):
// {
// first: ["abc", "def", "ghi"],
// last: ["rst", "uvw", "xyz"],
// numb: ["12", "34", "56"]
// }
db.collection.aggregate([
{ $project: { x: { $zip: { inputs: ["$first", "$last", "$numb"] } } } },
// { x: [["abc", "rst", "12"], ["def", "uvw", "34"], ["ghi", "xyz", "56" ]] }
{ $unwind: "$x" },
// { x: [ "abc", "rst", "12" ] }
// { x: [ "def", "uvw", "34" ] }
// { x: [ "ghi", "xyz", "56" ] }
{ $replaceWith: {
$arrayToObject: { $zip: { inputs: [["first", "last", "numb"], "$x"] } }
}}
])
// { first: "abc", last: "rst", numb: "12" }
// { first: "def", last: "uvw", numb: "34" }
// { first: "ghi", last: "xyz", numb: "56" }
This:
zips the 3 arrays such that elements at the same index will get grouped into the same sub-array.
$unwinds (explodes/flattens) those sub-arrays.
transforms the resulting arrays into objects to fit your expected output format:
by $zipping (again!) the keys we want to associate with the array's values (the keys: ["first", "last", "numb"] and the values: "$x")
and $replaceWith the current document with the result of the $zip.
Note that prior to Mongo 4.2, you can use $replaceRoot instead of $replaceWith.
Query
map on indexes to combine the same index members to 1 document
keeps the _id also to know from which document those came from
and the index to sort after
for each index take the element from each array
unwind
sort by _id and index to get the results sorted like it was in the arrays
*indexes are computed using the biggest array, to be safe, in case you already know that all are the same size, you can replace the :
{"$max": [{"$size": "$first"}, {"$size": "$last"}, {"$size": "$numb"}]}
with the size of any array for example(we need the biggest to work):
{"$size": "$first"}
Test code here
aggregate(
[{"$project":
{"data":
{"$map":
{"input":
{"$range":
[0,
{"$max":
[{"$size": "$first"}, {"$size": "$last"}, {"$size": "$numb"}]}]},
"in":
{"_id": "$_id",
"index": "$$this",
"first": {"$arrayElemAt": ["$first", "$$this"]},
"last": {"$arrayElemAt": ["$last", "$$this"]},
"numb": {"$arrayElemAt": ["$numb", "$$this"]}}}}}},
{"$unwind": {"path": "$data"}},
{"$replaceRoot": {"newRoot": "$data"}},
{"$sort": {"_id": 1, "index": 1}},
{"$unset": ["index"]}])
I have a sorted array
let things = [
Thing(activity: "1", name: "value1"),
Thing(activity: "1", name: "value2"),
Thing(activity: "1", name: "value3"),
Thing(activity: "2", name: "value4"),
Thing(activity: "2", name: "value5"),
Thing(activity: "3", name: "value6"),
Thing(activity: "3", name: "value7"),
Thing(activity: "1", name: "value8"),
Thing(activity: "1", name: "value9"),
Thing(activity: "1", name: "value10")
]
I would like to produce array of arrays splitted when the activity value changes similar to the following
[[Thing(activity: "1", name: "value1"),
Thing(activity: "1", name: "value2"),
Thing(activity: "1", name: "value3")],
[Thing(activity: "2", name: "value4"),
Thing(activity: "2", name: "value5")],
[Thing(activity: "3", name: "value6"),
Thing(activity: "3", name: "value7")],
[Thing(activity: "1", name: "value8"),
Thing(activity: "1", name: "value9"),
Thing(activity: "1", name: "value10")]]
A generalized solution would be:
extension Sequence {
func grouped<T: Equatable>(by block: (Element) throws -> T) rethrows -> [[Element]] {
return try reduce(into: []) { result, element in
if let lastElement = result.last?.last, try block(lastElement) == block(element) {
result[result.index(before: result.endIndex)].append(element)
} else {
result.append([element])
}
}
}
}
Then you can do:
let results = things.grouped { $0.activity }
A less elegant (but slightly more efficient) solution would be:
extension Sequence {
func grouped<T: Equatable>(by block: (Element) throws -> T) rethrows -> [[Element]] {
var results: [[Element]] = []
var lastValue: T?
var index = results.endIndex
for element in self {
let value = try block(element)
if let lastValue = lastValue, lastValue == value {
results[index].append(element)
} else {
results.append([element])
index = results.index(before: results.endIndex)
lastValue = value
}
}
return results
}
}
As already mentioned by #matt in comments you can use collection method reduce(into:) to group your elements by checking if the activity of the last element of the last array is equal to the current element activity, if so just append a new element to the last array, otherwise append a new array with a single element to the outer array:
struct Thing {
let activity, name: String
}
let things: [Thing] = [
.init(activity: "1", name: "value1"),
.init(activity: "1", name: "value2"),
.init(activity: "1", name: "value3"),
.init(activity: "2", name: "value4"),
.init(activity: "2", name: "value5"),
.init(activity: "3", name: "value6"),
.init(activity: "3", name: "value7"),
.init(activity: "1", name: "value8"),
.init(activity: "1", name: "value9"),
.init(activity: "1", name: "value10")]
let grouped: [[Thing]] = things.reduce(into: []) {
$0.last?.last?.activity == $1.activity ?
$0[$0.index(before: $0.endIndex)].append($1) :
$0.append([$1])
}
print(grouped) // "[[__lldb_expr_1.Thing(activity: "1", name: "value1"), __lldb_expr_1.Thing(activity: "1", name: "value2"), __lldb_expr_1.Thing(activity: "1", name: "value3")], [__lldb_expr_1.Thing(activity: "2", name: "value4"), __lldb_expr_1.Thing(activity: "2", name: "value5")], [__lldb_expr_1.Thing(activity: "3", name: "value6"), __lldb_expr_1.Thing(activity: "3", name: "value7")], [__lldb_expr_1.Thing(activity: "1", name: "value8"), __lldb_expr_1.Thing(activity: "1", name: "value9"), __lldb_expr_1.Thing(activity: "1", name: "value10")]]\n"
var myArray: [[String]] =
[
["1", "picture1.png", "John", "Smith"],
["2", "picture2.png", "Mike", "Rutherford"],
]
How to sort myArray on first item ? second item ? ascending ? descending ?
Many Thanks
I would suggest you create a struct or class to package this related data together:
struct Person {
let id: Int
let picture: String // This should probably be a URL, NSImage or UIImage
let firstName: String
let lastName: String
}
And then define your instances with the correct types (e.g. the id is an Int, not a String representation of an Int.
let people = [
Person(
id: 1,
picture: "picture1.png",
firstName: "John",
lastName: "Smith"
),
Person(
id: 2,
picture: "picture2.png",
firstName: "Mike",
lastName: "Rutherford"
),
]
From there, you can sort it any which way you please:
people.sorted{ $0.id < $1.id }
people.sorted{ $0.id > $1.id }
people.sorted{ $0.picture < $1.picture }
people.sorted{ $0.picture > $1.picture }
people.sorted{ $0.firstName < $1.firstName }
people.sorted{ $0.firstName > $1.firstName }
people.sorted{ $0.lastName < $1.lastName }
people.sorted{ $0.lastName > $1.lastName }
Notice, that index ranged is not checked, what could lead to a fatal error at runtime. Check Alexanders comment! :)
var myArray: [[String]] =
[
["1", "picture1.png", "John", "Smith"],
["2", "picture2.png", "Mike", "Rutherford"],
]
func sort<T: Comparable>(ArrayOfArrays: [[T]], sortingIndex: Int, sortFunc: (T, T) -> Bool)) -> [[T]] {
return ArrayOfArrays.sorted {sortFunc($0[sortingIndex], $1[sortingIndex])}
}
}
print(sort(ArrayOfArrays: myArray, sortingIndex: 0, sortFunc: <))
//[["1", "picture1.png", "John", "Smith"], ["2", "picture2.png", "Mike", "Rutherford"]]
print(sort(ArrayOfArrays: myArray, sortingIndex: 0, sortFunc: >))
//[["2", "picture2.png", "Mike", "Rutherford"], ["1", "picture1.png", "John", "Smith"]]
Swift's Array has a built-in sort function. Just call it.
myArray[0].sort { $0.compare($1, options: .numeric) == .orderedAscending }
myArray[1].sort { $0.compare($1, options: .numeric) == .orderedDescending }
To sort the arrays using numeric string comparison (e.g. where "2" < "10") of the item at a particular index:
let index = 1 // sort by second entry
myArray.sort { $0[index].compare($1[index], options: .numeric) == .orderedAscending }
If you don't need numeric comparisons (e.g. where "10" < "2"):
myArray.sort { $0[index] < $1[index] }
As others point out, though, you really should be using arrays of custom struct or class rather than representing your objects as mere array of strings.
I have a function that returns an array of objects data.sportdata. I would like to get all array elements with the same sports_id. The code
$scope.arrSportData = data.sportdata;
angular.forEach($scope.arrSportData, function(value, key) {
console.log($scope.arrSportData);
//getting reponse
/*
Object { id: "1", user_id: "2", sport_id: "1", position_id: "1", team_name: "JimmyTmname",}
Object { id: "2", user_id: "2", sport_id: "2", position_id: "6", team_name: "JimmyTmname2",}
Object { id: "3", user_id: "2", sport_id: "3", position_id: "12", team_name: "JimmyTmname3",}
Object { id: "4", user_id: "2", sport_id: "5", position_id: "20", team_name: "JimmyTmname5",}
*/
//code i wrote
if (value.sport_id == 1) {
$scope.positionId.spr1 = value.position_id;
$scope.teamname.spr1 = value.team_name;
}
if (value.sport_id == 2) {
$scope.positionId.spr2 = value.position_id;
$scope.teamname.spr2 = value.team_name;
}
if (value.sport_id == 3) {
$scope.positionId.spr3 = value.position_id;
$scope.teamname.spr3 = value.team_name;
}
if (value.sport_id == 4) {
$scope.positionId.spr4 = value.position_id;
$scope.teamname.spr4 = value.team_name;
}
});
Here I am always getting first value and nothing more. Please suggest and help to solve this problem.
I did try outside of the loop but does not work. I think filter function can do this but dont know how does it work.
Perform a groupBy function which will give you an object like this
{
key: [],
key: []
}
Here the key will be sport_id and [] will be the items with the same key.
A minimal working example will be,
// Written By: Ceaser Bautista
//Link: http://stackoverflow.com/a/34890276/17447
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
//here we have four teams with two sports id 13 and 22
var arrSportData = [{
id: "1",
user_id: "1",
sport_id: "13",
position_id: "1",
team_name: "JimmyTmname"
}, {
id: "2",
user_id: "2",
sport_id: "22",
position_id: "6",
team_name: "JimmyTmname2"
}, {
id: "3",
user_id: "3",
sport_id: "22",
position_id: "12",
team_name: "JimmyTmname2",
}, {
id: "4",
user_id: "4",
sport_id: "13",
position_id: "20",
team_name: "JimmyTmname1"
}];
$scope.groupedData = groupBy(arrSportData, "sport_id");
console.log(groupedData);
Now you will have an array for each sports_id. In view populate it like
<div ng-repeat="(key, items) in groupedData">
<h4>Sports ID: {{key}}</h4>
<ul ng-repeat="item in items">
<li>User ID: {{item.user_id}}</li>
</ul>
</div>
I want to save the subcategory data in this form [[value1,value2,...],[value1, value2],[value1],.....] in the following code
var subcategories = [SubCategory]()
for (_, item) in json {
//do something with item.id, item.name
for (_, subcategory) in item["subcategory"] {
let subcategory = SubCategory(
id: Int(subcategory ["id"].stringValue),
name: subcategory ["name"].string,
desc: subcategory ["desc"].string,
image: subcategory ["image"].string,
coupon: Int(subcategory ["coupon"].stringValue),
icon: subcategory ["icon"].string,
order: Int(subcategory ["order"].stringValue),
aname: subcategory ["aname"].string,
options: Int(subcategory ["options"].stringValue),
items: subcategory ["items"].arrayObject
)
subcategories.append(subcategory)
}
print(subcategories.count)
for sub in subcategories {
print(sub.name)
print(sub.id)
print(sub.desc)
self.SUBCATNAME.append(sub.name!)
self.SUBARRAY?.append(self.SUBCATNAME)
}
print(self.SUBCATNAME)
print(self.SUBARRAY)
}
I need to append all the subcategory name in an array of dictionary like the above structure. Here i created SUBARRAY as var SUBARRAY:[[String]] = []. but the value is coming as nil. Here i am getting the json data from Api using Swiftyjson. How to implement this??
my sample json data is as below:
like wise so many subcategories are there
[
{
"id": "244",
"name": "PIZZAS",
"subcategory": [
{
"id": "515",
"name": "MARGARITA",
"description": "Cheese and Tomato",
"image": "",
"icon": "",
"coupon": "1",
"order": "1",
"aname": "",
"options": "2"
},
{
"id": "516",
"name": "ABC",
"description": "HNDDSGHFD",
"image": "",
"icon": "",
"coupon": "1",
"order": "1",
"aname": "",
"options": "2",
},
{
"id": "516",
"name": "DEF",
"description": "HFDGFJJFHFKH",
"image": "",
"icon": "",
"coupon": "1",
"order": "1",
"aname": "",
"options": "2",
}
]
},
{
......
}
]
if you try this code
for (NSDictionary *dictionary in ArrCategory)
{
NSMutableArray *sub_cat_data=[dictionary objectForKey:#"sub_cat_data"];
for (NSDictionary *sub_cat_dict in sub_cat_data)
{
NSMutableArray *sub_cat_data=[sub_cat_dict objectForKey:#"sub_data"];
for (NSDictionary *dictionary1 in sub_cat_data)
{
NSMutableDictionary *Dic_Sub_cat=[[NSMutableDictionary alloc]init];
NSString *str_dic_id=[dictionary1 objectForKey:#"sub_id"];
NSString *str_dic_name=[dictionary1 objectForKey:#"sub_name"];
[Dic_Sub_cat setObject:str_dic_id forKey:#"sub_id"];
[Dic_Sub_cat setObject:str_dic_name forKey:#"sub_name"];
[Dic_Sub_cat setObject:#"0" forKey:#"sub_Chekid"];
[New_ArrCategory addObject:Dic_Sub_cat];
}
}
}
swift example
if let results: NSMutableArray = jsonResult["data"] as? NSMutableArray
{
var ArrMoodtype2 : NSMutableArray!
ArrMoodtype2 = NSMutableArray()
ArrMoodtype2 = results
ArrFeel = NSMutableArray()
for var i = 0; i < ArrMoodtype2.count; i++
{
var Dic : NSMutableDictionary!
Dic = NSMutableDictionary()
let tempDic: NSMutableDictionary = ArrMoodtype2[i] as! NSMutableDictionary
let strMoodCatID = tempDic ["mood_cat_id"] as! String
let strMoodID = tempDic ["mood_id"] as! String
let strMoodImage = tempDic ["mood_image"] as! String
let strMoodName = tempDic ["mood_name"] as! String
Dic.setObject(strMoodCatID, forKey: "mood_cat_id")
Dic.setObject(strMoodID, forKey: "mood_id")
Dic.setObject(strMoodImage, forKey: "mood_image")
Dic.setObject(strMoodName, forKey: "mood_name")
ArrFeel.addObject(Dic)
}
}