Custom string sorting in Swift - ios

I have an array that I want sorted alphabetically (for the most part). For example I want an array of string to be sorted A-Z with the exception of elements starting with "g", I want elements starting with "g" to be last (or first if that's easier) in the array.
Example:
let list = ["apple", "car", "boat", "zebra", "ghost", "far"]
sorted should be:
["apple", "boat", "car", "far", "zebra", "ghost"]
How would one accomplish this?

You could use sorted(by:) and compare cases that start with "g" and then fallback to normal String comparison if that doesn't happen:
let sorted = list.sorted { a, b in
if a.first == "g" && b.first != "g" { return false }
if b.first == "g" && a.first != "g" { return true }
return a < b
}

I would split it into 2 arrays, sort each of them, then combine them again.
let list = ["apple", "car", "boat", "zebra", "ghost", "far"]
let listWithoutG = list.filter { !$0.hasPrefix("g") }
let listOnlyG = list.filter { $0.hasPrefix("g") }
let sorted = listWithoutG.sorted() + listOnlyG.sorted()
print("Sorted: \(sorted)")
Result:
Sorted: ["apple", "boat", "car", "far", "zebra", "ghost"]

Related

Swift Printing an Array in a table form String and also adding special characters

Lets assume i have this string
var stringCSV = "Beth,Charles,Danielle,Adam,Eric\n17945,10091,10088,3907,10132\n2,12,13,48,11";
And i converted it into a 2D Array
[["Beth", "Charles", "Danielle", "Adam", "Eric"], ["17945", "10091", "10088", "3907", "10132"], ["2", "12", "13", "48", "11"]]
Below is the code i used to convert the String into a 2D Array and sort it.
struct Person {
let name: String
let id: String
let age: String
}
var csvFormatted = [[String]]()
stringCSV.enumerateLines { (line, _) in
var res = line.split(separator: ",",omittingEmptySubsequences: false).map{
String($0)
}
for i in 0 ..< res.count {
res[i] = res[i]
}
csvFormatted.append(res)
}
let properties = zip(csvFormatted[1],csvFormatted[2])
let namesAndProperties = zip(csvFormatted[0],properties)
let structArray = namesAndProperties.map { (name, properties) in
return Person(name: name, id: properties.0, age: properties.1)
}
let sortedArray = structArray.sorted {
return $0.name < $1.name
}
for i in sortedArray {
print(i.name, i.id, i.age)
}
i get the below output
Adam 3907 48
Beth 17945 2
Charles 10091 12
Danielle 10088 13
Eric 10132 11
But I wont to understand and know how i can print the sorted array back to string, just like it was before i splitted it into an array and sorted it and including the special characters like "\n" and ",".
and achieve something the below
Adam,Beth,Charles,Danielle,Eric\n
3907,17945,10091,10088,10132\n
48,2,12,13,11
Use map for each property and join it to create a comma separated row. Then using the results in an array join again with a new line character.
let csv = [array.map(\.name).joined(separator: ","),
array.map(\.id).joined(separator: ","),
array.map(\.age).joined(separator: ",")]
.joined(separator: "\n")

Swift How can I sort a list of clothing sizes (e.g. XL, S, 2XL, XXS)?

I've an array of dictionary with something like this:
[["XL":956], ["M":1010], ["S":998], ["L":955], ["XXL":921], ["XS":1041], ["30":45], ["28":41], ["32":46], ["26":35], ["34":50], ["One Size":1]]
How do I sort it so that it is in this order?
[["XS":1041], ["S":998], ["M":1010], ["L":955], ["XL":956], ["XXL":921], ["26":35], ["28":41], ["30":45], ["32":46], ["34":50], ["One Size":1]]
Note that every size is not always present, it's dynamic
If you're confident about knowing all of your size strings, and so happy to force unwrap, you can do it in a one-liner.
let order = ["XS", "S", "M", "L", "XL", "XXL", "26", "28", "30", "32", "34", "One Size"]
let sorted = sizes.sorted{order.firstIndex(of: $0.first!.key)! < order.firstIndex(of: $1.first!.key)!}
Depending on where you are going to use this, the better option would be to unwrap the optionals safely in case of bad data! :-)
Create a reference array that contains the alphabetical sizes in sorted order.
Now, sort the array based on reference array, i.e.
let data = [["XL":956], ["M":1010], ["S":998], ["L":955], ["XXL":921], ["XS":1041], ["30":45], ["28":41], ["32":46], ["26":35], ["34":50], ["One Size":1]]
let ref = ["XS", "S", "M", "L", "XL", "XXL"]
let result = data.sorted { (val1, val2) -> Bool in
if let key1 = val1.first?.key, let key2 = val2.first?.key {
if let x = Int(key1), let y = Int(key2) {
return x < y
} else if let x = ref.firstIndex(of: key1), let y = ref.firstIndex(of: key2) {
return x < y
} else {
return ref.firstIndex(of: key1) != nil
}
}
return false
}

