之前我提出了一个“趣味编程”,模仿Haskell的方式对一个链表进行快速排序。在那篇文章中我解释了Haskell列表的结构,并给出了ImmutableList的基础实现。快速排序的例子很多,多做也没有意思。这题虽然打着“快速排序”的旗帜,但事实上这里的关键在于实现ImmutableList数据结构的相关操作——否则为什么叫“函数式链表”的快速排序呢?。

一开始,我提出使用null来表示一个空链表,不过RednaxelaFX指出应该使用Null Object Pattern来表现。因此我修改了文章的代码示例。不过后来RednaxelaFX同学指出,其实我的做法并没有按照他的理解进行。我也认为他的做法更确切一些,因此也对代码进行了修改。于是这次我给出的“参考答案”,便参考了他的部分实现方式。

ImmutableList结构的定义如下:

public abstract class ImmutableList<T> : IEnumerable<T>
{public abstract T Head { get; }public abstract ImmutableList<T> Tail { get; }public abstract bool IsEmpty { get; }#region IEnumerable<T> Memberspublic IEnumerator<T> GetEnumerator(){var current = this;while (!current.IsEmpty){yield return current.Head;current = current.Tail;}}#endregion#region IEnumerable MembersIEnumerator IEnumerable.GetEnumerator(){return this.GetEnumerator();}#endregion
}

与上一篇文章给出的定义不同,ImmutableList<T>被定义为一个抽象类,包含一个ImmutableList的成员抽象与相关方法。ImmutableList抽象类有两个具体实现:分别表示空链表(EmptyImmutableList)和非空链表(NonEmptyImmutableList)。两种实现都非常简单:

public abstract class ImmutableList<T> : IEnumerable<T>
{public static readonly ImmutableList<T> Empty = new EmptyImmutableList<T>();    ...
}internal sealed class EmptyImmutableList<T> : ImmutableList<T>
{public EmptyImmutableList() { }public override T Head{get{throw new InvalidOperationException("an empty list has no head.");}}public override ImmutableList<T> Tail{get{throw new InvalidOperationException("an empty list has no tail.");}}public override bool IsEmpty { get { return true; } }
}internal class NonEmptyImmutableList<T> : ImmutableList<T>
{public NonEmptyImmutableList(T head, ImmutableList<T> tail){this.m_head = head;this.m_tail = tail;}private T m_head;public override T Head { get { return this.m_head; } }private ImmutableList<T> m_tail;public override ImmutableList<T> Tail { get { return this.m_tail; } }public override bool IsEmpty { get { return false; } }
}

EmptyImmutableList实现了ImmutableList,并让其Head和Tail属性都抛出异常。在整个ImmutableList体系中,EmptyImmutableList和NonEmptyImmutableList类都是对外部隐藏的(因此被定义为internal),用户可以操作的唯独只是ImmutableList抽象类。在这里,为了方便起见,我们定义了一个全局唯一的EmptyImmutableList对象,放在ImmutableList的静态只读属性Empty中,在任何需要使用“空链表”的情况下,都不需要重新生成EmptyImmutableList类。

接下来便是关键了,我们需要创建一些ImmutableList在快速排序中所必要的操作。例如,构建一个链表,其中只包含一个元素:

public abstract class ImmutableList<T> : IEnumerable<T>
{public static readonly ImmutableList<T> Empty = new EmptyImmutableList<T>();public static ImmutableList<T> Create(T element){return new NonEmptyImmutableList<T>(element, Empty);}
}

请注意,由于ImmutableList是唯一对外公开的接口,因此实际上这些方法都是定义在ImmutableList上的。那么再想想,快速排序还需要什么操作?没错,就是两个链表的连接操作,这里我把它定义为“+”操作:

public static ImmutableList<T> operator +(ImmutableList<T> headList, ImmutableList<T> tailList)
{if (headList == null) throw new ArgumentNullException("headList");if (tailList == null) throw new ArgumentNullException("tailList");if (headList.IsEmpty) return tailList;if (tailList.IsEmpty) return headList;...
}

这里我们要想一下,把两个链表连接起来之后结果是怎么样的?假设有list1和list2两个链表:

1 -> 2 -> 3↖ list1
4 -> 5 -> 6↖ list2

如果执行result = list1 + list2,那么最终的结果是什么呢?应该是这样的:

1 -> 2 -> 3↖ list1
1 -> 2 -> 3 -> 4 -> 5 -> 6↖ result      ↖ list2

