I was previously sorting a unique id that consisted of two letters followed by three numbers. This unique id is used to group the entries into a sectioned tableview.
For example [un101, un098, un100, un099, un999] using the below code was working perfectly.
self.figuresByLetter = Dictionary(grouping: self.figures, by: { String($0.number[2])}).sorted(by: {$0.0 < $1.0})
And resulted in [(key: "0", value: un098, un099), (key: "1", value: un100, un101), (key: "9", value: un999)]
When the number of entries surpassed 1,000, I added a leading zero to the previous entries. I tried updating the code to:
self.figuresByLetter = Dictionary(grouping: self.figures, by: { String($0.number[3])}).sorted(by: {$0.0 < $1.0})
Now the result is [(key: "0", value: un0098, un0099, un1000, un1001), (key: "1", value: un0100, un0101), (key: "9", value: un0999)]
What can I do so that the entries are in the correct order?
UPDATE:
I figured out an easier way to accomplish exactly what I was looking for.
self.figuresByLetter = Dictionary(grouping: self.figures, by: { String($0.number.prefix(4))}).sorted(by: {$0.0 < $1.0})
If "the two letters are the same for every entry" and if there are always four digits, then just sort:
let input = ["un0098", "un0099", "un1000", "un1001", "un1002", "un0100", "un0101"]
let output = input.sorted()
output // ["un0098", "un0099", "un0100", "un0101", "un1000", "un1001", "un1002"]
As you can see, that's the right answer.
Based on that, now you can proceed to group into a dictionary and sort by key, like this:
let input = ["un0098", "un0099", "un1000", "un1001", "un1002", "un0100", "un0101"]
let output = input.sorted()
let d = Dictionary(grouping: output, by: { s -> String in
let ix = s.index(s.startIndex, offsetBy:2)
let ix2 = s.index(ix, offsetBy:2)
return String(s[ix..<ix2])
})
let model = d.sorted{$0.key < $1.key}
Now model is suitable as a datasource for your table view:
[(key: "00", value: ["un0098", "un0099"]),
(key: "01", value: ["un0100", "un0101"]),
(key: "10", value: ["un1000", "un1001", "un1002"])]
If that isn't quite what you wanted, it should be easy to see how to tweak it. The structure and order are correct, so if the data isn't quite the desired format, you can now change it. For example, if you don't like the leading zero in the key names, you can easily get rid of it.
Related
Im trying to sort the columns of a CSV file,the contents of the CSV is provided in string
Beth,Charles,Danielle,Adam,Eric\n
17945,10091,10088,3907,10132\n
2,12,13,48,11
Converted String to 2D Array
[["Beth", "Charles", "Danielle", "Adam", "Eric"], ["17945", "10091", "10088", "3907",
"10132"], ["2", "12", "13", "48", "11"]]
How can i sort the only the first dimension of the 2D array or the Names in the 2D Array and still keep the mappings of the other dimension, i don't know how to explain this properly, but i hope the details below will help you understand what i want to achieve.
Adam,Beth,Charles,Danielle,Eric\n
3907,17945,10091,10088,10132\n
48,2,12,13,11
I want to achieve this with the names sorted and the other values in the other arrays mapping to the names like below,
[["Adam", "Beth", "Charles", "Danielle", "Eric"], ["3907", "17945", "10091", "10088",
"10132"], ["48", "2", "12", "13", "11"]]
Using this approach is not working but sorts the whole array
let sortedArray = 2dArray.sorted(by: {($0[0] as! String) < ($1[0] as! String) })
[["3907", "17945", "10091", "10088", "10132"], ["48", "2", "12", "13", "11"], ["Adam", "Beth", "Charles", "Danielle", "Eric"]]
Below if the full code
var stringCSV =
"Beth,Charles,Danielle,Adam,Eric\n17945
,10091,10088,3907,10132\n2,12,13,48,11";
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)
}
print(csvFormatted)
let sortedArray = csvFormatted.sorted(by: {($0[0] as! String)
< ($1[0] as! String) })
print(sortedArray)
Using "associated" arrays always ends up being messy.
I would start by creating a struct to represent each object (You haven't said what the numbers are, so I have picked a couple of property names. I have also kept String as their type, but converting to Int is possibly better, depending on what the data actually represents).
struct Person {
let name: String
let id: String
let age: String
}
Now you can combine the arrays and use that to build an array of these structs. Then you can sort by the name property.
let properties = zip(sourceArray[1],sourceArray[2])
let namesAndProperties = zip(sourceArray[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
}
Dictionary(init(grouping:by:)) works fine when I need to group array elements by some property.
What I get is:
{
key1: [
value1,
value2,
value3
],
key2: [
value4,
value5,
value6
]
}
However, I need to transform the array further, grouping every partition into smaller groups so, that the resulting data structure is having two layers:
{
key1: {
anotherKey1: [
value1,
],
anotherKey2: [
value2,
value3]
},
key1: {
anotherKey3: [
value4,
],
anotherKey4: [
value5,
value6]
},
}
What is the simplest way of achieving this result? Currently I have to iterate over the result of the 1st dictionary initializer call:
let grouped = Dictionary(grouping: Array(source), by: {$0.key1)
var grouped2 = [KeyType1 : [KeyType2? : [ValueType]]]()
for pair in grouped {
if let key = pair.key {
grouped2[key] = Dictionary(grouping: pair.value, by: {$0.key1})
}
}
print(grouped2)
And this gets me exactly the result I want: two-level dictionary of arrays.
But I suspect, there is a simpler way of achieving the same result, without manually interating over every key/value pair.
You can do this by doing a init(grouping:by:) call, followed by a mapValues call, which further maps every "group" into another dictionary. And this dictionary is going to be created by init(grouping:by:) again, using the second grouping key.
i.e.
let grouped = Dictionary(grouping: source, by: firstKey)
.mapValues { Dictionary(grouping: $0, by: secondKey) }
For example, to group a bunch of numbers first by their % 2 values, then by their % 4 values:
let source = [1,2,3,4,5,6,7,8,9,10,11,12,13]
let grouped = Dictionary(grouping: source, by: { $0 % 2 })
.mapValues { Dictionary.init(grouping: $0, by: { $0 % 4 }) }
print(grouped)
I have a NSDictionary of NSDictionaries and I was wondering how do I sort the dictionaries by their value (Name)? The number will change from 0-n. Below is how the structure is setup:
let attribute = cumstomClass.Attribute!
attribute =
-Krqq2AWOqWKys0siTmc
Description: ""
Name: "B"
Value: ""
-KrqpSvSX7eKqYfvu5ZO
Description: ""
Name: "A"
Value: ""
Thanks in advance! :)
Since a Dictionary is inherently unsorted, you'll need to use an Array instead. Fortunately, this is easily done via functional techniques:
guard let dict = nsDict as? [String : [String : String]] else { /* handle the error */ }
let sorted = dict.sorted { ($0.value["Name"] ?? "") < ($1.value["Name"] ?? "") }
print(sorted)
This will get you an Array of tuples containing the keys and values of the respective dictionaries, sorted by "Name":
[(key: "KrqpSvSX7eKqYfvu5ZO", value: ["Name": "A", "Value": "", "Description": ""]),
(key: "Krqq2AWOqWKys0siTmc", value: ["Name": "B", "Value": "", "Description": ""])]
Alternately, you can simply get a sorted array of the String keys:
let sortedKeys = dict.sorted { ($0.value["Name"] ?? "") < ($1.value["Name"] ?? "") }.map {
$0.key
}
print(sortedKeys)
which outputs:
["KrqpSvSX7eKqYfvu5ZO", "Krqq2AWOqWKys0siTmc"]
This array of keys can later be used to dynamically look up objects from the dictionary at runtime when populating the collection view. This method will be likely to be more expensive performance-wise than the first approach due to all the dictionary lookups, but will result in lower memory usage if you also need to keep the original dictionary around in its original form for some reason.
I am new to Swift and iOS dev. in general. I was wondering how I could sum multiple values from the same key across different dictionaries. e.g. I have
20 dictionaries with the same key value pair [String: AnyObject] e.g. "Height": 20 I want to sum these and calculate the average.
EG:
// Example dictionary
let player17: [String: AnyObject] = [
"Name": "Les Clay",
"Height": 42,
"Soccer Experience": true,
"Guardian Name(s)": "Wynonna Brown"
]
//Used any object here as they all go into a big array
You can use reduce to add numbers up, like this:
let totalHeight = allPlayers.reduce(0) { (p, c) -> Int in
return p + (c["Height"] as! Int)
}
Note: The c["Height"] as! Int approach requires hard knowledge that "Height" key is present in every dictionary, and its value is of type Int. Otherwise, this will produce an exception at run time.
If some of your dictionaries do not have the proper key in them, or contain the value of a different type, you need to pre-filter or use an optional cast, for example
return p + ((c["Height"] as? Int) ?? 0)
Given
let players: [[String:AnyObject]] = ...
here's another approach
let sum = players.flatMap { $0["Height"] as? Int }.reduce(0, combine: +)
If a dictionary doesn't have a valid Int value for the "Height" key then that dictionary is ignored.
Is it possible to iterate through an NSDictionary in a specific order so I can save an index for an key-value pair in CoreData, based on the order that the data is originally typed? i.e in the code below Set 1 would have an index of 1, set 2 - 2 and set 3 - 3 rather than at random, as is normal NSDictionary behaviour? Thanks in advance if anyone can either give a solution, or tell me its not possible!
let string1 = "rain, wait, train".wordsInArray
let string2 = "oil, join, coin".wordsInArray
let string3 = "made, came, same".wordsInArray
let lists: [String: [String]] =
["Set 1: List 1": string1,
"Set 1: List 2": string2,
"Set 1: List 3": string3]
var index = 0
For list in lists {
list.listIndex = index
index = index + 1
coreDataStack.saveMainContext()
}
extension String {
var wordsInArray:[String] {
return componentsSeparatedByCharactersInSet(NSCharacterSet.punctuationCharacterSet()).joinWithSeparator("").componentsSeparatedByString(" ")
}
In your example, your keys happen to be added in alphanumeric order. This could be fortuitous but if you meant to get the data in key sorted order, that is not the same request as creation order and it would be easy to do:
for (key,wordlist) in lists.sort({$0.0 < $1.0})
{
// you will be getting the dictionary entries in key order
}
// trickier to access by index though
let aKey = lists.keys.sort()[2]
let aWordList = lists[aKey]
// but it lets you get the wordlist from the key
let S1L3Words = lists["Set 1: List 3"]
On the other hand, if you're meaning to ONLY use a creation order, and do not need to access the elements by their keys, you could declare your structure as an array of tuples:
let lists: [(String, [String])] =
[
("Set 1: List 1", string1),
("Set 1: List 2", string2),
("Set 1: List 3", string3)
]
// your for loop will get them in the same order
for (key,wordlist) in lists
{
// array order, no matter what the key values are
}
// also accessible directly by index
let (aKey, aWordList) = lists[2] // ("Set 1: List 3", ["made", "came", "same"])
And finally, if you don't need keys at all, you can just make it an array of arrays:
let lists: [[String]] = [ string1 , string2 , string3 ]
for (key,wordlist) in lists
{
// array order
}
// also accessible directly by index
let aWordList = lists[2] // ["made", "came", "same"]