How can I change the order of two arrays when one array is sorted? [duplicate]

This question already has answers here:
In Swift how can I sort one array based on another array?
(4 answers)
Closed 4 years ago.
If I have:
var arrayOne = ["dog", "cat", "hamster", "horse"]​
and
var arrayTwo = [3, 2, 4, 1]
How can I assign 3 to dog, 2 to cat, 4 to hamster, and 1 to horse so that if I sort arrayTwo from biggest integer to smallest, it will automatically do that for arrayOne too. In result it would print out:
var arrayOne = ["hamster", "dog", "cat", "horse"]
var arrayTwo = [4, 3, 2, 1]
What code is easiest and simplest for this?
Thanks in Advance! :)
It's quite hard to "bind" the two variables together. You could do something like this:
let dict = [3: "dog", 2: "cat", 4: "hamster", 1: "horse"]
var arrayTwo = [3, 2, 4, 1] {
willSet {
// you should probably check whether arrayTwo still has the same elements here
arrayOne = newValue.map { dict[$0]! }
}
}
It is easier to zip the arrays and then sort by arrayTwo:
let result = zip(arrayOne, arrayTwo).sorted(by: { $0.1 > $1.1 })
Now, result.map { $0.0 } is your sorted array 1 and result.map { $0.1 } is your sorted array 2.

How to add element at Last index Swift

I am getting an Array from server and I store it in NSMutableArray. Now the issue is that the Array is not sorted. For eg. array = ["A","B","None","C","D"]. I want to sort it and place the "None" element at last. i.e ["A","B","C","D","None"]. Tried swapping but was unable to match the condition, as the array may increase in future. Check my code below which is not working as expected.
if array.containsObject( "None" ){
print("\(array.indexOfObject("None"))")
let noneIndex = array.indexOfObject("None")
print(noneIndex)
array.removeObject(noneIndex)
print("Remove Array:-\(array)")
array.insertObject(noneIndex, atIndex: (array.lastObject?.index)!)
print("Sorted Array:-\(array)")
}
Maybe I'm misunderstanding what it is that you need to do, but you could use sorted() on your array if you just want to sort it alphabetically.
You could also use filter to remove "None" from your array, sort it, and then append "None" as the last element
For instance, if you have
let elements = ["Alpha", "Bold", "None", "charlie", "Delta", "echo", "zebra", "k"]
You could start out by filtering it:
let filteredElements = elements.filter { $0.uppercased() != "NONE"}
Sort the filtered elements:
var sortedElements = filteredElements.sorted { $0.uppercased() < $1.uppercased()}
Append "None"
sortedElements.append("None") // ["Alpha", "Bold", "charlie", "Delta", "echo", "k", "zebra", "None"]
And be done.
Here it is combined:
let lastElement = "None"
let elements = ["Alpha", "Bold", "None", "charlie", "Delta", "echo", "zebra", "k"]
var sortedElements = elements.filter({$0.uppercased() != lastElement.uppercased()}).sorted(by: {$0.uppercased() < $1.uppercased()})
sortedElements.append(lastElement)
Hope that helps you.
var array = ["A", "B", "None", "C", "D"]
if let noneIndex = array.index(of: "None") {
array.remove(at: noneIndex)
array.append("None")
}
print(array)
This should move None at the end of the array, and sort the other elements:
let ["A", "B", "None", "C", "D"]
array.sorted { $1 == "None" || $0 < $1 } // ["A", "B", "C", "D", "None"]
This simply takes benefits of the by argument that can be passed to the sort/sorted method from Array.
Edit #MartinR had a very strong point regarding the comparison predicate from this answer, which indeed doesn't offer a strong weak ordering. Sorting the array with a correct predicate would be along the lines of:
array.sorted { $0 == "None" ? false : $1 == "None" ? true : $0 < $1 }
This will work:
// starting test array
let array = ["B", "None", "C","P","None","A", "Q"]
var sorted = array.sorted { (str1, str2) -> Bool in
return str1 < str2
}
sorted.forEach { str in
if str == "None" {
if let idx = sorted.index(of: str) {
sorted.remove(at: idx)
sorted.append(str)
}
}
}
// Sorted array is now ["A", "B", "C", "P", "Q", "None", "None"]

