Hacker Rank in Swift - Reuse Code
Reuse Swift IO code for multiple HackerRank assignments.
If you have read this article, you have probably noticed that code to read from stdin must be copied to each assignment file. Even though you need to copy-paste entire solution to HackerRank web site, this goes against DRY principle. In this post I’ll explain how you can keep all stdin code in one file and use it to run code for assignments.
Start by grabbing Swift code, you can use this file for example. Put it in a file, name it
StdIO.swift and put it in the root of your HackerRank folder alongside the makefile from this post (you’ll need that makefile later on).
For next step you should have 2 files. A
StdIO.swift file created earlier and, say,
solve-me-first.swift for the warmup assignment.
If you try to run
solve-me-first.swift with makefile created in the article mentioned before, you’ll get nowhere. Swift compiler has no idea where to look for
readLn methods, so it can’t interpret this script file alone. We have to build a Swift Module for
StdIO.swift and then link it with our main Swift file.
StdIO.swiftmodule- public interface and definitions. An analogue of header files from Objective-C world.
libStdIO.dylib- a shared (aka dynamic) library. That’s actually a binary that has all all your code compiled, much alike dynamic library for C, C++, Objective-C and so on. There’s a way to build a static (
.a) library as well.
In general, Swift Module can include more than one Swift file. It just so happens that we have only one. To build a module you need to run this command
The options are instructing compiler to emit Swift module and shared library. Compiler also needs to know which SDK to build for. As expected, you should have 3 new files created.
That’s great, the module is ready. Next step is to link it with your main Swift file and run the code. Before you run any command in the shell, you need to add one more line to
Now it’s time to link the two and run the code. This time we can use
Let’s talk about each option separately.
-l<library>- this is a flag that should be followed by the name of the library to link with.
-lStdIOworks as well, but that’s because
libStdIO.dylibis sitting in the same folder. I choose to be more verbose and use the full path to shared library. My intentions will become clear later when we talk about makefiles.
-I <import-path>- this flag is used to specify import path. Similar to include path in Objective-C world, this way we tell the compiler where to look for Swift modules to resolve
importstatements in the code.
- Once again, instead of specifying current folder as
., I’m using full path. That will be explained later.
- Once again, instead of specifying current folder as
-module-link-name <name>- well, it expects a name of the module to link with.
Actually, this is a bit of a cheat. This second command does not compile the code, but interprets it while linking with existing module. There is a way to use Swift compiler here as well, you can find the reference in the end of this post.
OK, so run this command and you should get a (high)
5 as output. Replace the name of the test case file and the name of Swift file and you can run code for any other assignment. But that is too verbose. It should be automated with…
… with Makefile, of course. With small effort we can convert shell script into a makefile script.
OK, so let’s walk the code.
First we declare 3 variables:
SELF_DIRis an absolute path to the location of the makefile itself. We need to know this information, because we also know that
StdIO.swiftis located in the same folder as
Makefile. It is not the same as
$(CURDIR)is the folder you run makefile from.
BUILD_DIRis the place to put build output. Don’t forget to put it in your
.gitignoreand remove it as part of
OSX_SDKis the path to current Mac OS X SDK.
One important note is the use of
:= instead of just
= when assigning values to the variables. If right side of assignment is one of the makefile functions, like
shell to execute shell command, or
dir, then each time you reference a variable, for example
$(OSX_SDK), the shell command will be executed. If the command is expensive, this will slow down execution of your makefile targets. Using
:= ensures that variable is assigned a value only when initialized, when it’s reference later on it uses that initial value.
Next block declares group of variables used as a reference to Swift executables, StdIO module and library name.
Then there are 2 variables and 2 functions related to test cases configuration. To get more details about
tc-path function, check out previous post. Declaring targets as phony is something that’s explained in that article as well.
%.swift target runs all the compile commands. This code is almost identical to the shell code we had before, with minor differences.
-o <library-name>option is used to explicitly tell compiler to put library file and accompanying module files into a build directory. That’s just to keep your workspace clean of compiler output.
- file name for
$(SELF_DIR)variable to find
StdIO.swiftin the same folder as
Give it a try and run it.
hrrun is an alias defined in previous article as well.
So it works now and you can keep stdin code separately and reuse it in assignments source. By now you know how to run code in interpreter and how to compile it, wouldn’t it be nice to be able to choose any of the two options?
Interpret or Compile
Since makefiles support if-else flow control statements, you can add a conditional statement and split your makefile code in 2 parts, one for running code using interpreters, another to compile before running. It’s as easy as this
By controlling value of
COMPILE variable you can choose one of the two ways to run assignments code. Another alias would be handy as well.
You can now do the same thing for Haskell, Python, C, C++, Java or any other language that support compilation. Grab the latest version of Makefile for your reference, and happy HackerRanking!
blog comments powered by Disqus