Michael
Michael

Reputation: 42100

Vertical Histogram in Scala

This is a simple coding exercise. Given a list of integers output a vertical histogram showing how many of each number are in the input list. If the lis is empty output an empty string.

List(5, 2, 3)

*  
*  
* *
***
*** 

I wrote a function as follows:

def hist(l: List[Int]) = if(l.isEmpty) "" 
  else Range(l.max, 0, -1).map(i => l.map(x => if(i <= x) "*" else " ").mkString)

How would you solve this problem ?

P.S. Forgot to say you need to output the resulting strings

val r = hist(List(5, 2, 3))
r.foreach(s => println(s))

Upvotes: 2

Views: 492

Answers (4)

Electric Coffee
Electric Coffee

Reputation: 12124

For the sake of readability I'd do this instead:

def hist: List[Int] => IndexedSeq[List[String]] = {
  case Nil => Vector(List("")) 
  case xs  => Range(xs.max, 0, -1).map(i => xs.map(x => if(i <= x) "*" else " "))
}

As I believe cases are easier to read and reason about than if statements

Running it looks like this:

scala> hlist(List(5, 2, 3)).foreach(x => println(x.mkString))
*
*
* *
***
***

Upvotes: 2

elm
elm

Reputation: 20415

Return type from empty list check differs from the general case; consider this update,

def hist(l: List[Int]): IndexedSeq[List[String]] = 
  if(l.isEmpty) 
    Vector(List("")) 
  else 
    Range(l.max, 0, -1).map(i => l.map(x => if (i <= x) "*" else " "))

Then for List(5, 2, 3),

hist(List(5, 2, 3))
res: Vector(List(*, " ", " "), List(*, " ", " "), List(*, " ", *), List(*, *, *), List(*, *, *))

and so for vertical printing,

hist(List(5, 2, 3)).foreach(v => println(v.mkString))

*  
*  
* *
***
***

Update

Let val a = List(5, 2, 3), then a one-liner is like this,

a.map(v => " " * (a.max - v) + "*" * v).transpose.foreach(x => println(x.mkString))

Upvotes: 1

Alexander Arendar
Alexander Arendar

Reputation: 3435

A bit more verbose but I believe more Scala-way:

object Histogram {
  def hist(list:List[Int]):List[String] ={
    val max = list.max
    val matrix = list match{
      case Nil => Nil
      case _ =>{
        list map (l => "*" * l)
      }.map{
        s => s + (" " * (max - s.length))
      }
    }

    (0 to max-1).reverse map (
      i => (for (s <- matrix) yield s(i).toString).mkString
    ) toList
  }

  def main(args:Array[String]):Unit ={
    hist(List(5,2,3,7,4)).foreach(println)
  }
}

Few good points: 1) you don't need to use if l.isEmpty since Scala has Nil for that

2) pattern matching is always good for decomposition

3) you need to have simple List[String] as a result and not the IndexedSeq[List[String]], so use IndexedSeq.toString method to convert

Upvotes: 0

Chris Martin
Chris Martin

Reputation: 30746

Here's how I'd write the function you implemented:

def hist(xs: Seq[Int]): String =
  xs.map(i => Seq.fill(i)('*').padTo(xs.max, ' '))
    .transpose.reverse.map(_.mkString).mkString("\n")

But wouldn't a histogram of Seq(5,2,3) look more like

 ** *
12345

?

Upvotes: 4

Related Questions