如何交换项目以将一个列表转换为另一个列表

时间:2012-12-31 04:34:30

标签: arrays haskell pseudocode

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

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

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

3 个答案:

答案 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)]