Query multidimensional arrays: filter on unique elements

I'm trying to extract an album list from an iTunes playlist export.

The file has a list of all songs in each album, and I want just 1 entry per album, discarding the song titles.

So, I need to filter on unique album titles.

I can accomplish this iteratively, but was wondering if there's a more elegant way to achieve this through higher-order functions?


Here's the 2D songs array:

var songs[[String]]

Here's the structure of inner array:

artist, album, song, ...

Array example:

("Beck", "Odelay", "Odelay")
("Beck", "Odelay", "Hotwax")
("Beck", "Odelay", "Derelict")

...

After processing, there should be just a single entry for "Odelay":

("Beck", "Odelay", "Odelay")


In SQL database it's trivial to accomplish this query:

SELECT artist, DISTINCT album FROM songs ORDER BY album


Here's my iterative code that achieves my goal:
var songs = [[""]]
var uniqueAlbums = [[""]]

    <code to load songs array here>


    // grab list of unique album titles
    let albumTitles = (Set(songs.map { $0[2] })).sorted()

    var cnt = 0
    for title in albumTitles {

        // grab all songs for this album
        let currentAlbum = songs.filter { $0[2] == title }

        // loop thru songs till last song, then add only one entry per album to new array
        for album in currentAlbum {
            if cnt < currentAlbum.count - 1 {
                cnt += 1
            } else {
                uniqueAlbums.append(album)
            }
        }
        cnt = 0
    }

    // uniqueAlbums[] is now a filtered collection of songs[] with only 1 album per line

It'd be neat if you could use SQL-like queries within Swift for 2D arrays.

Hi!

There's a number of inconsistencies in the description of your problem and what you want (the inner array is written as a tuple rather than an array and the album Odelay looks like it has a song named Odelay which it hasn't, the example code seems to suggest that the album title is at index 2 while in the example it seems to be at index 1, and the resulting 2D array will start with a [""]-element if run as written, it's unclear whether you want any song to be part of the result or not (which one if so, and why?), etc), but assuming you want your result to be a 2D array in which each inner array is unique and has two elements: artist and album, you can do this:

let uniqueAlbums = Set(songs.map { Array($0.prefix(2)) }).map { $0 }

(There are at least 45 albums named "Smile" (by different artists), so I guess it's not enough for it to be unique only on album title.)

And unless you really need the result to be of type [[String]], you'd probably want something like this instead:

struct Album : Hashable {
    var title, artist: String
}
let uniqueAlbums = Set(songs.map { Album(title: $0[1], artist: $0[0]) })

(the inner array is written as a tuple rather than an array

Yes, I meant:
["Beck", "Odelay", "Hotwax"]

and the album Odelay looks like it has a song named Odelay which it hasn't,

My, you're thorough...heh heh
Yes, I meant
["Beck", "Odelay", "Lord Only Knows"]

t's unclear whether you want any song to be part of the result or not

The song titles are irrelevant, and will be discarded once I get the list into Excel.

There are at least 45 albums named "Smile" (by different artists), so I guess it's not enough for it to be unique only on album title.

Good point; luckily my collection is unique on album titles.

And unless you really need the result to be of type [[String]] , you'd probably want something like this instead:

Yes, I'd normally use struct here, but wanted to simplify things for my question.

let uniqueAlbums = Set(songs.map { Array($0.prefix(2)) }).map { $0 }

Thanks, I think this will work, but need to test.

This code works:

let uniqueAlbums = Set(songs.map { Array($0.prefix(2)) }).map { $0 }

Because 2 of the 3 fields in the array slice are guaranteed to be identical (artist, album), leaving the third (song) as the one that varies. So it will always filter down to one album, artist.

That leaves my original question unanswered:
is there a way using higher-order function to filter an array of n members by one, unique element, whether or not there are duplicate strings in the other elements.

My iterative code above will always work.