【问题标题】:How to swap items in place to transform one list in to another如何交换项目以将一个列表转换为另一个
【发布时间】:2012-12-15 08:53:01
【问题描述】:

我有两个列表:源列表和目标列表。两个列表都包含所有相同的项目,但列表的顺序不同。鉴于这两个列表,我需要在源列表上找到一系列交换操作,将列表中的一个项目与另一个项目交换,最终以与目标列表相同的顺序结束源列表。

我正在编写一个脚本,按专辑随机播放 MPD 播放列表,因为默认情况下 MPD 中没有此功能。该脚本当前获取当前播放列表(源列表),执行列表的自定义随机播放,并以新的歌曲顺序(目标列表)结束。然后,该脚本会从播放列表中删除所有项目,并按照新的打乱播放列表的顺序将它们重新插入到播放列表中。删除和添加所有歌曲是一项缓慢的操作。 MPD 库为播放列表中的两首歌曲提供了更快的就地交换,但我不知道如何找到正确的一系列交换操作来将源列表转换为新的随机列表。

这是用 Haskell 编写的,但任何语言/伪代码的答案都可以。

【问题讨论】:

  • MPD 是否只允许按索引交换相邻元素或任意元素对?如果是后者,索引数组上的 Knuth-Fisher-Yates 洗牌会做得很好。
  • 算法的输入是什么?你有交换前后的初始和最终索引吗?
  • @DanielFischer:mpd.wikia.com/wiki/MusicPlayerDaemonCommands#swap 按索引表示任意元素对。所以,这听起来对我来说是一个很好的答案。 :-)

标签: arrays haskell pseudocode


【解决方案1】:
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 开始计算元素这一事实。

这个实现使用了对第一个列表中的元素施加总排序的想法,根据它们在第二个列表中的位置。然后它使用选择排序来获得交换。这可能不是最有效的解决方案,但可以让您领先一步。

【讨论】:

  • 这行得通,谢谢!使用交换而不是删除/添加已将运行时间减半。
【解决方案2】:

一种简单的方法是只使用目标列表顺序作为排序的总顺序。例如,使用索引顺序。那么总的订单关系就是索引上的&lt;。

现在运行您最喜欢的、最有效的基于交换的排序算法,对第二个列表进行排序以符合第一个列表的总顺序。 (想到快速排序。)每次排序进行交换时,按顺序记录这对。这个序列就是你的答案。

这里有一些一次性的 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

如果您进行这些交换,结果是按顺序从 a 到 g,正如您所期望的那样。

请注意,快速排序非常适合最小化比较。 This page 说选择排序(最多需要 O(n^2) 次比较)最小化交换次数,至少在渐近最坏情况下是这样。它最多需要 n-1 次交换。事实上,当我在 100 个项目上尝试快速排序时,它需要 156 次交换,所以选择排序会更好。

【讨论】:

    【解决方案3】:

    我想出了以下丑陋的代码。这个想法类似于基于交换的排序技术。假设你有两个列表

    [7,1,2,4,3] 和 [2,3,4,1,7]

    现在您可以一次获得一项交换。首先让第一个元素正确,我已经提到交换作为一对索引在列表中交换,然后是应用交换后获得的列表

    (1,5) => [7,3,4,1,2]

    (2,4) => [7,1,4,3,2]

    (3,5) => [7,1,2,3,4]

    (4,5) => [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)]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-24
      • 1970-01-01
      • 2022-11-29
      • 1970-01-01
      • 2012-08-22
      相关资源
      最近更新 更多