28
\$\begingroup\$

What general tips do you have for golfing in Scala? I'm looking for ideas that can be applied to code golf problems in general that are at least somewhat specific to Scala (e.g. "remove comments" is not an answer). Please post one tip per answer.

(This is a shameless copy of ... in Python)

\$\endgroup\$

16 Answers 16

14
\$\begingroup\$

The shortest way of repeating something is with Seq.fill.

1 to 10 map(_=>println("hi!")) // Wrong!
for(i<-1 to 10)println("hi!") // Wrong!
Seq.fill(10)(println("hi!")) // Right!
\$\endgroup\$
1
  • \$\begingroup\$ for(i<-1to 10)println("hi!") has the same length \$\endgroup\$
    – Wezl
    Commented Feb 1, 2022 at 23:17
13
\$\begingroup\$

suspicious identifier: ?

You can use ? as identifier:

val l=List(1,2,3)
val? =List(1,2,3)

Here it doesn't save you anything, because you can't stick it to the equal sign:

val ?=List(1,2,3) // illegal

But later on, it often saves one character, since you don't need a delimiter:

print(?size)  // l.size needs a dot
def a(? :Int*)=(?,?tail).zipped.map(_-_)

However, it is often tricky to use:

       print(?size)
3
       print(?size-5)
<console>:12: error: Int does not take parameters
       print(?size-5)
              ^
\$\endgroup\$
1
  • 2
    \$\begingroup\$ There are also other identifiers, such as |, >, and pretty much any symbol that's not a word character (other than parentheses, commas, dots, semicolons, equal signs, square brackets, curly brackets...) \$\endgroup\$
    – user
    Commented Aug 31, 2020 at 23:35
12
\$\begingroup\$

Collections

The first choice for a random collection is often List. In many cases you can replace it with Seq, which saves one character instantan. :)

Instead of

val l=List(1,2,3)
val s=Seq(1,2,3)

and, while s.head and s.tail is more elegant in usual code, s(0) is again one character shorter than s.head.

Even shorter in some cases - depending on needed functionality is a tuple:

val s=Seq(1,2,3)
val t=(1,2,3)

saving 3 characters immediately, and for accessing:

s(0)
t._1

it is the same for direct index access. But for elaborated concepts, tuples fail:

scala> s.map(_*2)
res55: Seq[Int] = List(2, 4, 6)

scala> t.map(_*2)
<console>:9: error: value map is not a member of (Int, Int, Int)
       t.map(_*2)
         ^

update

def foo(s:Seq[Int])
def foo(s:Int*)

In parameter declaration, Int* saves 4 characters over Seq[Int]. It is not equivalent, but sometimes, Int* will do.

\$\endgroup\$
11
\$\begingroup\$

disclaimer: parts of this answers are generalizations of other answers found here.

Use lambdas without specifying their argument types

It's allowed to submit something like this: a=>a.size instead of (a:String)=>a.size.

Use ascii-symbols as identifiers.

These include !%&/?+*~'-^<>|. Because they arent't letters, they get parsed separately when they're next to letters.

Examples:

a=>b       //ok
%=>%        //error, parsed as one token
% => %      //ok
val% =3     //ok
&contains+  //ok
if(x)&else* //ok

Use Set instead of contains

if (Seq(1,2,3,'A')contains x)... //wrong
if (Set(1,2,3,'A')(x))...         //right

This is possible because Set[A] extends (A => Boolean).

Use a curried function when you need two arguments.

(a,b)=>... //wrong
a=>b=>...  //right

Use the _-syntax when possible

The rules for this are somewhat obscure, you have to play a little bit around sometimes to find the shortest way.

a=>a.map(b=>b.size)) //wrong
a=>a.map(_.size)     //better
_.map(_.size)        //right

Use partial application

a=>a+1 //wrong
_+1    //better, see above
1+     //right; this treats the method + of 1 as a function

Use ""+ instead of toString

a=>a.toString //wrong
a=>a+""       //right

Use strings as sequences

"" is sometimes the shortest way to create an empty sequence if you don't care about the actual type

Use BigInt to convert numbers to and from strings

The shortest way to convert a number to a string in a base other than base 10 is through BigInt's toString(base: Int)method

Integer.toString(n,b) //wrong
BigInt(n)toString b   //right

If you want to convert a string to a number, use BigInt.apply(s: String, base: Int)

Integer.parseInt(n,b) //wrong
BigInt(n,b)           //right

Be aware that this returns a BigInt, which is useable like a number most of the times, but can't be used as an index for a sequence, for example.

Use Seq to create sequences

a::b::Nil   //wrong
List(...)   //also wrong
Vector(...) //even more wrong
Seq(...)    //right
Array(...)  //also wrong, except if you need a mutable sequence

Use Strings for Seqences of chars:

Seq('a','z') //wrong
"az"         //right

Make use of Stream for infinite sequences

Some challenges ask for the n-th element of an infinite sequence. Stream is the perfect candidate for this. Remember that Stream[A] extends (Int => A), that is, a stream is a function from an index to the element at that index.