由于链表是不可变的,因此我们必须把list1的所有元素复制为result的前部,但是list2完全可以直接作为另一个链表的尾部。因此,最后两个链表连接后的结果会像上面所示。在这里,我们可以使用这样的代码完成这个功能:

public abstract class ImmutableList<T> : IEnumerable<T>
{...public static ImmutableList<T> Create(params T[] elements){if (elements == null) throw new ArgumentNullException("elements");return Create(elements, Empty);}public static ImmutableList<T> Create(IEnumerable<T> elements){if (elements == null) throw new ArgumentNullException("elements");return Create(elements, Empty);}private static ImmutableList<T> Create(IEnumerable<T> elements, ImmutableList<T> tail){return NonEmptyImmutableList<T>.FromEnumerable(elements, tail);}public static ImmutableList<T> operator +(ImmutableList<T> headList, ImmutableList<T> tailList){...return Create(headList, tailList);}...
}internal class NonEmptyImmutableList<T> : ImmutableList<T>
{public static ImmutableList<T> FromEnumerable(IEnumerable<T> elements, ImmutableList<T> tail){NonEmptyImmutableList<T> result = null, last = null;foreach (var item in elements){var newNode = new NonEmptyImmutableList<T>(item, Empty);if (last == null){result = last = newNode;}else{last.m_tail = newNode;last = newNode;}}if (last != null){last.m_tail = tail;}return result ?? tail;}...
}

具体的实现方式是NonEmptyImmutableList的FromEnumerable方法。这个方法有些“特别”,因为它直接修改了m_tail私有变量的值。这是因为,由于传入的IEnumerable是单向的,但是构造一个链表时,使用“正常操作”只能“从后往前”构造。这里NonEmptyImmutableList算是“以权谋私”了一回,利用职权便利提高了“性能”。

ImmutableList的连接操作实现完成后,我们便可以为其添加一些额外的辅助方法了。如Filter:

public static class ImmutableListExtensions
{public static ImmutableList<T> Filter<T>(this ImmutableList<T> source, Func<T, bool> predicate){if (source == null) throw new ArgumentNullException("source");if (predicate == null) throw new ArgumentNullException("predicate");if (source.IsEmpty) return source;return ImmutableList<T>.Create(source.Where(predicate));}...
}

有了Filter之后,QuickSort便是轻而易举的:

public static class ImmutableListExtensions
{...public static ImmutableList<T> QuickSort<T>(this ImmutableList<T> list, Func<T, T, int> compare){if (list == null) throw new ArgumentNullException("list");if (compare == null) throw new ArgumentNullException("compare");return list.IsEmpty ? list :list.Tail.Filter(e => compare(e, list.Head) < 0).QuickSort(compare) +ImmutableList<T>.Create(list.Head) +list.Tail.Filter(e => compare(e, list.Head) >= 0).QuickSort(compare);}
}

看一下,是不是和Haskell代码的含义非常接近?

qsort [] = []
qsort (x:xs) = qsort (filter (< x) xs) ++ [x] ++ qsort (filter (>= x) xs)

使用一下:

var random = new Random();
var source = new List<int>();
for (int i = 0; i < 10; i++) source.Add(random.Next());var sourceList = ImmutableList<int>.Create(source);
var sortedList = sourceList.QuickSort((x, y) => x - y);foreach (var i in sourceList) Console.WriteLine(i);
Console.WriteLine();
foreach (var i in sortedList) Console.WriteLine(i);

至此,您有什么疑问吗?本文所有的代码可以访问http://gist.github.com/179520。如果您感兴趣的话,可以再试试看以下两个问题。

  • ImmutableList的快速排序已经完成了,那么您是否可以尝试着进行归并排序(Merge Sort)?
  • 能否模拟ImmutableList构造一个ImmutableStack?我不是指“封装ImmutableList”,而是直接编写一个ImmutableStack,需要实现Pop,Push和Peek三种操作。
  • from: http://blog.zhaojie.me/2009/09/functinal-list-quick-sort-answer.html

