From 0a1a54d28d1824e3652f6dc968c8282a8ec4b6a0 Mon Sep 17 00:00:00 2001 From: Stephen Kitt Date: Thu, 9 May 2019 18:38:34 +0200 Subject: [PATCH] Smart shuffle: spread episodes evenly This reworks the sort algorithm used in smart shuffle so that episodes are spread out evenly, which avoids episodes bunching up at the bottom of the queue when one feed has more episodes than others, and avoids running through feeds with few episodes very quickly. Signed-off-by: Stephen Kitt --- .../antennapod/core/util/QueueSorter.java | 60 +++++++++++-------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java index 5c827dfe9..8680b2d2e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/QueueSorter.java @@ -104,13 +104,10 @@ public class QueueSorter { * prefer a more balanced ordering that avoids having to listen to clusters of consecutive * episodes from the same feed. This is what "Smart Shuffle" tries to accomplish. * - * The Smart Shuffle algorithm involves choosing episodes (in round-robin fashion) from a - * collection of individual, pubdate-sorted lists that each contain only items from a specific - * feed. - * - * Of course, clusters of consecutive episodes at the end of the queue may be - * unavoidable. This seems unlikely to be an issue for most users who presumably maintain - * large queues with new episodes continuously being added. + * The Smart Shuffle algorithm involves spreading episodes from each feed out over the whole + * queue. To do this, we calculate the number of episodes in each feed, then a common multiple + * (not the smallest); each episode is then spread out, and we sort the resulting list of + * episodes by "spread out factor" and feed name. * * For example, given a queue containing three episodes each from three different feeds * (A, B, and C), a simple pubdate sort might result in a queue that looks like the following: @@ -152,8 +149,17 @@ public class QueueSorter { ? (f1, f2) -> f1.getPubDate().compareTo(f2.getPubDate()) : (f1, f2) -> f2.getPubDate().compareTo(f1.getPubDate()); - for (Long id : map.keySet()) { - Collections.sort(map.get(id), itemComparator); + // Calculate the spread + + long spread = 0; + for (Map.Entry> mapEntry : map.entrySet()) { + List feedItems = mapEntry.getValue(); + Collections.sort(feedItems, itemComparator); + if (spread == 0) { + spread = feedItems.size(); + } else if (feedItems.size() % spread != 0){ + spread *= feedItems.size(); + } } // Create a list of the individual FeedItems lists, and sort it by feed title (ascending). @@ -161,25 +167,29 @@ public class QueueSorter { List> feeds = new ArrayList<>(map.values()); Collections.sort(feeds, - // (we use a desc sort here, since we're iterating back-to-front below) - (f1, f2) -> f2.get(0).getFeed().getTitle().compareTo(f1.get(0).getFeed().getTitle())); + (f1, f2) -> f1.get(0).getFeed().getTitle().compareTo(f2.get(0).getFeed().getTitle())); - // Cycle through the (sorted) feed lists in a round-robin fashion, removing the first item - // and adding it back into to the original queue - - while (!feeds.isEmpty()) { - // Iterate across the (sorted) list of feeds, removing the first item in each, and - // appending it to the queue. Note that we're iterating back-to-front here, since we - // will be deleting feed lists as they become empty. - for (int i = feeds.size() - 1; i >= 0; --i) { - List items = feeds.get(i); - queue.add(items.remove(0)); - // Removed the last item in this particular feed? Then remove this feed from the - // list of feeds. - if (items.isEmpty()) { - feeds.remove(i); + // Spread each episode out + Map> spreadItems = new HashMap<>(); + for (List feedItems : feeds) { + long thisSpread = spread / feedItems.size(); + // Starting from 0 ensures we front-load, so the queue starts with one episode from + // each feed in the queue + long itemSpread = 0; + for (FeedItem feedItem : feedItems) { + if (!spreadItems.containsKey(itemSpread)) { + spreadItems.put(itemSpread, new ArrayList<>()); } + spreadItems.get(itemSpread).add(feedItem); + itemSpread += thisSpread; } } + + // Go through the spread items and add them to the queue + List spreads = new ArrayList<>(spreadItems.keySet()); + Collections.sort(spreads); + for (long itemSpread : spreads) { + queue.addAll(spreadItems.get(itemSpread)); + } } }