Stream.iterate(start)(x=>calculateNextElement(x))

Use symbolic operators instead of their wordy counterparts

:\ and :/ instead of foldRight and foldLeft

a.foldLeft(z)(f) //wrong
(z/:a)(f)        //right
a.foldRight(z)(f) //wrong
(a:\z)(f)         //right

hashCode -> ##

throw new Error() -> ???

Use -> for creating and unpacking tuples

(a,b)  //wrong
a->b   //right

Use & and | instead of && and ||

They work the same for booleans, but will always evaluate both operands

Alias long method as functions

def r(x:Double)=math.sqrt(x) //wrong
var r=math.sqrt _            //right; r is of type (Double=>Double)

Know the functions in the standard library

This especially applies to the methods of collections.

Very useful methods are:

map
flatMap
filter
:/ and :\ (folds)
scanLeft and scanRight
sliding
grouped (only for iterators)
inits
headOption
drop and take
collect
find
zip
zipWithIndex3
distinct and/or toSet
startsWith
\$\endgroup\$
0
10
\$\begingroup\$

Use infix syntax to remove the need for . characters. You don't need spaces unless adjacent items are both in alphanumeric or both in operator characters (see here), and not separated by reserved characters (brackets, commas etc).

E.g.

List(1,2,3,4).filter(_ % 2 == 0) // change to:
List(1,2,3,4)filter(_%2==0)
\$\endgroup\$
10
\$\begingroup\$

You can usually use map instead of foreach:

List("a","b","c") foreach println

can be replaced with

List("a","b","c") map println

The only difference is the return type (Unit vs List[Unit]), which you aren't interested in anyway when using foreach.

\$\endgroup\$
9
\$\begingroup\$

The true and false literals are shorter to write as 2>1 for true and 1>2 for false

\$\endgroup\$
9
\$\begingroup\$

Call two times the same function for initialization:

val n,k=readInt

(Seen somewhere else, but can't find it now).

\$\endgroup\$
7
\$\begingroup\$

define shorter Types:

If you have multiple declarations of a type, like

def f(a:String,b:String,c:String) 

it is shorter to define a type alias, and use it instead:

type S=String;def f(a:S,b:S,c:S)

Original length is 3*6=18 Replacement-code is 8(type S=;)+6+3*1(=new length)=17

if (n*length < 8+length+n), then it is an advantage.

For classes which are instantiated via a factory, we can set a shorter variable name to point to that object. Instead of:

val a=Array(Array(1,2),Array(3,4))

we can write

val A=Array;val a=A(A(1,2),A(3,4))
\$\endgroup\$
4
  • 1
    \$\begingroup\$ This applies to C++ as well with #define for example, but I admit it's nice that def and val are shorter. \$\endgroup\$ Commented Nov 11, 2011 at 18:21
  • \$\begingroup\$ Hm. def is the keyword to define a method, and a simple translation to c++ for val is 'const', and it is a declaration, but the type is often inferred. The shortening is in the first case the type= which is closer to typedef - isn't it? The second example isn't from me and it is new to me. I have to watch out, where to use it. \$\endgroup\$ Commented Nov 11, 2011 at 19:01
  • \$\begingroup\$ typedef long long ll; is the same as #define ll long long, so the latter's shorter by 1. But yeah, typedef does work. Looking at the val example again I definitely misread it. It seems even less Scala-specific. x = thingWithAReallyLongComplicatedNameForNoReason is a pretty general strategy :P \$\endgroup\$ Commented Nov 11, 2011 at 19:21
  • \$\begingroup\$ @userunknown When you instantiate a List or Array etc with syntax val x = List(1,2,3) you're just calling the apply method on the List object. (This technique for object creation is known as a "factory method", in contrast to using a constructor with new.) So above, we're just making a new variable that points to the same singleton object as the variable name Array. Since it's the same thing, all the methods, including apply, are available. \$\endgroup\$ Commented Nov 12, 2011 at 17:36
6
\$\begingroup\$

Rename Methods, if their name is long, and if they're used multiple times - real world example:

 x.replaceAll(y,z)

 type S=String; def r(x:S,y:S,z:S)=x.replaceAll(y,z)

Depending on the possibility to save 'S=String' at different places too, this will only be economical, if you replace at least replaceAll 3 times.

\$\endgroup\$
6
\$\begingroup\$

Initialize several variables at once using a tuple:

var(a,b,c)=("One","Two","Three") //32 characters

vs.

var a="One";var b="Two";var c="Three" //37 characters
\$\endgroup\$
3
\$\begingroup\$

Use lazyZip or zipped (and _) instead of zip when combining lists

Using a.zip(b) is shorter than a.lazyZip(b) or (a,b).zipped, but if you're using map on it later, the latter will be shorter, since with zip, map requires function taking a single Tuple2, but with lazyZip/zipped, you can use a function of two parameters with underscores.

Here's an example adding together two lists:

val list1 = List(1, 2, 3, 4)
val list2 = List(5, 6, 7, 8)

list1.zip(list2).map(t=>t._1+t._2)   //Not good
list1.lazyZip(list2).map((a,b)=>a+b) //Okay, this is worse
list1.lazyZip(list2).map(_+_)        //But underscores make it all better
(list1,list2).zipped.map((a,b)=>a+b) //Same as with lazyZip
(list1,list2).zipped.map(_+_)        //Same as with lazyZip

Since zipped is deprecated, I'd recommend lazyZip, as you can also use infix syntax with it. However, in some situations, such as when you're zipping more than 2 lists, zipped may still be better (and it's not available on TIO, which uses an older version of Scala - you'll need Scastie).

\$\endgroup\$
3
\$\begingroup\$

Treat a String as a Sequence

Use filter instead of replace or replaceAll

If you need to erase characters from a String use instead of replace / replaceAll

x replaceAll(y,"") // 18 bytes
x replace(y,"")    // 15 bytes

the filter method:

x filter(y!=)      // 13 bytes

Do not use charAt

If you need a character from a specific index from a String, do not use charAt:

x charAt 7  // 10 bytes

Treat the String as a sequence:

x(7)        // 4 bytes

Use drop / slice instead of substring

x substring y    // 13 bytes
x drop y         //  8 bytes


x substring(a,b) // 16 bytes
x slice(a,b)     // 12 bytes

Thanks to user for extending the answer!

\$\endgroup\$
1
  • 1
    \$\begingroup\$ If you want to expand your answer(s) with more String-related tips, here's another: s.drop(x) is shorter than s.substring(x), and s.slice(a,b) is shorter than s.substring(a,b) (really, almost any time you treat a String like a Seq, it's shorter). \$\endgroup\$
    – user
    Commented Jan 22, 2021 at 21:12