An Array of Doubles from Two Arrays of Strings

Say I have two arrays:
let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi"]
and I have this for loop that gets the percent similarity of the doubles:
var matches = 0
for (index, item) in enumerate(arrayOne) {
if item == arrayTwo[index] {
matches++
}
}
However, what if those arrays are longer and I want data points of those similarities instead of one single calculation. What kind of a function could I write where it takes the first 5 elements off the array, returns their similarity with the for loop, and moves on to the next 5? ie. it would be a function that takes two string arrays and returns an array of doubles. I am sure this is a simple question but I do not know how to approach taking 5 array elements at a time and then returning an array of doubles (like data points the the string array similarities).
I’m not clear quite what you’re asking, however, you might find playing around with zip, map, and reduce helpful.
For example, you could rewrite your original loop like this (assuming Swift 2.0, you’d have to rearrange slightly for 1.2):
zip(arrayOne, arrayTwo).reduce(0) { $0 + ($1.0 == $1.1 ? 1 : 0) }
// returns 4
zip creates a new sequence of the pairs of elements at each corresponding position.
reduce takes a starting value, then keeps a running value by applying a function to the current value and the next value in the sequence – bearing in mind this is a sequence of pair elements, you want to add 1 when they are the same, 0 when they aren’t. This then gives you a count of the positions where both match.
If instead you wanted an array, with true representing a match at that point, and false if different, you could use map:
zip(arrayOne, arrayTwo).map(==)
// returns [true, false, false, false, false, true]
If on the other hand you wanted a list of the differences in string length between the two strings at each position, you could change it to:
zip(arrayOne, arrayTwo).map { (a,b) in
a.characters.count - b.characters.count
}
// returns [0, -3, 2, -2, 3, 0]
As some have suggested, Set might help, e.g.:
let commonElements = Set(arrayOne).intersect(arrayTwo)
// returns strings present in both e.g. {"Hi", "Hello”}
This is a good approach if you are OK treating your data as a set i.e. order doesn’t matter and duplicates can be ignored. If order and dupes do matter, you probably have to stick with arrays.
Take a look at this. I think it does what you are asking. By introducing a count variable to keep track of the number of items you have processed, you will know when to update your counts array:
var matches = 0
let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi", "a", "b", "c"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi", "a", "B", "C"]
var count = 0
var counts:[Int] = []
for (index, item) in enumerate(arrayOne) {
if item == arrayTwo[index] {
matches++
}
// Have we done 5? If so, time to update counts array
if ++count == 5 {
counts.append(matches)
count = 0
matches = 0
}
}
// If we didn't have 5, just append the matches for the remaining items
if count > 0 {
counts.append(matches)
}
println(counts) // prints "[1, 2]"
Right - so I think I understand what you're looking for: you want to have a matches function like the one you've written that works for chunks of a certain number of elements. First off, you're going to need a chunk function. There's a good discussion of them here, but they're for arrays, and you're going to want to zip your two arrays together here, so you'll need one for SequenceType. This works:
public extension SequenceType {
/// Returns an array of arrays of n non-overlapping elements of self
/// - Parameter n: The size of the chunk
/// ```swift
/// [1, 2, 3, 4, 5].chunk(2)
///
/// [[1, 2], [3, 4], [5]]
/// ```
func chunk(_ n: Int) -> [[Generator.Element]] {
var g = self.generate()
var ret: [[Generator.Element]] = [[]]
while let next = g.next() {
if ret.last!.count < n {
ret[ret.endIndex.predecessor()].append(next)
} else {
ret.append([next])
}
}
return ret
}
}
Then, you need a function that counts the matches in two arrays. You could inline it with a closure, or you could define it separately, it doesn't make much of a difference.
func matchesEvery<
S0 : SequenceType,
S1 : SequenceType,
T : Equatable where
S0.Generator.Element == T,
S1.Generator.Element == T
>(_ n: Int, s0: S0, s1: S1) -> [Int] {
return zip(s0, s1)
.chunk(5)
.map { $0.reduce(0) { $1.0 == $1.1 ? $0 + 1 : $0 } }
}
That will return:
let arrayOne = ["Hi", "Hi", "Hello", "Not Hey", "Howdy", "Hi"]
let arrayTwo = ["Hi", "Hello", "Hey", "Not Howdy", "Hi", "Hi"]
matchesEvery(5, s0: arrayOne, s1: arrayTwo) // [1, 1]
Since in the first five there's one match, and in the last there is one as well.

Resources