我有两个列表:源列表和目标列表。两个列表都包含所有相同的项目,但列表的顺序不同。鉴于这两个列表,我需要在源列表上找到一系列交换操作,它们将列表中的一个项目与另一个项目交换,最终以与目标列表相同的顺序结束源列表。
我正在编写一个脚本,按照专辑对MPD播放列表进行洗牌,因为默认情况下此功能不在MPD中。该脚本当前获取当前播放列表(源列表),执行列表的自定义随机播放,最终得到新的歌曲排序(目的地列表)。该脚本然后从播放列表中删除所有项目,并按照新的,随机播放的播放列表的顺序将它们重新插入播放列表。删除和添加所有歌曲是一个缓慢的操作。 MPD库提供了播放列表中两首歌曲的更快就地交换,但我不知道如何找到正确的交换操作系列,以将源列表转换为新的混洗列表。
这是用Haskell编写的,但任何语言/伪代码的答案都可以。
答案 0 :(得分:2)
import Data.List
import Data.Maybe
orderBySecond :: Ord a => (a, a) -> (a, a) -> Ordering
orderBySecond (_, x1) (_, x2) = compare x1 x2
-- Gets the position in xs of elements in the second list (ys)
indices :: Eq a => [a] -> [a] -> [(Int, Int)]
indices xs ys = zip (map (\x -> fromJust $ x `elemIndex` xs) ys) [0 ..]
getSwapsfromIndices :: [(Int, Int)] -> [(Int, Int)]
getSwapsfromIndices xs = getSwapsfromIndices' xs []
-- The second argument for this is an accumulator used for tail recursion
getSwapsfromIndices' :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)]
getSwapsfromIndices' [] ys = ys
getSwapsfromIndices' xs ys = getSwapsfromIndices' xs' (ys ++ new_swap)
where (l1, l2) = minimumBy orderBySecond xs
-- remove minimum from the list
unordered = [ (x, y) | (x, y) <- xs, y /= l2]
-- swap
xs' = [ (if x == l2 then l1 else x, y) | (x, y) <- unordered]
-- if no swap is needed, do not append anything
new_swap = if l1 == l2 then [] else [(l1, l2)]
swaps :: Eq a => [a] -> [a] -> [(Int, Int)]
swaps xs ys = getSwapsfromIndices $ indices xs ys
通过上面的示例运行代码:
*Main> swap [2,3,4,1,7] [7,1,2,4,3]
[(4,0),(3,1),(4,2),(4,3)]
请注意,结果的唯一区别在于交换中的索引(这是常规问题)的顺序以及我从0开始计算元素的事实。
该实现使用对第一个列表中的元素强加总排序的想法,根据它们在第二个列表中的位置。然后使用Selection sort来获取交换。它可能不是最有效的解决方案,但很高兴为您提供一个良好的开端。
答案 1 :(得分:1)
一种简单的方法是只使用目的地列表顺序作为排序的总顺序。例如,使用索引顺序。那么指数的总顺序关系只有<
。
现在运行您最喜欢的,最有效的基于交换的排序算法,对第二个列表进行排序以符合第一个列表的总顺序。 (Quicksort浮现在脑海中。)每次排序进行交换时,按顺序记录该对。这个顺序就是你的答案。
这是一个小小的丢失C代码,向您展示我在说什么:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// A faux play list item.
struct list_item {
char name[9];
int index;
};
// Randomized quicksort that prints its swaps.
// Note this sorts on the 'index' field, which defines the total order.
void sort(struct list_item **a, int n)
{
if (n <= 1) return;
struct list_item *p = a[rand() % n];
int lo = 0;
int hi = n - 1;
while (lo <= hi) {
while (a[lo]->index < p->index) ++lo;
while (a[hi]->index > p->index) --hi;
if (lo < hi) {
// We need a swap! Print it!
printf("swap %s and %s\n", a[hi]->name, a[lo]->name);
struct list_item *t = a[lo];
a[lo] = a[hi];
a[hi] = t;
++lo;
--hi;
}
else if (lo == hi) {
++lo;
--hi;
}
}
sort(a, hi + 1);
sort(a + lo, n - lo);
}
// Make an array of pointers to simulated play list items.
struct list_item **make_list(int n)
{
int j;
struct list_item **a = malloc(n * sizeof(struct list_item *));
char x[9] = "a";
for (int i = 0; i < n; i++) {
a[i] = malloc(sizeof(struct list_item));
strcpy(a[i]->name, x);
for (j = 0; x[j] == 'z'; j++)
x[j] = 'a';
x[j] = x[j] ? x[j] + 1 : 'a';
}
return a;
}
// Randomize a list of pointers.
void randomize_list(struct list_item **a, int n)
{
for (int i = 0; i < n - 1; i++) {
int r = i + rand() % (n - i);
struct list_item *t = a[r];
a[r] = a[i];
a[i] = t;
}
}
// Test case size.
#define N 7
int main(void)
{
// Make a nice test destination list..
struct list_item **dst = make_list(N);
// Make a copy of the pointers and shuffle them to make the source list.
struct list_item **src = malloc(N * sizeof(struct list_item *));
memcpy(src, dst, N * sizeof(struct list_item *));
randomize_list(src, N);
// Print the source to see where we're starting.
for (int i = 0; i < N; i++)
printf("%d: %s\n", i + 1, src[i]->name);
// Define the total order to be the destination's index order.
for (int i = 0; i < N; i++)
dst[i]->index = i;
// Sort the source to duplicate the destination.
// Swaps printed above will get the job done.
sort(src, N);
return 0;
}
长度为7的列表的结果:
1: g
2: a
3: b
4: d
5: c
6: f
7: e
swap e and g
swap c and e
swap a and c
swap b and c
如果你进行这些交换,结果就像你期望的那样按顺序排列。
请注意,QuickSort非常适合最小化比较。 This page表示,选择排序(需要最多O(n ^ 2)次比较)最小化掉期数,至少在渐近最坏情况意义上。它最多需要n-1次互换。事实上,当我在100个项目上尝试使用QuickSort时,需要进行156次交换,因此选择排序会更好。
答案 2 :(得分:1)
我想出了以下丑陋的代码。这个想法类似于基于交换的排序技术。假设你有两个列表
[7,1,2,4,3]和[2,3,4,1,7]
现在您可以一次获得一个项目。首先得到第一个元素是正确的,我已经提到了交换作为在列表中交换的索引对,然后是应用交换后获得的列表
(1,5)=&gt; [7,3,4,1,2]
(2,4)=&gt; [7,1,4,3,2]
(3,5)=&gt; [7,1,2,3,4]
(4,5)=&gt; [7,1,2,4,3]
交换是
[(1,5),(2,4),(3,5),(4,5)]
import qualified Data.Map as M
import Data.Maybe
-- It will totally break if lists don't contain same items.
swaps :: Ord a => [a] -> [a] -> [(Int,Int)]
swaps xs ys = reverse . getFirst $ foldl f ([],xsm,mxs,1) ys
where
getFirst (a,_,_,_) = a
xsm = M.fromList $ zip xs ([1..]) -- Construct map for O(logn) lookups
mxs = M.fromList $ zip ([1..]) xs -- Map for Reverse lookup
f (swps,xm,mx,i) y = if i==a then (swps,newxm,newmx,i+1)
else ((i,a):swps,newxm,newmx,i+1)
where
a = fromJust $ M.lookup y xm -- Not very safe to use fromJust
b = fromJust $ M.lookup i mx
newxm = M.insert b a $ M.insert y i xm
newmx = M.insert a b $ M.insert i y mx
在ghci
*Main> swaps [2,3,4,1,7] [7,1,2,4,3]
[(1,5),(2,4),(3,5),(4,5)]