趣味编程:函数式链表的快速排序(参考答案)相关推荐

  1. 趣味编程:函数式链表的快速排序

    前一段时间有朋友问我,以下这段Haskell快速排序的代码,是否可以转化成C#中等价的Lambda表达式实现: qsort [] = [] qsort (x:xs) = qsort (filter ( ...

  2. python在线作业_南开大学20春学期《Python编程基础》在线作业参考答案

    南开大学20春学期(1709.1803.1809.1903.1909.2003)<Python编程基础>在线作业 试卷总分:100 得分:98 一.单选题(共20 道试题,共40 分) 1 ...

  3. python一级考试试题题库_Python编程一级试卷一及参考答案

    青少年编程能力等级测评试卷 Python编程(一级) (考试时间90分钟,满分100分) 一.单项选择题(共20题,每题2.5分,共50分) 1. 运行下方代码段,输出的是( D ). print(& ...

  4. 魔法王国java_网易2018校园招聘面试编程题真题与参考答案集合

    [编程题] 魔法币 小易准备去魔法王国采购魔法神器,购买魔法神器需要使用魔法币,但是小易现在一枚魔法币都没有,但是小易有两台魔法机器可以通过投入x(x可以为0)个魔法币产生更多的魔法币. 魔法机器1: ...

  5. 趣味编程:从字符串中提取信息(参考答案 - 下)

    昨天我们观察了如何使用基于状态机的顺序解析方式来提取字符串中的信息,不过由于winter-cn的做法和我原始的想法不谋而合,但实现的更为清晰,因此我在不献丑的同时,又设法使用另外一种方式来解决这个问题 ...

  6. 趣味编程:从字符串中提取信息(参考答案 - 上)

    这次"趣味编程"的目的是解析字符串,从一个指定模式的字符串中提取信息.对于目前这个问题,解决方案有很多种,例如直接拆分,使用正则表达式,或是如现在本文这般按照顺序解析.总结果上来说 ...

  7. python趣味编程从入门到人工智能答案-趣味编程挑战:从Python入门到AI应用

    商品详情 书名:趣味编程挑战:从Python入门到AI应用 定价:69.0 ISBN:9787121363177 作者:孙勇 版次:第1版 出版时间:2020-04 内容提要: 本书是趣味编程的入门教 ...

  8. c语言编程 验证用户名和密码是否正确(函数定义),《C语言程序设计教程》习题参考答案[精品资料].doc...

    <C语言程序设计教程>习题参考答案[精品资料] <C语言程序设计教程>习题参考答案 默认分类 2007-09-10 12:38:44 阅读6618 评论13 ??字号:大中小? ...

  9. 青少年软件编程(202209)(C语言)(数据结构)等级考试(六级)试题及参考答案

    等级标准 掌握数据结构及结构的概念: 掌握数据结构中的指针和链表: 掌握数据结构中的栈: 掌握数据结构中的队列: 掌握数据结构中的哈希: 能够使用上述方法编写指定功能的正确完整的程序. stack o ...

最新文章

  1. 从春晚说起:总有一种无耻让我们泪流满面
  2. 为什么单片机通常只有那么小的数据内存?
  3. C#以post方式调用struts rest-plugin service的问题
  4. Java基础笔记-异常
  5. java css是什么_Java 之 CSS
  6. mysq show 指令
  7. OpenShift 4 - 用容器提升MySQL的可用性
  8. pythonm 用法-------list实现购物车
  9. linux代码实现进程监控,linux进程监控shell脚本代码
  10. 仙人掌真的会防辐射吗
  11. 日订单量达到100万单后,我们做了订单中心重构
  12. BooT模式: ISP、Flash、SRAM
  13. 动态高斯模糊 surfaceView View
  14. 【docker专栏5】详解docker镜像管理命令
  15. 蓝筹股获利回吐,13000点得而复失
  16. 开发一个系统软件开发大概需要多少资金钱呢
  17. 量化交易学习系列13-系统交易的职业道路是什么
  18. 唐骏:先做人再做事偶尔作作秀
  19. CSS特殊样式(三)纯CSS实现各类气球泡泡对话框效果
  20. mysql 数据库备份 乱码_再谈 MySQL 数据库备份恢复和乱码问题

热门文章

  1. 四、【线性表】线性表的顺序表示和实现
  2. Java 8 - 02 Lambda Expression
  3. Spring-注入参数详解-[通过util命名空间简化集合类型的配置]
  4. cookie获取java_java中如何获取cookie
  5. java消息通信_原生 Java 客户端进行消息通信
  6. 学习笔记——使用下划线命名的规则
  7. 小米算法题判断直线相交
  8. blockingqueue java_记录 Java 的 BlockingQueue 中的一些坑
  9. c语言中go的作用,go语言与c语言的相互调用
  10. echarts formatter_手把手教你玩转echarts(二)折线图