3
\$\begingroup\$
  1. Use syntactic sugar and built-in functions to save bytes.

syntactic sugar, e.g.   use /: operator (fold left)

val sum = numbers.foldLeft(0) { (accumulator, element) => accumulator + element }

val sum = (0 /: numbers) { (accumulator, element) => accumulator + element }
  1. Type conversion. 231+.0 instead of 231.toDouble.  231+"" instead of 231.toString

  2. Use Seq instead of Array or List

  3. Rewrite type name and method name.

Rewrite the type name if the type appears very many times.

Use generic parameter instead of external type definition to save more bytes.

/*42 bytes*/
def f(a:String,b:String,c:String,d:String)

/*36 bytes*/
type S=String
def f(a:S,b:S,c:S,d:S)

/*33 bytes*/
def f[S<:String](a:S,b:S,c:S,d:S)

If the method appears very many times, please rewrite the method name.

implicit class V[A](val x:A)extends AnyVal{def Q=x.toSet}
  1. Avoid def return type if possible.   Use def instead of lambda function val f=.   e.g.

    use def f(a:Int)=a+1 instead of val f:(Int=>Int)={a:Int=>a+1} to save bytes.

  2. Avoid unnecessary braces {} ()

  3. Simultaneously initialize multiple variables val r,c=new StringBuilder;

  4. use 0 to x-1 instead of 0 until x

\$\endgroup\$
2
  • \$\begingroup\$ Use generic parameter instead of external type definition: def f[S<:String](a:S,b:S,c:S,d:S) This is shorter than just inlining the type if you have more than 2 arguments of that type. \$\endgroup\$ Commented Nov 2, 2023 at 0:38
  • \$\begingroup\$ @noodle man, so many thanks. \$\endgroup\$
    – 138 Aspen
    Commented Nov 2, 2023 at 0:39
2
\$\begingroup\$

If you need to reuse a function, make a def instead of a val

If you absolutely need to define a function in your answer to reuse multiple times, it's usually better to define it like a method rather than using a lambda.

Consider these two pairs of functions:

//Recursive
def f(i:Int):Int=if(i>0)i*f(i-1)else 1
val f:Int=>Int=i=>if(i>0)i*f(i-1)else 1

//Not recursive is also shorter with def
def f(i:Int)=i+1
val g=(i:Int)=>i+1

Except when using pattern matching

However, if you're pattern matching, you can use Scala's special syntax and omit match when assigning an anonymous function to a val. As you can see, it's a lot shorter:

//Highly efficient way to sum a list
val g:List[Int]=>Int={case h::t=>h+f(t)case _=>0}
def f(l:List[Int]):Int=l match{case h::t=>h+f(t)case _=>0}
\$\endgroup\$
-1
\$\begingroup\$

You can also use instead of using => for function definitions.

\$\endgroup\$
1
  • 5
    \$\begingroup\$ Hello and welcome to PPCG. Since most of the time, answers are counted in bytes rather than characters, your tip only has a limited scope. I would address this and also add a tip title like Shortening function definitions in character count based code golf challenges. \$\endgroup\$ Commented Sep 10, 2018 at 23:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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