how can i join in array same id in swift? - arrays

i want to make like a group array
struct songST {
var singerid:[Int]
var songname:[String]
var songembeded:[String]
}
**fist item;**
songST(singerid: "1", songname: ["Its my life"], songembeded: ["url1"])
**seconditem=**
songST(singerid: "1", songname: ["Always"], songembeded: ["url2"])
**i want to make join like this**
songST(singerid: "1", songname: ["Its my life","Always"], songembeded: ["url1","url2"])
if there was same singerid than will join
how can i do that? Please Help me.

I think you can improve how data is modeled.
A song consists of the following fields:
singer id
song name
song url
So it would make sense to create a model with exactly those fields, as opposed to creating an array for each of them:
struct Song {
let singerId: Int
let songName: String
let songEmbedded: String
}
Next, you need a data container for all your songs, which must be grouped by singer. The easiest way is to use a dictionary, whose key is the singer id and the value is an array of Song.
Rather than manually creating and handling the dictionary, it's better if a proper data container is created:
struct Singers {
var singers: [Int : [Song]] = [:]
mutating func addSong(song: Song) {
if self.singers[song.singerId] == nil {
self.singers[song.singerId] = []
}
self.singers[song.singerId]?.append(song)
}
func getSongs(singerId: Int) -> [Song] {
if self.singers[singerId] == nil {
return []
}
return self.singers[singerId]!
}
}
The Singers struct contains a dictionary property, and two method:
addSong adds a song, assigning it to the proper array identified by the key (singer id)
getSongs returns an array of songs for a singer id
Some sample code taken from a storyboard:
let s1 = Song(singerId: 1, songName: "Its my life", songEmbedded: "url1")
let s2 = Song(singerId: 1, songName: "Always", songEmbedded: "url2")
let s3 = Song(singerId: 2, songName: "Another", songEmbedded: "url3")
var singers = Singers()
singers.addSong(s1)
singers.addSong(s2)
singers.addSong(s3)
singers.getSongs(1) // Returns an array of 2 items
singers.getSongs(2) // Returns an array of 1 item
singers.getSongs(3) // Returns an empty array (no song for singer 3)

Easy.
You asked - "if there was same singerid than will join".
Make a function that uses that if statement:
func combineFirstSongST(firstSongST: songST, withSecondSongST secondSongST: songST) -> songST?
{
if firstSongST.singerid != secondSongST.singerid
{
return nil
}
else
{
return songST(singerid: firstSongST.singerid, songname: firstSongST.songName + secondSongST.songName, songembeded: firstSongST.songembeded + secondSongST.songembeded) //single statement
}
}
You should use camel case.

Related

Populate UITableView with Sections

I'm trying to populate TableView with sections. Where an artist is the Section Name and all songs of that artist are listed below. In total over 100 artists. Like so
FirstArtist
- Song 1
- Song 2
SecondArtist
- Song 1
- Song 2
OneMoreArtist...
I have an array of objects Song
struct Song {
let songName: String?
let album: String?
let artist: String?
}
var songs = [Song]()
And an array of all artists names
var artists = [String]()
As I understand, to populate tableView with sections I need a Dictionary with Artist as key and array of Songs as value, like so
var toShow = [String : [Song]]()
So, then I'm trying to loop
for artist in artists {
for song in songs {
if song.artist == artist {
toShow[artist] = [song]
// toShow[artist]?.append(song)
}
}
}
But it doesn't work.
Probably I'm going wrong way.
What is the solution in this situation?
Thanks
Update
Made a stuct
struct ArtistWithSongs {
let name: String
let songs: [Song]
init(name: String, songs: [Song]) {
self.name = name
self.songs = songs
}
}
and trying to loop
var artistWithSongs = [ArtistWithSongs]()
for artist in artists {
for song in songs {
if artist == song.artist {
artistWithSongs.append(ArtistWithSongs(name: song, songs: [song]))
}
}
}
But apparently, my variant of looping is not correct. Now I'm getting an array of objects contains duplicate keys and only one Song per Key. It looks like
[Atrist1 : [Song1], Artist1 : [Song2], Artist1 : [Song3], ...]
My question is - What is the right way to make a loop, or is it possible somehow to merge objects with an identical key inside a Dictionary to get this [Artist1 :[Song1, Song2, Song3]]?
Thanks to everyone, I found a solution here, it worked for what I was trying to achieve. Roughly like this
let toShow = artistWithSongs.reduce([ArtistWithSongs](), { partialResult, artist in
var dupe = partialResult.filter {$0.songName == group.name }.first
if let dupeArtist = dupe {
dupeArtist.songs?.append(contentsOf: artist.songs ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
In addition to your Song struct
struct Song {
let songName: String?
let album: String?
}
create Artist one like
struct Artist {
let name:String
let songs = [Song]()
}
Then create an array of them
var artists = [Artist]()
let s1 = Song(songName:"name1",album:"alb1")
let s2 = Song(songName:"name2",album:"alb2")
let artist1 = Artist(name:"art",songs:[s1,s2])
artists.append(artist1)
In numberOfSections return
artists.count
In numberOfRows return
artists[section].songs.count
In cellForRowAt access
let song = artists[indexPath.section].songs[indexPath.row]
If you want to transform an array of songs ([Song]) to a dictionary ([String : [Song]]), I would recommend using functional style:
self.toShow = songs.reduce([String: [Song]]()) { acc, next in
// mutable copy
var acc = acc
// since artist is optional we need to substitute it with non-optional value if it's nil
let artist = next.artist ?? "Unknown Artist"
// if dictionary already has this artist, then append the `next` song to an array
if acc[artist] != nil {
acc[artist]?.append(next)
} else {
// otherwise create new array
acc[artist] = [next]
}
return acc
}
In your table's data source provide the number of sections:
func numberOfSections(in tableView: UITableView) -> Int {
return self.toShow.keys.count
}
And then you'll face a trouble: for number of rows in section you'll need to know which artist is the section stands for. So, I suggest you to use one more structure, an array of artists so that you keep the order of sections consistent:
self.artists = Array(self.toShow.keys).sorted()
This way, you can provide your table with following numbers of sections and rows:
func numberOfSections(in tableView: UITableView) -> Int {
return self.artists.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let artist = self.artists[section] // get the artist's name
return self.toShow[artist]?.count ?? 0
}

Filter between two arrays of objects avoiding nested for loop

In Swift 4, how do you convert a nested for loop that is checking equality on only one property into a filter?
Basic Example:
// Basic object
struct Message {
let id: String
let content: String
init(id: String, content: String) {
self.id = id
self.content = content
}
}
// Array of objects
let local = [Message.init(id: "1234", content: "test1"), Message.init(id: "2345", content: "test2")]
// Array of objects, one has updated content
let server = [Message.init(id: "1234", content: "testDiff1"), Message.init(id: "3456", content: "test3")]
var foundList = [Message]()
// Nested loop to find based on one property matching
for i in local {
for j in server {
if i.id == j.id {
foundList.append(i)
}
}
}
This works as expected (foundList contains local[0]) but feeling there should be a 'swift-ier' way to do this?
for loops can be rewritten with one for loop + where condition:
for m in local where server.contains(where: { $0.id == m.id }) {
foundList.append(m)
}
or combine filter with contains:
foundList = local.filter { m in server.contains(where: { $0.id == m.id }) }
P.S. Also, conform Message struct to Equatable protocol. It allows you to simplify contains method:
for m in local where server.contains(m) {
foundList.append(m)
}
using filter:
foundList = local.filter { server.contains($0) }
Use filter.
import Foundation
struct Message: Equatable {
let id: String
let content: String
static func == (lhs: Message, rhs: Message) -> Bool {
return lhs.id == rhs.id
}
}
let local = [ Message(id: "1234", content: "test1"), Message(id: "2345", content: "test2") ]
let server = [ Message(id: "1234", content: "testDiff1"), Message(id: "3456", content: "test3") ]
let foundList = local.filter { server.contains($0) }
print(foundList) // prints [Message(id: "1234", content: "test1")]
Note that I removed the initialiser and I’m using Message(...) instead Message.init(...).
It should be easy to write a filter for this. I assume you want the local messages that are also on the server.
let m = local.filter
{
localMessage in
server.contains(where: { $0.id == localMessage.id })
}
If you have a lot of messages, it might be a good idea to create a set of interesting ids.
let filterIds = Set(server.map{ $0.id })
let m = local.filter { filterIds.contains($0) }
That will reduce the big O time complexity because you aren't doing a linear search through the set. The time complexity on contains(where:) for an array is going to be O(n) where n is the number of elements. For a set, Apple documents the complexity of contains() as O(1) Of course, there is an overhead for creating the set and for small n the linear search might be quicker than the set access.

How to group Array of Dictionaries by a key in swift?

For example, I have this array of dictionaries
[["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
I want to group them by Country to become
{"Egypt": [{"Country":"Egypt","Name":"Mustafa","Age":"20"} {"Country":"Egypt","Name":"Ali","Age":"40"}],
"Palestine": [{"Country":"Palestine","Name":"Amani","Age":"30"},{"Country":"Palestine","Name":"Omar","Age":"15"}],
"Jordan":[{"Country":"Jordan","Name":"Ahmad","Age":"25"},{"Country":"Jordan","Name":"Mustafa","Age":"20"}]
}
Please help.
Swift has a nice function that does this for you...
let people = [["Country":"Egypt","Name":"Mustafa","Age":"20"],["Country":"Palestine","Name":"Omar","Age":"15"],["Country":"Egypt","Name":"Ali","Age":"40"],["Country":"Jordan","Name":"Ahmad","Age":"25"],["Country":"Palestine","Name":"Amani","Age":"30"],["Country":"Jordan","Name":"Mustafa","Age":"20"]]
let peopleByCountry = Dictionary(grouping: people, by: { $0["Country"]! } )
peopleByCountry will now be the format that you want.
You can read more about this function in the documentation.
Just to add to Hamish's comment.
You really shouldn't be working with Dictionaries here. You should be working with Structs...
struct Person {
let countryName: String
let name: String
let age: Int
}
Even better would be to have a Country struct...
struct Country {
let name: String
}
and use that in the Person for their country property instead of String.
let arrCountry: [[String:String]] = [["Country":"Egypt","Name":"Mustafa","Age":"20"],
["Country":"Palestine","Name":"Omar","Age":"15"],
["Country":"Egypt","Name":"Ali","Age":"40"],
["Country":"Jordan","Name":"Ahmad","Age":"25"],
["Country":"Palestine","Name":"Amani","Age":"30"],
["Country":"Jordan","Name":"Mustafa","Age":"20"]]
func sortCountry() {
var sortedCountries : [String : [[String:String]]] = [:]
for object in arrCountry {
let country = object["Country"] as! String
if var arrCountry = sortedCountries[country] {
arrCountry.append(object)
sortedCountries[country] = arrCountry
}
else {
sortedCountries[country] = [object]
}
}
}
Well I would go like this:
Get all the countries by traversing the array once and store it in an array.
Loop for this array of countries.
Filter the array with predicate where country is current country.
Store this in the final dictionary of country.

deleting objects if string of object matches string in a separate array

I have an nsobject class with four strings
class Post: NSObject {
var author: String!
var postID: String!
var pathToImage: String!
var userID: String!
}
I also have a separate class viewcontroller which has a function grabbing posts from firebase. I have an array called posts = [Post](), which is filled by a seperate function going through firebase and grabbing data for each photo. I also have an array called removeArray which is array of strings, which the string is the postID of certain posts. Now this is my problem, I am trying to loop through removeArray, check if the each in removeArray = to the each in posts.postID and check if they are equal. Then either I delete that each in posts.postID post, or I create a new array that is posts - posts with postID's in removeArray. Here is my code now that does not work, it just keeps posts as is.
if posts != nil {
if var array = UserDefaults.standard.object(forKey: "removeArray") as? [String] {
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
return self.posts.count
}
}
So if you have any idea how to take a string in an array, check if that string if eqaul to a string in an array of objects.postID, and remove that object from the array if it is equal. I have tried to research a way to filter it, but so far nothing. Please give me some feedback. Thanks
My problem = http://imgur.com/a/m5CiY
var posts = [p1,p2,p3,p4,p5]
let array = ["aaa","bbb"]
var new:Array<Post> = []
for each in posts {
for one in array {
if one == each.postID {
new.append(each)
}
}
}
print("This objects should be remvoed: \(new)")
posts = Array(Set(posts).subtracting(new))
print("After removing matching objects: \(posts)")
You could use reduce(_:_:)!
class Country {
var name: String!
init(name: String) {
self.name = name
}
}
let countries = [Country(name: "Norway"), Country(name: "Sweden"), Country(name: "Denmark"), Country(name: "Finland"), Country(name: "Iceland")]
let scandinavianCountries = ["Norway", "Sweden", "Denmark"]
// Store the objects you are removing here
var nonScandinavianCountries: [Country]?
let scandinavia = countries.reduce([Country](), {
result, country in
// Assign result to a temporary variable since result is immutable
var temp = result
// This if condition works as a filter between the countries array and the result of the reduce function.
if scandinavianCountries.contains(country.name) {
temp.append(country)
} else {
if nonScandinavianCountries == nil {
// We've reached a point where we need to allocate memory for the nonScandinavianContries array. Instantiate it before we append to it!
nonScandinavianCountries = []
}
nonScandinavianCountries!.append(country)
}
return temp
})
scandinavia.count // 3
nonScandinavianCountries?.count // 2
Resouces:
https://developer.apple.com/reference/swift/array/2298686-reduce

Swift Array remove only one item with a specific value

Alright, this should not be too difficult, but Sunday morning proves me wrong...
I have an Array with structs, and want to remove only one struct that matches its name property to a String. For example:
struct Person {
let name: String
}
var myPersons =
[Person(name: "Jim"),
Person(name: "Bob"),
Person(name: "Julie"),
Person(name: "Bob")]
func removePersonsWith(name: String) {
myPersons = myPersons.filter { $0.name != name }
}
removePersonsWith(name: "Bob")
print(myPersons)
results in:
[Person(name: "Jim"), Person(name: "Julie")]
But how do I only remove one Bob?
filter filters all items which match the condition.
firstIndex returns the index of the first item which matches the condition.
func removePersonsWith(name: String) {
if let index = myPersons.firstIndex(where: {$0.name == name}) {
myPersons.remove(at: index)
}
}
However the name of the function is misleading. It's supposed to be removeAPersonWith ;-)

Resources