24
\$\begingroup\$

Swift's SequenceType is a useful means of generating a sequence of values, and it makes it particularly useful iterate over these values.

I don't really have much experience with these SequenceType types, so I wanted to implement my own for some practice and learning. What better sequence to take a look at than a Fizz Buzz sequence, right?

I wanted to make this Fizz Buzz a little special though. I wanted the user to define any sort of rules and add as many tests as they wanted. We just pair each test with a word, pass an array of these test-word pairs, and let the sequence do all the work.

So, to start out, I create custom types for the "Test" and the test-word "Pair":

typealias FizzBuzzRule = (Int) -> Bool
typealias FizzBuzzPair = (test: FizzBuzzRule, word: String)

So using a normal FizzBuzz example, we'd create the ordinary "Fizz" and "Buzz" tests like this:

let fizzTest = { (i: Int) -> Bool in
    return i % 3 == 0
}
let buzzTest = { (i: Int) -> Bool in
    return i % 5 == 0
}
let fizzPair: FizzBuzzPair = (fizzTest, "Fizz")
let buzzPair: FizzBuzzPair = (buzzTest, "Buzz")
let pairs = [fizzPair, buzzPair]

But of course, we can create any sort of rules we want. These are just examples, and as we see the rest of the code, we'll see how using these example rules will produce the standard "FizzBuzz" problem results.

The next step is writing a function to apply the rules and produce the required output. For that, I wrote the fizzBuzzify function:

func fizzBuzzify(value: Int, fizzBuzzPairs: [FizzBuzzPair]) -> String {
    var retnValue: String? = nil
    for pair in fizzBuzzPairs {
        if pair.test(value) {
            retnValue = (retnValue ?? "") + pair.word
        }
    }
    return retnValue ?? String(value)
}

So now, we can pass any value and any array of Test-Word pairs, and build our FizzBuzz-type string simply using this function.

Already, we could do something like this:

for x in 1...100 {
    println(fizzBuzzify(value, pairs))
}

But, I wanted to go one step further and improve this into a sequence which generates the values for us, so I needed to create FizzBuzzSequence as a SequenceType:

struct FizzBuzzSequence: SequenceType {
    let startValue: Int
    let endValue: Int
    let pairs: [FizzBuzzPair]

    init(start: Int = 1, end: Int = 100, pairs: [FizzBuzzPair]) {
        self.startValue = start
        self.endValue = end
        self.pairs = pairs
    }

    init(start: Int = 1, end: Int = 100, pairs: FizzBuzzPair...) {
        self.init(start: start, end: end, pairs: pairs)
    }

    func generate() -> GeneratorOf<String> {
        var value: Int = self.startValue
        return GeneratorOf<String> {
            return (value <= self.endValue) ? fizzBuzzify(value++, self.pairs) : nil
        }
    }
}

And now, that we've put it all together, it can be used as simply as:

for fizzBuzzValue in FizzBuzzSequence(start: 1, end: 100, pairs: pairs) {
    println(fizzBuzzValue)
}

And assuming pairs is the same array of FizzBuzzPair that we set up earlier, this will have the exact same results as any other FizzBuzz program you'd expect to see.

But we can now start at any value, end at any value, and set up any rules we want.

I'm looking for general comments on Swiftiness of this code, as well as double checking efficiency of the program in general. Am I even using the SequenceType how it's intended to be used?


For clarify, below is the full set of code to be reviewed put together (it was split up by commentary above):

typealias FizzBuzzRule = (Int) -> Bool
typealias FizzBuzzPair = (test: FizzBuzzRule, word: String)

func fizzBuzzify(value: Int, fizzBuzzPairs: [FizzBuzzPair]) -> String {
    var retnValue: String? = nil
    for pair in fizzBuzzPairs {
        if pair.test(value) {
            retnValue = (retnValue ?? "") + pair.word
        }
    }
    return retnValue ?? String(value)
}

struct FizzBuzzSequence: SequenceType {
    let startValue: Int
    let endValue: Int
    let pairs: [FizzBuzzPair]

    init(start: Int = 1, end: Int = 100, pairs: [FizzBuzzPair]) {
        self.startValue = start
        self.endValue = end
        self.pairs = pairs
    }

    init(start: Int = 1, end: Int = 100, pairs: FizzBuzzPair...) {
        self.init(start: start, end: end, pairs: pairs)
    }

    func generate() -> GeneratorOf<String> {
        var value: Int = self.startValue
        return GeneratorOf<String> {
            return (value <= self.endValue) ? fizzBuzzify(value++, self.pairs) : nil
        }
    }
}
\$\endgroup\$
19
\$\begingroup\$

Your code looks good to me, and I have only some minor remarks and suggestions.

An Optional has an implicit initial value of nil, so

var retnValue: String? = nil

can be simplified to

var retnValue: String?

In

func generate() -> GeneratorOf<String> {
    var value: Int = self.startValue
    return GeneratorOf<String> {
        return (value <= self.endValue) ? fizzBuzzify(value++, self.pairs) : nil
    }
}

the type annotation : Int and the first self are not needed, and the generic type <String> can be inferred from the compiler in the second instance:

func generate() -> GeneratorOf<String> {
    var value = startValue
    return GeneratorOf {
        return (value <= self.endValue) ? fizzBuzzify(value++, self.pairs) : nil
    }
}

Instead of named functions "fizzTest", "buzzTest", closures can be used to define the tests, and the entire rule set can be written as

let pairs : [FizzBuzzPair] = [
    ( { $0 % 3 == 0 } , "Fizz"),
    ( { $0 % 5 == 0 } , "Buzz")
]

The typealias FizzBuzzRule is used only in one place, so you could drop that and define

typealias FizzBuzzPair = (test: (Int) -> Bool, word: String)

instead. I would probably define a struct instead of a tuple:

struct FizzBuzzRule {
    let test : (Int) -> Bool
    let word : String
}

let rules = [
    FizzBuzzRule(test: { $0 % 3 == 0 }, word: "Fizz") ,
    FizzBuzzRule(test: { $0 % 5 == 0 }, word: "Buzz")
]

because compiler diagnostics and autocompletion works better with struct, but that may be a matter of taste.

Finally note that FizzBuzzSequence essentially does a mapping from a range of integers to a sequence of strings, so the same would be achieved with

let fbSequence = lazy(1 ... 100).map { fizzBuzzify($0, pairs) }
for fbValue in fbSequence {
    println(fbValue)
}
\$\endgroup\$
  • \$\begingroup\$ Is there any time when mapping would be preferred to the SequenceType or vice versa? Is it a matter of taste or are there times where one or the other is actually better? \$\endgroup\$ – nhgrif May 25 '15 at 11:44
  • \$\begingroup\$ @nhgrif: The map() method of the various Lazy... types return a sequence as well, so fbSequence is a sequence. It is probably a matter of taste, I just had the feeling that this approach separates the concerns better (enumeration vs mapping). \$\endgroup\$ – Martin R May 25 '15 at 12:01

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.