Swift算法-行程编码Run-Length Encoding (RLE) 压缩与解压缩

声明:算法和数据结构的文章均是作者从github上翻译过来,为方便大家阅读。如果英语阅读能力强的朋友,可以直接到swift算法俱乐部查看所有原文,以便快速学习。作者同时也在学习中,欢迎交流

在编程过程中,我们可能会遇到某些情况,需要我们压缩数据,而行程编码(RLE)可以说是最简单的压缩算法。假定我们有需要压缩的数据如下:

aaaaabbbcdeeeeeeef...

经过行程编码压缩后:

5a3b1c1d7e1f...

原始数据中,不断重复出现的字母变成了其出现次数以及字母本身。即5a便是aaaaa。如果数据里面出现很多类似情况,那么RLE算法将可以节省很多空间。特别是对于图像数据。

我们有很多种不同的方法可以实现RLE。下面要介绍的方法是通过对Data进行拓展的一个版本,灵感来源于老式的PCX图像格式

具体规则如下:
1.对于每一个字节来说,当某一字节重复出现多次,我们用两个字节来表示这种情况--第一个字节记录这个字节重复出现的次数,第二个字节记录这个字节本身。第一个字节表示的格式为:191+count所以这里的count最多只能为64个重复字节的长度。(255-191)
2.在区间0-191的单一字节,不需要压缩,可以直接复制
3.在区间192-255的单一字节,用两个字节来表示,第一个字节为192,意思是这个字节只出现一次,第二个字节为其真实值,也就是字节本身。

以下为压缩算法的代码。算法最后返回的是一个经过RLE处理的Data对象。

extension Data {
    public func compressRLE() -> Data {
        var data = Data()
        self.withUnsafeBytes { (uPtr: UnsafePointer<UInt8>) in
            var ptr = uPtr
            let end = ptr + count
            while ptr < end {                       //1
                var count = 0
                var byte = ptr.pointee
                var next = byte

                while next == byte && ptr < end && count < 64 { //2
                    ptr = ptr.advanced(by: 1)
                    next = ptr.pointee
                    count += 1
                }

                if count > 1 || byte >= 192 {       // 3
                    var size = 191 + UInt8(count)
                    data.append(&size, count: 1)
                    data.append(&byte, count: 1)
                } else {                            // 4
                    data.append(&byte, count: 1)
                }
            }
        }
        return data
    }

代码解析:

1.使用UnsafePointer来逐一查看原始Data对象的每一个字节
2.在这个步骤中,我们已经读到当前字节的数值,并赋值到byte变量里。如果下一个字节是一样的,我们会一直继续读取新数值,直到我们发现不一样的新数值,或者完全所有数据读取。当前,如果某一字节重复出现64次,我们也需要停止,因为这是我们可以进行编码的最大值。
3.第三步中,我们需要决定刚刚读取到的字节的编码方式。第一种可能性是我们刚刚读到的字节,重复出现次数大于1次,当然最大值为64。在这种情况我们需要写出两个字节,即长度+字节本身。 第二种可能性为我们读取到的字节大于191,这种情况也需要用两个字节来编码。
4.第四步中表示的是第三种可能性,也就是读取到的字节小于192.此时我们只需要复制此字节即可。

我们可以在playground中用以下代码进行测试:

let originalString = aaaaabbbcdeeeeeeef
let utf8 = originalString.data(using: String.Encoding.utf8)!
let compressed = utf8.compressRLE()

此时被压缩后的Data对象为<c461c262 6364c665 66>。解析过程如下:

c4    十进制为196. 表示下一个字节会重复出现五次
61    表示字节 "a".
c2    下一个字节重复出现3次.
62    表示字节 "b".
63    表示字节 "c". 因为 < 192, 所以是单一字节.
64    表示字节 "d". 同上,只出现一次.
c6    下一个字节出现7次.
65    表示字节 "e".
66    表示字节 "f". 只出现一次.

压缩后的数据为9个字节,而原始数据为18个字节。节约了50%。当然,这里只是举了一个简单的例子。如果你刚好非常不幸运,遇到一组原始数据没有任何重复又都大于191,那你可能将会得到一个2倍于原始数据的压缩结果。所以,次算法的效率与输入的原始数据相关性很大。

以下为解压缩的代码:

public func decompressRLE() -> Data {
        var data = Data()
        self.withUnsafeBytes { (uPtr: UnsafePointer<UInt8>) in
            var ptr = uPtr
            let end = ptr + count

            while ptr < end {
                // Read the next byte. This is either a single value less than 192,
                // or the start of a byte run.
                var byte = ptr.pointee                          // 1
                ptr = ptr.advanced(by: 1)

                if byte < 192 {                     // 2
                    data.append(&byte, count: 1)
                } else if ptr < end {               // 3
                    // Read the actual data value.
                    var value = ptr.pointee
                    ptr = ptr.advanced(by: 1)

                    // And write it out repeatedly.
                    for _ in 0 ..< byte - 191 {
                        data.append(&value, count: 1)
                    }
                }
            }
        }
        return data
    }

代码解析:

  1. 再一次利用UnsafePointer读取Data,这里有可能是一个数值小于192单一字节,也有可能是下一个字节需要重复出现的次数。
    2.判断结果为单一字节,直接复制到输出数据里
    3.判断结果为下一个字节的出现次数,立刻读取下一个字节的数值,并根据次数添加到输出数据里

将被压缩的数据还原,你需要用以下命令:

let decompressed = compressed.decompressRLE()
let restoredString = String(data: decompressed, encoding: NSUTF8StringEncoding)

此时originalString == restoredString返回肯定为true!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,849评论 6 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 单机存储引擎就是哈希表、B树等数据结构在机械磁盘、SSD等持久化介质上的实现。单机存储系统是单机存储引擎的一种封装...
    olostin阅读 2,382评论 0 5
  • 知道吗?和脸盲一样,我成了标题盲。比如前几日,打开任何一款平台,扑面而来的都是—— 刘鑫刘鑫刘鑫江歌歌江歌刘鑫刘鑫...
    何以故哉阅读 1,084评论 2 6
  • 尽管周媚儿表面上对叶蛮很好,大方豁达地为叶蛮添置任何东西;一副以夫为天,贤良淑德的女主人模样。但暗地里,可不忘对叶...
    清薇忆阅读 251评论 0 0