How to add object in array after every 3 objects in array? - ios

I have an array from 0 to 100.
let array = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"...."100"]
now, I want to add "-" after every 3 objects in an array.
So the output would be like this
array = ["0", "1", "2","-","3", "4", "5","-", "6", "7", "8", "-","9"...."100"]
So, How can I achieve this?
and what if i have model array instead of String array?
struct SubCategory {
var title: String = ""
var subTitle: String = ""
}
let array = [SubCategory]()

let array = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
extension Array {
func chunks(of size: Int) -> [[Element]] {
return stride(from: 0, to: count, by: size).map {
let n = Swift.min(size, count - $0)
return Array(self[$0 ..< $0 + n])
}
}
}
let joined = Array(array.chunks(of: 3).joined(separator: ["-"]))

Here is a way to directly create the result with a single map and a bit of math:
extension Array {
func adding(_ element: Element, afterEvery n: Int) -> [Element] {
guard n > 0 else { fatalError("afterEvery value must be greater than 0") }
let newcount = self.count + self.count / n
return (0 ..< newcount).map { (i: Int) -> Element in
(i + 1) % (n + 1) == 0 ? element : self[i - i / (n + 1)]
}
}
}
Example:
let result = ["0", "1", "2", "3", "4", "5", "6"].adding("-", afterEvery: 3)
print(result)
Output:
["0", "1", "2", "-", "3", "4", "5", "-", "6"]
Example 2:
This time with [Int]:
let result2 = [1, 2, 3, 4, 5, 6].adding(0, afterEvery: 2)
print(result2)
Output:
[1, 2, 0, 3, 4, 0, 5, 6, 0]
Example 3:
With a custom struct:
struct SubCategory: CustomStringConvertible {
var title = ""
var subTitle = ""
var description: String { return "SubCategory(title: \(title), subTitle: \(subTitle)" }
}
let array: [SubCategory] = [
SubCategory(title: "2001", subTitle: "A Space Odyssey"),
SubCategory(title: "Star Wars Episode 1", subTitle: "The Phantom Menace"),
SubCategory(title: "Star Wars Episode 2", subTitle: "Attack of the Clones"),
SubCategory(title: "Star Wars Episode 3", subTitle: "Revenge of the Sith"),
SubCategory(title: "Star Wars Episode 4", subTitle: "A New Hope"),
SubCategory(title: "Star Wars Episode 5", subTitle: "The Empire Strikes Back"),
SubCategory(title: "Star Wars Episode 6", subTitle: "Return of the Jedi")
]
let result3 = array.adding(SubCategory(title: "none", subTitle: "none"), afterEvery: 3)
print(result3)
Output:
[SubCategory(title: 2001, subTitle: A Space Odyssey, SubCategory(title: Star Wars Episode 1, subTitle: The Phantom Menace, SubCategory(title: Star Wars Episode 2, subTitle: Attack of the Clones, SubCategory(title: none, subTitle: none, SubCategory(title: Star Wars Episode 3, subTitle: Revenge of the Sith, SubCategory(title: Star Wars Episode 4, subTitle: A New Hope, SubCategory(title: Star Wars Episode 5, subTitle: The Empire Strikes Back, SubCategory(title: none, subTitle: none, SubCategory(title: Star Wars Episode 6, subTitle: Return of the Jedi]
Mutating version
Here is a version that mutates the original array instead of creating a new one:
extension Array {
mutating func add(_ element: Element, afterEvery n: Int) {
guard n > 0 else { fatalError("afterEvery value must be greater than 0") }
var index = (self.count / n) * n
while index > 0 {
self.insert(element, at: index)
index -= n
}
}
}
Example:
var array = ["0", "1", "2", "3", "4", "5", "6"]
array.add("-", afterEvery: 3)
print(array)
Output:
["0", "1", "2", "-", "3", "4", "5", "-", "6"]

Related

Find Scalable solution to print data from Array in Swift

I have an array starting from 1 to 100 and I have to print element if the number is divisible by 4 it should print the letter "A" and if the number is divisible by 5 it should print the letter "B" and if it is divisible by both then "AB" I want to make a scalable solution if in future I want to add number divisible by 8 should print "C" and divisible by 4 & 8 should print "AC", by 5&8 should print "BC" and if all three then "ABC"
desired output:
1
2
3
A
B
6
7
C
9
B
11
AB
13
14
...
I wrote this
for number in 1...100 {
if number.isMultiple(of: 4) && !number.isMultiple(of: 5){
print("A"
} else if !number.isMultiple(of: 4) && number.isMultiple(of: 5){
print("B")
} else if number.isMultiple(of: 4) && number.isMultiple(of: 5){
print("AB")
} else {
print(number)
}
}
Please provide a scalable solution to keep adding If-else is not a good option.
You were pretty close but you don't need the else conditions. Just add the character to the string if it matches another condition:
for number in 1...100 {
var string = ""
if number.isMultiple(of: 4) { string.append("A") }
if number.isMultiple(of: 5) { string.append("B") }
if number.isMultiple(of: 8) { string.append("C") }
print(string.isEmpty ? number : string)
}
Using a dictionary to store the characters:
let dict = [
4: "A",
5: "B",
8: "C"
]
for number in 1...100 {
var string = ""
for (key, character) in dict where number.isMultiple(of: key) {
string.append(character)
}
print(string.isEmpty ? number : string)
}
Note that dictionary is an unordered collection. If you need the characters to be sorted you would need to sort the dictionary by its values before iterating its key value pairs:
let sortedDict = dict.sorted(by: { $0.value < $1.value })
for number in 1...100 {
var string = ""
for (key, character) in sortedDict where number.isMultiple(of: key) {
string.append(character)
}
print(string.isEmpty ? number : string)
}
Here it is, instead of using if-else, you can just add up whenever you need
var stringArray = [String]()
for number in 0...100 {
stringArray.append(String(number))
}
// stringArray = ["0","1", "2", "3",....,"99", "100"]
// Adding a zero before to compare with the index
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 4 == 0 {
value = Int(item) == nil ? item + "A": "A"
}
return value
})
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 5 == 0 {
value = Int(item) == nil ? item + "B": "B"
}
return value
})
stringArray = stringArray.enumerated().map({ index, item in
var value = item
if index % 8 == 0 {
value = Int(item) == nil ? item + "C": "C"
}
return value
})
stringArray.removeFirst()
print(stringArray)
Result::
"1", "2", "3", "A", "B", "6", "7", "AC", "9", "B", "11", "A", "13", "14", "B", "AC", "17", "18", "19", "AB", "21", "22", "23", "AC", "B", "26", "27", "A", "29", "B", "31", "AC", "33", "34", "B", "A", "37", "38", "39", "ABC", "41", "42", "43", "A", "B", "46", "47", "AC", "49", "B", "51", "A", "53", "54", "B", "AC", "57", "58", "59", "AB", "61", "62", "63", "AC", "B", "66", "67", "A", "69", "B", "71", "AC", "73", "74", "B", "A", "77", "78", "79", "ABC", "81", "82", "83", "A", "B", "86", "87", "AC", "89", "B", "91", "A", "93", "94", "B", "AC", "97", "98", "99", "AB"
if you just want [Any] type then just
var resultArray = [Any]()
resultArray = stringArray.map({ number in
if let num = Int(number) { return num }
else { return number }
})
print(resultArray)

How to define structure in iOS XCode for JSON?

Being new to iOS, XCode I'm trying to create a structure to represent JSON data. However, regardless of what I try for defining "segments" (which consists of a int and an array of strings) XCode just errors out and when I try to follow suggested fixes it just generates other errors.
Anybody know how to actually define a structure for JSON that is named, e.g., not using "ANY", since all the name-value pairs and data types are known?
Example XCODE (one variation shown below, though dozens have been tried and generates errors):
struct Information: Decodable {
var entry: [Entry]
}
struct Entry: Decodable {
var section: Int
***ERROR HERE ->*** var segments: Array<var id: Int, var values: Array<String>>
}
Example JSON:
{
"entry": [
{
"section": 1,
"segments": [
{
"id": 1,
"values": ["1", "2", "3"]
},
{
"id": 2,
"values": [ "4", "5", "6" ]
}
]
},
{
"section": 2,
"segments": [
{
"id": 1,
"values": ["7", "8", "9"]
},
{
"id": 2,
"values": [ "a", "b", "c" ]
}
]
}
]
}
It's the same as on the top level: You have to create a struct for the lower level.
struct Information: Decodable {
let entry: [Entry]
}
struct Entry: Decodable {
let section: Int
let segments: [Segment]
}
struct Segment: Decodable {
let id: Int
let values: [String]
}

Did here existed a better solution to handle this api problem in Swift?

my raw json data maybe mislead you. The keys array were not always matched its value at the same index. So I rewrote my data to reflect my intentions.
Assume we have a table view to show songs with its json:
{
"albums": [
{
"title": "A",
"id": "174172",
"artistName": "Person X"
},
{
"title": "B",
"id": "19201827",
"artistName": "Person Y"
},
{
"title": "C",
"id": "1927",
"artistName": "Person Z"
}
],
"songs": [
{
"name": "Song A",
"albumName": "A",
"albumId": "174172",
"duration": 180
},
{
"name": "Song B",
"albumName": "A",
"albumId": "174172",
"duration": 200
},
{
"name": "Song C",
"albumName": "B",
"albumId": "19201827",
"duration": 216
},
{
"name": "Song D",
"albumName": "C",
"albumId": "1927",
"duration": 216
}
]
}
My schemas like this:
struct Album: Decodable {
let title: String
let id: String
let artistName: String
}
struct Song: Decodable {
let name: String
let albumName: String
let albumId: String
let duration: Int
}
The view controller fake code like this:
class ViewController: UIViewController {
var songs: [Song] = []
var albums: [Album] = []
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return songs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableview.dequeueReusableCell(withIdentifier: "SongCell", for: indexPath) as! SongCell
let song = songs[indexPath.row]
let album = albums.first { $0.id == song.albumId }
cell.updateUI(withSong: song, album: album)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let song = songs[indexPath.row]
let album = albums.first { $0.id == song.albumId }
pushDetailSongViewController(song, album)
}
func pushDetailSongViewController(_ song: Song, _ album: Album?) {
}
}
When we have too many songs with albums, let album = albums.first { $0.id == song.albumId } is place with terrible performance problem.
So what data structure should we use here to handle updateing performance?
After parsing both keys and values, you could combine the two arrays to a dictionary and then have your table view's data source be that dictionary.
First, make your Song struct conform to the Hashable protocol:
struct Song: Hashable {
Create an array for both albums and songs:
var albums: [Album] = []
var songs: [Song] = []
Then, reduce the songs array to a dictionary as follows:
let data = songs.reduce([Album: Song]()) { (result, song) -> [Album: Song] in
guard let album = albums.first(where: { $0.id == song.albumID }) else { return result }
return result.merging([album: song], uniquingKeysWith: { (first, _) in first })
}
I tested this with two demo arrays:
let albums = [Album(id: "1", name: "one"), Album(id: "2", name: "two"), Album(id: "3", name: "three")]
let songs = [Song(albumID: "1", name: "ONE"), Song(albumID: "2", name: "TWO"), Song(albumID: "3", name: "THREE")]
Those turn data into:
[
<Album id: "1", name: "one"> : <Song albumID: "1", name: "ONE">,
<Album id: "2", name: "two"> : <Song albumID: "2", name: "TWO">,
<Album id: "3", name: "three">: <Song albumID: "3", name: "THREE">
]
Extra Credit
If you want all songs for each album, you have to make data [Album: [Song]]:
let data = albums.reduce([Album: [Song]]()) { (result, album) -> [Album: [Song]] in
let _songs = songs.filter({ $0.albumID == album.id })
guard !_songs.isEmpty else { return result }
return result.merging([album: _songs], uniquingKeysWith: { (first, _) in first })
}
With the following arrays:
let albums = [Album(id: "1", name: "one"), Album(id: "2", name: "two"), Album(id: "3", name: "three")]
let songs = [Song(albumID: "1", name: "ONE"), Song(albumID: "2", name: "TWO"), Song(albumID: "3", name: "THREE"),
Song(albumID: "1", name: "ONE-1"), Song(albumID: "1", name: "ONE-2"), Song(albumID: "3", name: "THREE-1")]
...you will get:
[
<Album name: three, id: 3>: [
<Song name: THREE, albumID: 3>
<Song name: THREE-1, albumID: 3>
],
<Album name: one, id: 1>: [
<Song name: ONE, albumID: 1>,
<Song name: ONE-1, albumID: 1>,
<Song name: ONE-2, albumID: 1>
],
<Album name: two, id: 2>: [
<Song name: TWO, albumID: 2>
]
]
You should create a struct like below after the JSON parsing has been completed.
struct DataSet {
let id: String
let name: String
let value: String
}
Moreover looking at your json, it seems like objects at same index of Key and Value arrays are same with respect to id and key . So, at the time of combining both the arrays, if you iterate one array, you will be knowing the index of another array (O(1)) . Hence time complexity of merging will be O(n).
If you don't want to change too much, maybe a mapDictionary will help:
let keyMaps = [String : String](uniqueKeysWithValues: keys.map{($0.id, $0.name)})
keyNamesInSequenceSameWithValues = values.map{ keyMaps[$0.key]! )

How do I find the position of a certain value given a second array?

I am comparing two arrays, and I need to figure out at which position the duplicates occur. I have to map the array, but am unsure on how to do that. In the end I want to have a filtered array. 1 indicates there is a duplicate, and "" indicates there is no duplicate.
var Strarr = ["1", "2", "3", "4"]
var importarr = ["1", "3"]
filteredArray = ["1", "", "1", ""]
let result = Strarr.map { importarr.contains($0) ? "1" : ""}
print(result) // ["1", "", "1", ""]
A bit shorter.
This will work:
var Strarr = ["1", "2", "3", "4"]
var importarr = ["1", "3"]
var newArray = Strarr
for i in 0..<Strarr.count {
if importarr.contains(Strarr[i]) {
newArray[i] = "1"
}
else{
newArray[i] = "0"
}
}
print(newArray)
prints "["1", "0", "1", "0"]\n"
this should do it
var Strarr = ["1", "2", "3", "4"]
var importarr = ["1", "3"]
var filteredArray = Strarr.map(function(val){
return importarr.indexOf(val) != -1 ? "1" : "";
});

How to use a struct with a library in a tableview

I have created a struct with a library and have initialised that struct in another struct. Now I want to use the resulting struct in a Table View. However, the struct currently doesn't work properly. I've tried to find out why, but seem to be stuck.
It looks like the issue is that the library doesn't get translated properly to the struct. For in instance, when I do a count on an instance of the struct, using the following code:
var personalDetailsStructExtra: [PersonalDetailsStruct] = []
personalDetailsStructExtra.count
It returns 0, while it should be 5 (See code below, there are 5 entries into the dictionary):
struct PersonalDetailsStructLibrary {
let library = [
[
"title": "Country",
"icon": "country.pdf",
"questions": ["Belgium", "France", "Germany", "Netherlands", "Sweden", "UK", "USA"]
],
[
"title": "Age",
"icon": "age.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Gender",
"icon": "gender.pdf",
"questions": ["Male", "Female", "Other"]
],
[
"title": "Height",
"icon": "height.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Weight",
"icon": "weight.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
] }
And
struct PersonalDetailsStruct {
var title: String?
var icon: UIImage?
var questions: [String] = []
init(index: Int) {
let personalDetailsStructLibrary = PersonalDetailsStructLibrary().library
let personalDetailsDictionary = personalDetailsStructLibrary[index]
title = personalDetailsDictionary["title"] as! String!
let iconNamePD = personalDetailsDictionary["icon"] as! String!
icon = UIImage(named: iconNamePD!)
questions += personalDetailsDictionary["artists"] as! [String]
} }
As you can see in the code I want use the struct to fill up a label (title), image (icon) and UITextView with UIPickerView (questions) in my table view.
Since it doesn't work, I'm looking for either:
A: Feedback on how to make this code work in a tableview
B: Whether I should use another method to populate the dynamic cells in my tableview
You have to initialize the personalDetailsStructExtra array, only then you would see the required count.
var personalDetailsStructExtra = [PersonalDetailsStruct]()
PersonalDetailsStructLibrary().library.count
let count = PersonalDetailsStructLibrary().library.count
for i in 0..<count {
personalDetailsStructExtra.append(PersonalDetailsStruct(index: i))
}
personalDetailsStructExtra.count // 5
A better option is to use the Library struct to construct and maintain all model objects.
struct PersonalDetailDataSource {
let library = [
[
"title": "Country",
"icon": "country.pdf",
"questions": ["Belgium", "France", "Germany", "Netherlands", "Sweden", "UK", "USA"]
],
[
"title": "Age",
"icon": "age.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Gender",
"icon": "gender.pdf",
"questions": ["Male", "Female", "Other"]
],
[
"title": "Height",
"icon": "height.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
[
"title": "Weight",
"icon": "weight.pdf",
"questions": ["1", "2", "3", "4", "5", "6", "7"]
],
]
var personalDetails = [PersonalDetail]()
init() {
loadData()
}
mutating private func loadData() {
for i in 0..<library.count {
let personalDetailsDictionary = library[i]
let title = personalDetailsDictionary["title"] as! String!
let iconName = personalDetailsDictionary["icon"] as! String!
let questions = personalDetailsDictionary["questions"] as! [String]
personalDetails.append(PersonalDetail(title: title, iconName: iconName, questions: questions))
}
}
}
struct PersonalDetail {
var title: String?
var icon: UIImage?
var questions: [String] = []
init(title: String, iconName: String, questions: [String]) {
self.title = title
if let icon = UIImage(named: iconName) {
self.icon = icon
}
self.questions = questions
}
}
PersonalDetailDataSource().personalDetails.count // count: 5

Resources