7

I need to check if a Traversable (which I already know to be nonEmpty) has a single element or more.

I could use size, but (tell me if I'm wrong) I suspect that this could be O(n), and traverse the collection to compute it.

I could check if tail.nonEmpty, or if .head != .last

Which are the pros and cons of the two approaches? Is there a better way? (for example, will .last do a full iteration as well?)

Lorenzo Dematté
  • 7,638
  • 3
  • 37
  • 77

4 Answers4

10

All approaches that cut elements from beginning of the collection and return tail are inefficient. For example tail for List is O(1), while tail for Array is O(N). Same with drop.

I propose using take:

 list.take(2).size == 1 // list is singleton

take is declared to return whole collection if collection length is less that take's argument. Thus there will be no error if collection is empty or has only one element. On the other hand if collection is huge take will run in O(1) time nevertheless. Internally take will start iterating your collection, take two steps and break, putting elements in new collection to return.

UPD: I changed condition to exactly match the question

Aivean
  • 10,692
  • 25
  • 39
3

Not all will be the same, but let's take a worst case scenario where it's a List. last will consume the entire List just to access that element, as will size.

tail.nonEmpty is obtained from a head :: tail pattern match, which doesn't need to consume the entire List. If you already know the list to be non-empty, this should be the obvious choice.

But not all tail operations take constant time like a List: Scala Collections Performance

Michael Zajac
  • 55,144
  • 7
  • 113
  • 138
1

You can take a view of a traversable. You can slice the TraversableView lazily.

The initial star is because the REPL prints some output.

scala> val t: Traversable[Int] = Stream continually { println("*"); 42 }
*
t: Traversable[Int] = Stream(42, ?)

scala> t.view.slice(0,2).size
*
res1: Int = 2

scala> val t: Traversable[Int] = Stream.fill(1) { println("*"); 42 }
*
t: Traversable[Int] = Stream(42, ?)

scala> t.view.slice(0,2).size
res2: Int = 1

The advantage is that there is no intermediate collection.

scala> val t: Traversable[_] = Map((1 to 10) map ((_, "x")): _*)
t: Traversable[_] = Map(5 -> x, 10 -> x, 1 -> x, 6 -> x, 9 -> x, 2 -> x, 7 -> x, 3 -> x, 8 -> x, 4 -> x)

scala> t.take(2)
res3: Traversable[Any] = Map(5 -> x, 10 -> x)

That returns an unoptimized Map, for instance:

scala> res3.getClass
res4: Class[_ <: Traversable[Any]] = class scala.collection.immutable.HashMap$HashTrieMap

scala> Map(1->"x",2->"x").getClass
res5: Class[_ <: scala.collection.immutable.Map[Int,String]] = class scala.collection.immutable.Map$Map2
som-snytt
  • 39,429
  • 2
  • 47
  • 129
0

What about pattern matching?

 itrbl match { case _::Nil => "one"; case _=>"more" }
Azzie
  • 366
  • 1
  • 4
  • Isn't this the same as using .tail.nonEmpty? Sure, you could argue it's more elegant... – Lorenzo Dematté Sep 25 '14 at 14:24
  • Actually, I think it should be more like calling head twice which translates to itetable.iterator.next while tail.isEmpty looks like iterator.next iterator.hasNext – Azzie Sep 25 '14 at 15:05