我有两个列表,其目的是通过使用一系列删除,移动和追加命令将list1
转换为list2
。
作为一个例子;给出以下列表
list1 = ['A','B','C']
list2 = ['B','A','D','C']
要发出的命令是:
commands = [
'append D',
'move D,C', #move D over C
'move B,A' #move B over A
]
我已经编写了一个程序,以非常强制的方式在c#中执行此操作。我为每个不在list2中的项发出删除命令。我为list1中没有的每个项发出append命令。在计算移动命令时,我依次从list2中取出一对项目,如果list1中的顺序不同,则发出移动命令。
我的问题是我有兴趣用F#或类似语言编写另一种解决方案。由于我几乎没有函数式编程的经验,所以无法解决如何处理这个问题。
您如何以功能性的方式解决这个问题?
编辑:我的解决方案
var oldList = new List<char>() { 'A', 'B', 'C', 'D', 'E' };
var newList = new List<char>() { 'C', 'E', 'B', 'D' };
var deletes = oldList.Except(newList).ToList();
var appends = newList.Except(oldList).ToList();
var commands = new List<string>();
commands.AddRange(deletes.Select(x => String.Format("DELETE {0}", x)));
commands.AddRange(appends.Select(x => String.Format("APPEND {0}", x)));
var tmpList = new List<char>(oldList);
tmpList.RemoveAll(deletes.Contains);
tmpList.AddRange(appends);
bool changed = true;
while (changed)
{
changed = false;
for (int i = 0; i < newList.Count-1; i++)
{
char current = newList[i];
char next = newList[i+1];
int currentIndex = tmpList.IndexOf(current);
int nextIndex = tmpList.IndexOf(next);
if (currentIndex <= nextIndex) // same ordering, so continue
continue;
commands.Add(String.Format("MOVE {0}, {1}", current, next)); // move current over next
tmpList.RemoveAt(currentIndex);
tmpList.Insert(nextIndex, current);
changed = true;
}
}
Console.WriteLine(String.Join(", ", oldList.ToArray()));
Console.WriteLine(String.Join(", ", newList.ToArray()));
Console.WriteLine(String.Join(", ", tmpList.ToArray()));
Console.WriteLine(String.Join("\r\n", commands.ToArray()));
答案 0 :(得分:1)
首先,为了更容易使用,您应该使用被破坏的联合来表示命令,以便您可以轻松地执行它们。此外,由于F#列表是链接列表,因此使用prepend而不是append将更好地实际执行。
type Operation<'a> =
| Delete of 'a
| Prepend of 'a
| Swap of 'a * 'b
我们很快就会需要函数来将项目存在于一个列表而不是另一个列表中让我们称之为-
:
let (-) list1 list2 =
List.filter
(fun x -> not (List.exists ((=) x) list2))
list1
大。这使我们能够编写一个函数,该函数接受两个列表,并返回从第一个中删除所有额外元素以获取第二个元素所需的删除列表,以及执行相同但前置操作的函数。
let deletionOps list1 list2 = (list1 - list2) |> List.map Delete
let prependOps list1 list2 = (list2 - list1) |> List.map Prepend
显而易见的下一步是编写一个函数,为我们提供从一个列表到另一个列表的所有必要的交换。首先,我们需要一个可以实际交换列表中项目的函数。满足需求的简单方法可能如下所示:
module List
/// Replaces all instances of a with b, and vice versa
let swap a b l =
l |> List.map (fun x ->
if e = a then b
elif e = b then a
elif e)
现在,这是有趣的部分,编写swapOps函数,告诉我们从一个列表到另一个列表的必要交换:
let rec swapOps listIn listOut =
match listIn, listOut with
| _, [] -> []
| [], _ -> []
| a :: listIn, b :: listOut when a = b -> swapOps listIn listOut
| a :: listIn, b :: listOut -> Swap(a, b) :: swapOps (List.swap a b listIn) listOut
几乎就在那里!我们需要功能来实际执行这些操作。这是编写它们的一种方法:
let applyOp list op =
match op with
| Delete x -> List.filter ((<>) x) list
| Prepend x -> x :: list
| Swap(a, b) -> List.swap a b list
let rec applyOps list ops =
match ops with
| [] -> list
| op :: restOps -> applyOps (applyOp list op) restOps
最后,为了将它们组合在一起,您可以编写一些代码来获取两个列表并返回执行操作,这些操作在执行时将一个列表转换为另一个列表(假设没有重复的元素)。并不是说我的代码肯定可以改进以减少重复:
let ops listIn listOut =
let dOps = deleteOps listIn listOut
let listIn = applyOps listIn dOps
let pOps = prependOps listIn listOut
let listIn = applyOps listIn pOps
let sOps = swapOps listIn listOut
dOps @ pOps @ sOps
您可以使用之前的applyOps
功能对此进行验证。
答案 1 :(得分:0)
您可以使用“折叠”来表达这一点,该“折叠”一次一个地将更新应用于输入列表,每次生成新的输出列表而不是更改现有列表。
例如,在F#中,您可能首先为命令定义代数数据类型:
type Command =
| Append of char // append this char to the list
| Move of char * char // move the first char over the second char
然后编写一个函数将命令应用于列表:
let applyCommand list command =
match command with
| Append c -> list@[c] // note that appending at the end is quite inefficient
| Move (c1, c2) -> ...
然后最后使用折叠将所有命令应用到列表中:
List.fold applyCommand list1 commands