#####View on github.io
Functional implementation of FizzBuzz using lambdas
This started as a project to make use of Java 8's new lambda expressions but I decided to implement the same (similar) functional logic in Java, Scala, Clojure, and Python, as comparisons.
The functional logic basically follows this
- Define a base function that takes a predicate and a String
and returns a function that takes an Integer.
If the predicate is
met return the String, if not return None/Empty/nil
((Int) ⇒ Boolean, String) ⇒ (Int) ⇒ Optional[String]
static final Function<Predicate<Integer>, Function<String, Function<Integer, Optional<String>>>>
isaFizzBuzz = p -> sz -> i -> {
if (p.test(i)) return Optional.of(sz);
return Optional.empty();
};
def isaFizzBuzz(p:(Int) => Boolean, sz:String) =
(i:Int) =>
if (p(i)) Some(sz)
else None
(defn isaFizzBuzz [pred sz]
(fn [i] (cond (pred i) sz)))
def isaFizzBuzz(p, sz):
return lambda i : sz if p(i) else None
- Define higher order functions from the base function by partially applying the predicate and string
(Int) ⇒ Optional[String]
Function<Integer, Optional<String>>
isFizz = isaFizzBuzz.apply(i -> (i % 3) == 0).apply("Fizz");
def isFizz = isaFizzBuzz((x) => x % 3 == 0, "Fizz")
(def isFizz
(isaFizzBuzz
(fn [x] (= 0 (mod x 3)))
"Fizz"))
isFizz = isaFizzBuzz(lambda x: x % 3 == 0, "Fizz")
- Create an ordered sequence of the functions to apply to each number. (juxtaposition)
List<Function<Integer, Optional<String>>>
conditions = Arrays.asList(isFizz, isBuzz);
val conditions = List(isFizz, isBuzz)
(def conditions (juxt isFizz isBuzz))
conditions = lambda x: [fn(x) for fn in [isFizz, isBuzz]]
- Define a combination function for joining **Optional[String]**s and None.
- String + String = Combined Strings
- String + nil = String
- nil + nil = nil.
static final BinaryOperator<Optional<String>>
combiner = (Optional<String> a, Optional<String> b) -> {
if (a.isPresent() && b.isPresent()) return Optional.of(a.get() + b.get());
if (a.isPresent()) return a;
return b;
};
def combine(a:Option[String], b:Option[String]):Option[String] =
(a,b) match {
case (Some(s1), Some(s2)) => Some(s1 + s2)
case (Some(_), None) => a
case (None, Some(_)) => b
case (_,_) => None
}
clojure handles this a bit differently because str will combine nil and a String and nil + nil results in "" we can just use the empty? on "" to achieve similar results to the Optional None
(reduce str (conditions i))
python handles this differently as well. Because None evaluates to false we can use or to return and empty string. The empty string also evaluates to false this achieves the similar Optional None functionality
def combine(a,b):
return "%s%s" % (a or '', b or '')
- Reduce the results of applying each function and joining the strings together with the combination function, to produce an optional String. If the result is None/nil return the input number as a String.
(Int) ⇒ String
i -> conditions.stream().sequential()
.reduce(
emptyString,
(s, fn) -> combiner.apply(s, fn.apply(i)),
(xs, x) -> combiner.apply(xs, x)
).orElse(String.valueOf(i))
(i) =>
conditions.foldLeft(emptyString) {
(xs, fn) => combine(xs, fn(i))
}.orElse(Some(i.toString)).get)
(fn [i]
(let [sz (reduce str (conditions i))]
(if (empty? sz)
(str i)
sz)))
lambda i: reduce(combine, conditions(i)) or str(i)
- Take the range from 1..N and map/apply the function and print the results to the console.
IntStream.iterate(1, inc)
.limit(limit)
.mapToObj(i ->
conditions.stream().sequential()
.reduce(
emptyString,
(s, fn) -> combiner.apply(s, fn.apply(i)),
(xs, x) -> combiner.apply(xs, x)
).orElse(String.valueOf(i))
).forEach(System.out::println);
Stream.from(1)
.take(limit)
.map((i) =>
conditions.foldLeft(emptyString) {
(xs, fn) => combine(xs, fn(i))
}.orElse(Some(i.toString)).get)
.foreach(println)
(doseq [x (map fizzbuzz
(range 1 (inc limit)))]
(println x))
for s in map(lambda i: reduce(combine, conditions(i)) or str(i), range(1, limit+1)):
print s
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
16
17
Fizz
19
Buzz
Basically we want a Range of numbers from 1..N and for each number apply some function that will return the String to print or the number itself if no conditions are met.
The isaFizzBuzz function is a way to curry the logic being applied to each number. It allows you to create a partial function with the Predicate and String to return if the predicate is met.
Function<Predicate<Integer>, Function<String, Function<Integer, Optional<String>>>> isaFizzBuzz =
p -> sz -> i -> {
if (p.test(i)) return Optional.of(sz);
return Optional.empty();
};
With this function we can now create higher order functions defining isFizz and isBuzz
this is pseudo equivalent to this
isFizz = isaFizzBuzz(λ i -> (i % 3) == 0, "Fizz")
The next chunk of logic to tackle is applying each function to a
number. This could be done with if/else or case/switch or possibly
Optional.orElse.
I preferred to create a List of the function
that I want them to be applied. By using an ordered List I can
just apply the functions and combine the results. This is the reason
for implementing the BinaryOperator<Optional<String>>
combiner.
This could alternatively be achieved if there was some kind of
juxtaposition function like in clojure
This logic follows, apply the function on i combine the results with the previous result. If the predicate does not pass the function just returns empty. This means if all of the functions applied and none of the predicates are matched I can make use of the Optional.orElse to get the string value of the number and display it. And if multiple conditions are met like with 15, Fizz and Buzz will be combined to print out FizzBuzz
That covers what is going on in this chunk of code
.mapToObj(i ->
conditions.stream().sequential()
.reduce(
emptyString,
(s, fn) -> combiner.apply(s, fn.apply(i)),
(xs, x) -> combiner.apply(xs, x))
.orElse(String.valueOf(i))
What I like about this solution is the composability of this solution. It actually follows the open/closed principle although obviously not OO.
If we want to extend this solution and say we want to add "Woof" for every multiple of 7. It only requires 2 changes
The new partial function composed from the isaFizzBuzz function
Function<Integer, Optional<String>>
isWoof = isaFizzBuzz.apply(i -> (i % 7) == 0).apply("Woof");
And then add the new function to the end of the conditions list
List<Function<Integer, Optional<String>>>
conditions = Arrays.asList(isFizz, isBuzz, isWoof);
$ ./run.sh 25
1
2
Fizz
4
Buzz
Fizz
Woof
8
Fizz
Buzz
11
Fizz
13
Woof
FizzBuzz
16
17
Fizz
19
Buzz
FizzWoof
22
23
Fizz
Buzz
And we can very easily keep going
Function<Integer, Optional<String>>
isMeow = isaFizzBuzz.apply(i -> (i % 9) == 0).apply("Meow");
List<Function<Integer, Optional<String>>>
conditions = Arrays.asList(isFizz, isBuzz, isWoof, isMeow);
$ ./run.sh 25
1
2
Fizz
4
Buzz
Fizz
Woof
8
FizzMeow
Buzz
11
Fizz
13
Woof
FizzBuzz
16
17
FizzMeow
19
Buzz
FizzWoof
22
23
Fizz