Avatar of milowyner

milowyner's solution

to Run Length Encoding in the Swift Track

Published at Jul 22 2019 · 0 comments
Instructions
Test suite
Solution

Implement run-length encoding and decoding.

Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count.

For example we can represent the original 53 characters with only 13.

"WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"  ->  "12WB12W3B24WB"

RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression.

"AABCCCDEEEE"  ->  "2AB3CD4E"  ->  "AABCCCDEEEE"

For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character.

Setup

Go through the project setup instructions for Xcode using Swift:

http://exercism.io/languages/swift
http://exercism.io/languages/swift/tests

Notably from the source directory:

swift test runs tests
swift package generate-xcodeproj creates an Xcode project

Source

Wikipedia https://en.wikipedia.org/wiki/Run-length_encoding

Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

LinuxMain.swift

import XCTest
@testable import RunLengthEncodingTests

XCTMain([
    testCase(RunLengthEncodingTests.allTests),
    ])

RunLengthEncodingTests.swift

import XCTest
@testable import RunLengthEncoding

class RunLengthEncodingTests: XCTestCase {
    func testEncodeSimple() {
        XCTAssertEqual(RunLengthEncoding.encode("AABBBCCCC"), "2A3B4C")
    }

    func testDecodeSimple() {
        XCTAssertEqual(RunLengthEncoding.decode("2A3B4C"), "AABBBCCCC")
    }

    func testEncodeWithSingleValues() {
        XCTAssertEqual(RunLengthEncoding.encode("WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB"), "12WB12W3B24WB")
    }

    func testDecodeWithSingleValues() {
        XCTAssertEqual(RunLengthEncoding.decode("12WB12W3B24WB"), "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB")
    }

    func testDecodeEncodeCombination() {
        XCTAssertEqual(RunLengthEncoding.decode(RunLengthEncoding.encode("zzz ZZ  zZ")), "zzz ZZ  zZ")
    }

    func testEncodeUnicode() {
        XCTAssertEqual(RunLengthEncoding.encode("⏰⚽⚽⚽⭐⭐⏰"), "⏰3⚽2⭐⏰")
    }

    func testDecodeUnicode() {
        XCTAssertEqual(RunLengthEncoding.decode("⏰3⚽2⭐⏰"), "⏰⚽⚽⚽⭐⭐⏰")
    }

    static var allTests: [(String, (RunLengthEncodingTests) -> () throws -> Void)] {
        return [
            ("testEncodeSimple", testEncodeSimple),
            ("testDecodeSimple", testDecodeSimple),
            ("testEncodeWithSingleValues", testEncodeWithSingleValues),
            ("testDecodeWithSingleValues", testDecodeWithSingleValues),
            ("testDecodeEncodeCombination", testDecodeEncodeCombination),
            ("testEncodeUnicode", testEncodeUnicode),
            ("testDecodeUnicode", testDecodeUnicode),
        ]
    }
}
//Solution goes in Sources

import Foundation

struct RunLengthEncoding {
    static func encode(_ string: String) -> String {
        var currentCharacter: Character?
        var count: Int = 1
        var encodedString = ""
        
        for character in string {
            
            if let current = currentCharacter {
                if character == current {
                    count += 1
                } else {
                    // Add count and character to encoded string
                    encodedString += count == 1 ? "" : "\(count)"
                    encodedString.append(current)
                    // Set new current character
                    currentCharacter = character
                    count = 1
                }
            } else {
                // Set new current character
                currentCharacter = character
                count = 1
            }
        }
        
        // Add final count and character to encoded string
        encodedString += count == 1 ? "" : "\(count)"
        if let current = currentCharacter {
            encodedString.append(current)
        }
        
        return encodedString
    }
    
    static func decode(_ string: String) -> String {
        var decodedString = ""
        var countString = ""
        var currentLetter: Character?
        
        for character in string {
            
            if Int(String(character)) != nil {
                // Character is a digit, so add digit to count string
                countString.append(character)
            } else {
                // Character is a letter, so set as current letter
                currentLetter = character
            }
            
            // If the most recently added character is a letter
            if let letter = currentLetter {
                // If there's no count associated with it
                if countString.isEmpty {
                    // Add current letter to decoded string
                    decodedString.append(letter)
                } else {
                    // There is a count associated with the letter
                    
                    // Make sure count string only contains digits
                    guard let count = Int(countString) else {
                        fatalError("Characters other than digits shouldn't be in countString")
                    }
                    
                    // Add count number of the current letter to decoded string
                    for _ in 1...count {
                        decodedString.append(letter)
                    }
                    // Clear count string since the letter has been added
                    countString = ""
                }
                // Clear current letter since it's been added
                currentLetter = nil
            }
        }
        
        return decodedString
    }
}

Community comments

Find this solution interesting? Ask the author a question to learn more.

What can you learn from this solution?

A huge amount can be learned from reading other people’s code. This is why we wanted to give exercism users the option of making their solutions public.

Here are some questions to help you reflect on this solution and learn the most from it.

  • What compromises have been made?
  • Are there new concepts here that you could read more about to improve your understanding?