Java vs Typescript: tweeter message length comparison
You will find several comparison over the net explaining how classes, interfaces, typing etc is different but I would like to share a different aspect of the languages: the actual usage in implementation more than the model usage which is actually the easy part.
What is the challenge? How to handle functional programming?
In both cases we get “lambda” but does it really feel the same?
To answer that question let’s see how we could implement a simple heurisitic to evaluate the length of a tweet. If you are not familiar with that the trick is that the length is the message length with the link length replaced by a constant (all links do 23 characters for twitter).
Here are examples:
- “This is a tweet” does 15 characters, easy!
- “This is a tweet with a link: http://link” does 40 actual characters but twitter counts 40-11+23=52 cause of the link.
Side note: the actual algorithm is more complicated cause if you have two links making your tweet longer than the maximum number of characters (140) you can still tweet it if the real link size is smaller than the evaluated 23x2 one but let’s ignore it for this post.
Implementing this heuristic can be as easy as taking the input, finding all links and replacing their length by the twitter length constant (23). Finding a link is as easy as finding strings starting with http:// or https:// but to find their end (and therefore their length) you need to find the first space or if not the end of line or if not use the message length (means the link is the end of the message).
In pseudo algorithm is looks like:
In:
- Message: string
Out:
- lenResult: integer
lenResult = length(Message)
For linkStart in { ‘http://’, ‘https://’ } Do
startIndex = 0
exit = false
Repeat until exit
indexLink = findIndexStringInStringFromIndex(Message, linkStart, startIndex
If (indexLink found) Then
endLink = findIndexOfFirstStringFromIndex(Message, indexLink, EndOfLine, Space, EndOfString)
linkLen = endLink - indexLink
lenResult = lenResult - linkLen + 23
startIndex = endLink
Else
exit = true
EndIf
EndFor
Return lenResult
To get the remaining length available for the tweet you can just remove the value lenResult from the maximum size (140).
In Java it would look like:
public int maxTweeterMessageLength(final String message) {
return twitterMaxLen - Stream.of("http://", "https://")
.mapToInt(prefix -> {
int diff = 0;
int startIdx = 0;
do {
final int linkIdx = message.indexOf(prefix, startIdx);
if (linkIdx >= 0) {
final int minIdx = startIdx + prefix.length();
final int realEnd = IntStream.of(message.indexOf(' ', linkIdx), message.indexOf('\n', linkIdx), message.length())
.filter(i -> i >= minIdx)
.min()
.orElse(-1);
final int linkLen = realEnd - linkIdx;
diff += twitterLinkLen - linkLen;
startIdx = realEnd;
continue;
}
break;
} while (startIdx > 0 && startIdx < message.length());
return diff;
}).sum() - message.length();
}
Interesting things are:
-
The loop on searched strings (http:// and https://) is a Stream, each string is converted to an int representing the difference between the actual length and the evaluated length and finally the differences for each string are summed up to compute the overall difference allowing to do the computation we want
-
The “repeat while there is a link” part of the algorithm is done with a plain do/while loop. Of course it can be more functional using a function but it would also split the algorithm and make it less readable IMO
-
The “findEnd” is implemented using a stream of all potential ends (EOL, end of string and space), filtering the not found ones and taking the smallest one. Of course it enforces to compute the 3 potential cases which is not the fastest implementation but it makes it easy to read and speed difference shouldn’t be very noticeable at that level IMO.
In Typescript (~javascript) it looks like:
maxTweeterMessageLength(message: string): number {
return TWEETER_MAX_LEN - TWEETER_LINK_PREFIXES.map(prefix => {
let diff = 0;
let startIdx = 0;
do {
const linkIdx = message.indexOf(prefix, startIdx);
if (linkIdx >= 0) {
const realEnd = Math.min.apply(null, [message.indexOf(' ', linkIdx), message.indexOf('\n', linkIdx), message.length].filter(i => i >= startIdx + prefix.length));
const linkLen = realEnd - linkIdx;
diff += TWEEK_LINK_LEN - linkLen;
startIdx = realEnd;
continue;
}
break;
} while (startIdx > 0 && startIdx < message.length);
return diff;
}).reduce((a, b) => a + b, 0) - message.length;
}
We used the same algorithm (with the same drawback in term of algorithm but that’s to compare languages).
Here things to note are:
-
The Stream we use to iterate over http:// and https:// strings is not a stream but an array. It allows to keep it as a constant but the drawback is map() and then reduce() calls work on new array instances and not on a stream (as in streaming). So it likely rely more on memory than the java version by design. The exact same point applies on the evaluation of the end of the link.
-
Javascript doesn’t have a IntStream so no built-in min() method on the “stream”/array. However min() function can take an array directly. Only trick is to use its generic apply function instead of call Math.min(array) directly.
-
For the same reason as the previous point, there is no sum() function on arrays by default so we rely on reduce() to compute the sum ourself.
Conclusion
So globally Java streams are better in term of memory, have more flavors making this algorithm more natural to implement (number stream features) so javascript completely loses this comparison? Not exactly.
Of course memory point is important, in particular if you don’t work on streams of 3 elements however javascript/Typescript has some advantages over java:
-
Invocations of “streams” operation are computing the result avoiding lazy evaluation in a different context (important in javascript where “this” is not always “this”).
-
Javascript doesn’t have number sugar (sum(), min()) but by nature allows you to add it since you can modify the built-in types (updating prototype) so this is just a matter of adding the few primitives you want.
-
If you want something closer to java - and in particular if you use Angular - you can rely on RxJs which implements a very advanced stream solution.
So what to conclude? Both languages have pro and cons and I tend to think their specificities are adapted to their default target environment. This means as a developer you need to understand that when coding in both language. However we can’t negate that frontend and backend has never been so close.
In a world where being a fullstack developer is no more an option, it is very appreciable and welcomed.
Checking last javascript version (es6) you would see that javascript itself is closer to Typescript enforcing again this statement.
It is funny to see that Java -> Javascript solutions never became main stream but that today this happens in a slightly different flavor...but this is our today’s world :).
From the same author:
In the same category: