Home > Net >  Kotlin filter nested lists
Kotlin filter nested lists

Time:07-17

I want to filter a list that has episodes of series. The list looks a bit like this:

[
  {
    "media": {
      "episode_number": "7"
    },
    "collection": {
      "collection_id": "26464"
    }
  },
  {
    "media": {
      "episode_number": "6"
    },
    "collection": {
      "collection_id": "26464"
    }
  },
  {
    "media": {
      "episode_number": "9"
    },
    "collection": {
      "collection_id": "123456"
    }
  }
]

I only want the highest episode number of each collection (serie). so the result would be:

[
  {
    "media": {
      "episode_number": "7"
    },
    "collection": {
      "collection_id": "26464"
    }
  },
  {
    "media": {
      "episode_number": "9"
    },
    "collection": {
      "collection_id": "123456"
    }
  }
]

I tried this using a filter but I can't figure out how to use the nested structure.

CodePudding user response:

What you need here is to group items by collection.collectionId and then for each group look for an item with maximum value of media.episode_number. In Kotlin it can be done almost as it was said above:

items
    .groupBy { it.collection.collectionId }
    .values
    .map { group -> group.maxByOrNull { it.media.episode_number }!! }

Above solution creates intermediary collections that are not strictly necessary. We can improve the performance by calculating the max while grouping. Unfortunately, Kotlin does not provide a handy maxBy() function in this case, but we can create it by ourselves:

fun main() {
    val items = ...
    items
        .groupingBy { it.collection.collectionId }
        .maxBy { _, it -> it.media.episode_number }
        .values
}

fun <T, K, R : Comparable<R>> Grouping<T, K>.maxBy(selector: (K, T) -> R): Map<K, T> {
    return reduce { k, it1, it2 ->
        if (selector(k, it1) >= selector(k, it2)) it1 else it2
    }
}

CodePudding user response:

I found the solution:

list = list.sortedBy { it.collection.collectionId }.filter { item ->
                        item == list.first { it.collection.collectionId == item.collection.collectionId }
                    }

(turns out they are already sorted by episode in the correct order)

I think this is one of the fastest solutions, but feel free to share more

CodePudding user response:

A general solution that will work with a list of that type with any initial sorting, would be this:

list
    .groupBy { episode -> episode.collection.collectionId }
    .map { (_, episodeList) ->
        episodeList.maxByOrNull { episode -> episode.media.episodeNumber }
    }
    .filterNotNull()

This will create a map where the key is the collectionId and the value is a list of all the episodes with that collectionId. Then for each entry of the map it will select the episode with the highest episodeNumber.

  • Related