今日推荐开源项目:《此乃谎言 awesome-falsehood》
今日推荐英文原文:《How to Skin a Cat: FizzBuzz 5 Ways》
今日推荐开源项目:《此乃谎言 awesome-falsehood》传送门:GitHub链接
推荐理由:一个关于谎言的文章合集。里面收录了在各种领域中的谎言破除文章,上到美术和科学下到软件工程,便于削除各种各样的误解,在对行外人说明或者用来涨涨自己的见识的时候这些知识会帮上不少忙的,当然了,学软件工程的不会修电脑这样时时刻刻都有的误解还是得自己想办法去说明。
今日推荐英文原文:《How to Skin a Cat: FizzBuzz 5 Ways》作者:Eddie Prislac
原文链接:https://medium.com/@semperfried76/how-to-skin-a-cat-fizzbuzz-5-ways-dc4ec811bfcb
推荐理由:用不同的语言完成 FizzBuzz 这道算法题。
How to Skin a Cat: FizzBuzz 5 Ways
FizzBuzz is a first-year computing problem many of us have faced. The rules are pretty simple: Given an integer, if the integer is a multiple of three, return ‘Fizz’, if the integer is a multiple of five, return ‘Buzz’, if a multiple of both three and five, return ‘FizzBuzz’, and if it’s not a multiple of either, return an empty string. Most folks’ go-to in this situation is to whip out a simple if/else conditional, but honestly, that’s kid-stuff. To make this a bit more difficult, today, we’ll be trying to accomplish this seemingly easy task using as few conditional statements as possible. To make matters even more difficult, we’re gonna do it in five very different languages (and that’s where this article gets its name… “There’s more than one way to skin a cat… get it?)Eddie, you’re a madman, that’s crazy talk! ~ you. We’ll see.
The first language we’ll start out with is the one that most beginning developers will be familiar with: Javascript.
Javascript is so ubiquitous in the world of software development these days, that if someone says “I’m a programmer’, it’s more likely they write JS than any other language. The explosion of popularity in what used to be derided by ‘real programmers’ as a ‘scripting language’ can be ascribed to the advent of node.js and its npm ecosystem of libraries. Nowadays, you’d be hard-pressed to find a type of program you couldn’t write in Javascript. While in the past, it was limited to crafting cheesy animations and simple interactivity on web pages, JS can now be utilized to write entire UI frameworks, network servers, real-time chat applications, even automation and/or robotics controllers, such as those created with the Raspberry pi.
Before you say anything, I’ll be deliberately omitting type checking and error handling, just in case any of you are in school and are thinking this article is going to be a good way to cheat.
The most basic code for accomplishing FizzBuzz in JS goes like this:
// num is a whole number Integer
(const fizzbuzziness = (num) => {
if(num % 3 === 0) {
return 'Fizz'
} else if(num % 5 === 0) {
return 'Buzz'
} else if((num % 3 === 0) && (num % 5 === 0)) {
return 'FizzBuzz'
} else {
return ''
}
})()
Simple, right?First, a few explanations:
1. If you’re just starting out, you may be wondering what that % operator is doing… that doesn’t actually mean ‘percent’. The % is the modulus operator, and modulus is a function we’re going to make a lot of use of in this article. Modulus is the function used when you want to find the remainder of dividing one number by another. In this case, we want to check if the number is cleanly divisible by 3, 5 or both, so we check if the number mod 3 or 5 is equal to zero.
2. No, you’re not seeing triple, those triple-equals signs are the way we check equivalence in JS. Most languages use a double-equals to check equivalence, and a single-equals for variable assignment (ex: let x = "foo"). In JS, the double-equals is used as well, but not as commonly, as the ==operator allows for type coersion; in other words, a string value of '1'would evaluate to be equivalent to a number value of 1. That’s useful if you’re yanking the innerHTML of a tag to compare to a number in your script, but generally ill-advised. The === operator is a strict equivalence comparison, meaning '1' === 1 would evaluate to false.
3. The double-ampersand ( &&)in that third conditional branch is a logical AND , but we won’t be using it again for the remainder of this article, so this will be the last time I mention it.
This code will run reasonably well, and do what it’s supposed to do. However, it can also be written like this:
(() => {
'use strict'
const fizzbuzziness = (num) => {
const tbl = Object({ 'Fizz': (num % 3 === 0), 'Buzz': (num % 5 === 0) })
return Object
.keys(tbl)
.filter(key => tbl[key])
.reduce((acc, curr) => { return `${acc}${curr}` }, '')
}
})()
Fun fact… the guy who introduced me to Immediately Invoked Function Expressions (IIFEs) refers to the parens at the end as ‘donkey balls’. Note how the whole thing is wrapped in parens, then is followed by two empty parens… this makes the JS interpreter view the whole thing as a function that’s immediately executed when loaded, as the name implies.In this snippet, the code is a lot cleaner-looking. Unfortunately, because we need to instantiate Object twice, it runs about 82% slower than the first example. If it’s slower, why would I even bother showing it to you, you might ask? Well, performance was not the point of this demonstration, but since you asked, consider the results from this test, in which I compare the results of using an Object map vs if/else, vs case/switch. In a case where you need to compare static results, tables have turned, and Object mapping is now 80% faster than if/else, and narrowly beats out even switch/case. Why the flip? In this case, if/else is the one which needs to run comparisons every time the operation is run, whereas the object is being used as a binary search tree… it’s only taking the sum and looking up the key in its structure. This is why it’s sometimes much, much faster to utilize this type of structure than iterating over load of conditionals. What’s more, this can be done in nearly any language that supports lists of key-value pairs, such as Python:
from functools import reduce
def fizzbuzziness(num):
tbl = { 'Fizz': num % 3 == 0, 'Buzz': num % 5 == 0 }
trues = { k: v for k, v in tbl.items() if v }
return reduce((lambda x, y: x + y), list(trues.keys()), '')
Ooh, that’s short and sweet!
In Python, we see that we can again use a reduce function, and a lot of the parts can almost be recognized from the JS version we looked at previously, even though the syntax has changed greatly. Here, we’re using a lambda list comprehension to reduce the keys to a single string. Also, in Python, whitespace is important… this is why I’ve used four spaces to indent my code instead of two, like in the rest of my examples — in many other languages, whitespace is a nicety, but screw it up, and it’s no big deal. Not so in Python, if your formatting’s off even a little, it causes big problems for your code. Even so, it’s less code than the JS version, but I couldn’t quite get away from using that ifstatement, even if it’s in a guard clause, rather than a standard conditional. Also notice the double-equals used to determine equivalence in our modulus checkers — the === does not exist in Python, but that’s the norm, and JS is really the exception, as we’ll see in the rest of our examples…
Lastly, see at the top of that code how we had to import the reduce statement? Kinda reminds me of C++… In fact, let’s consider C++:
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <numeric>
using namespace std;
map<string, bool, greater<string>> fbMap(int num) {
map<string, bool, greater<string>> tbl;
tbl["Fizz"] = num % 3 == 0;
tbl["Buzz"] = num % 5 == 0;
return tbl;
}
vector<string> trueKeys(map<string, bool, greater<string>> myMap) {
vector<string> arr;
arr.reserve(2);
for(const auto & [key, val]: myMap) {
string item = val ? key : "";
arr.push_back(item);
}
return arr;
}
string trueKeysJoined(vector<string> arr) {
string retVal;
retVal = accumulate(arr.begin(), arr.end(), string(""));
return retVal;
}
string fizzbuzziness(int num) {
return trueKeysJoined(trueKeys(fbMap(num)));
}
int main() {
int num;
cout << "Please input a whole-number value:" << endl;
cin >> num;
cout << fizzbuzziness(num) << endl;
return 0;
}
AAAAAAAAAAIIIIIIIIIIIIGGGGGGGGHHHHHHH!!!!!!I can’t even.
HOKEY-SMOKES, BULLWINKLE, that’s a lot of code! Of course, I did have to add an entry point (int main() ) with operations to accept input and output the results, (cin and cout) because C++ is a compiled language. JS and Python, are interpreted languages, and can call their functions at runtime without any prep, but as I needed to be able to run the C++ code as a shell script, it was either do it this way, or script in a way to take the number param from the command-line (sorry, I just didn’t feel like it, it’s already a buttload longer without doing that). You’ll notice that even though the syntax looks similar to JS with the parentheses wrapping the params and braces surrounding the methods, we need to do something else in C++, and that’s define the types, not only for each variable we’re using, but also for the return values of each method. We also don’t get quite the easiest way of defining our key/value pair list, which here is called a map. In C++, a vector takes the place of an Array, and we need to set the maximum size of the vector when defining it. This is because C++ forces you to pay a lot closer attention to the memory being used by your program. This is also the reason we need to include the libraries for each of the datatypes we want to use in the program up at the top of the file, because C++ will only include the bare minimum it needs to compile. We also declared a namespace: std. This is because all the libraries we’ve included come from the ‘standard’ library, and if we don’t declare the namespace, we end up writing out std. in front of every declaration of a variable that uses those types. If we’d wanted, we probably could’ve written the same program in C++ using same algorithm as in our first JS example, and it would’ve looked very similar, but still would’ve required a bit more setup and code. JS and Python are both dynamically typed, meaning they infer the type of the variable from the data that’s assigned to the variable. C++ on the other hand, will throw an error if you try to assign a string to a variable which you’ve defined as an int. One final note is that hard as I try, I couldn’t get away from having at least one if/else statement, although it doesn’t appear to have one. Can you spot it? It’s this:
string item = val ? key : "";
That’s a ternary operator, and JS, Python, Ruby, C++ and virtually every other language supports them. What’s going on here is that we’re assigning a string the value of key if val is true, else "" . The question mark makes it shorthand for if and the colon for else. This is what’s coined in the biz as a bit of ‘syntactic sugar’.Gimme some sugar, Baby. — (I always kinda hoped I’d get to work in a Bruce Campbell quote into one of my articles. Here it is.)
OK, that’s enough about C++ for now, I need a palate-cleanser. Let’s move on to Clojure. Clojure is a functional language, a dialect of Common Lisp that can be run in the JVM, and can utilize Java libraries. We won’t be getting into any of that Java stuff for now, because we really don’t need it for this example. Be prepared, though, even though Clojure provides us with the shortest example in this list, it’s also one of the weirdest-looking:
(defn fizzbuzziness [num]
(def tbl {"Fizz" (mod num 3),"Buzz" (mod num 5)})
(clojure.string/join "" (filter (comp #{0} tbl) (keys tbl))))
WHOA.
Yep, excluding the functions’ name, that whole method is only two lines of code, but what the hell is going on?The first thing is that the entire thing gets wrapped in a set of parentheses, (Clojure is pronounced ‘closure’, after all…), and our function declaration starts off with a defnstatement. Also notice that the param we’ll be inputting ( num ) is wrapped in brackets instead of parens. The other things you should notice is our tblvar is defined with a def statement, and that there are no colons separating the keys from the values. And how about those values? In Clojure, rather that using a % operator, we get the modulus using the mod function, and it comes before the two params… rather that writing num % 3 == 0 , we’re writing mod num 3, which will result in 0. Why are we doing it differently than the other examples? Well, in the list comprehension we’re using to filter down the values, you’ll see that we’re using the comp(compare) function to compare the values to zero. If you squint really hard, that function should remind you a little of the last line of our Python example… the syntax may be different, but the structure of the line is very similar.
My final example comes from Ruby, and it makes me smile, because Ruby allows us to do something really clever. See if you can spot it.
class Integer
def fizzbuzziness
{ 'Fizz' => (self % 3).zero?, 'Buzz' => (self % 5).zero? }
.select { |_key, value| value }
.keys
.inject('') { |prev, curr| "#{prev}#{curr}" }
end
end
See it yet?Hey, you may say, that doesn’t look so special… it’s longer than all but the C++ versions of this method, but otherwise doesn’t look all that different from the JS ver… wait what… whaaaaa? What’s that class definition doing there? Where’s your num param? What’s that call to self? WHAT DID YOU DO???!!!
I iz skurd.
Ruby, while also an interpreted, dynamically typed language like JS and Python, and object oriented like C++, allows us a few liberties we can’t really take all that easily in other languages. One of those is the ability to re-open any class and add methods to its definition. In this example, I’ve reopened the Integer class and added the fizzbuzziness method directly to it. This allows the method to be called directly on the number we want to get the ‘fizzbuzziness’ of, rather than calling the method with the number as a param (ex: 5.fizzbuzziness instead of fizzbuzziness(5)). Why in Gods name would you want to reopen a core class of the language and add methods to it? Isn’t that dangerous? Kinda, yeah. You really shouldn’t do it all that often, as methods you add may collide with methods that get added to the standard Ruby library later on, and this could result in massive code-breakages for your application. There’s also the possibility that you could overwrite any method in the class, and end up with something completely nonsensical, like so:
# Never do this for real.
class Array
def count
'fish'
end
end
If you do, I don’t know you, you never got this from me.It’s for this express reason that, even though similar behavior is possible by modifying a class’ prototype in Javascript, they tell you in big, all-uppercase, shouty letter in their API documentation that you should NEVER EVER do it with a standard library class. However… in Ruby, if you have a method like mine that isn’t likely to get added to the standard library, it can be extremely useful as well, as it helps your application adhere to the principle of ‘tell, don’t ask’ in a way that’s more idiomatic to Ruby programming.
And so, oh my droogies, we come to the end of the article. Different programming languages, same result. A first-year coding problem, beaten within an inch of its life with a meat tenderizer and examined under a microscope. You may not enjoy digging around in different languages to compare how they work as much as I do, but I hope you had as much fun reading this article as I had researching and writing it. Until next time!
下载开源日报APP:https://openingsource.org/2579/
加入我们:https://openingsource.org/about/join/
关注我们:https://openingsource.org/about/love/