ocwirk
ocwirk

Reputation: 1089

zipping lists with an optional list to construct a list of object in Scala

I have a case class like this:

case class Metric(name: String, value: Double, timeStamp: Int)

I receive individual components to build metrics in separate lists and zip them to create a list of Metric objects.

def buildMetric(names: Seq[String], values: Seq[Double], ts: Seq[Int]): Seq[Metric] = {
    (names, values, ts).zipped.toList map {
         case (name, value, time) => Metric(name, value, time)
    }
}

Now I need to add an optional parameter to both buildMetric function and Metric class.

case class Metric(name: String, value: Double, timeStamp: Int, type: Option[Type])

&

def buildMetric(names: Seq[String], values: Seq[Double], ts: Seq[Int], types: Option[Seq[Type]]): Seq[Metric]

The idea is that we some times receive a sequence of the type which if present matches the length of names and values lists. I am not sure how to modify the body of buildMetric function to create the Metric objects with type information idiomatically. I can think of a couple of approaches.

  1. Do an if-else on types.isDefined and then zip the types with types.get with another list in one condition and leave as above in the other. This makes me write the same code twice.

  2. The other option is to simply use a while loop and create a Metric object with types.map(_(i)) passed a last parameter.

So far I am using the second option, but I wonder if there is a more functional way of handling this problem.

Upvotes: 2

Views: 367

Answers (3)

Krzysztof Atłasik
Krzysztof Atłasik

Reputation: 22595

You could just use pattern matching on types:

def buildMetric(names: Seq[String], values: Seq[Double], ts: Seq[Int], types: Option[Seq[Type]]): Seq[Metric] = {

  types match {
    case Some(types) => names.zip(values).zip(ts).zip(types).map {
        case (((name, value), ts,), t) => Metric(name, value, ts, Some(t))
    }
    case None => (names, values, ts).zipped.map(Metric(_, _, _, None))
  }
}

Upvotes: 3

Tim
Tim

Reputation: 27356

The first option can't be done because zipped only works with tuples of 3 or fewer elements. The second version might look like this:

def buildMetric(names: Seq[String], values: Seq[Double], ts: Seq[Int], types: Option[Seq[Type]]): Seq[Metric] =
  for {
    (name, i) <- names.zipWithIndex
    value <- values.lift(i)
    time <- ts.lift(i)
    optType = types.flatMap(_.lift(i))
  } yield {
    Metric(name, value, time, optType)
  }

Upvotes: 3

Ivan Kurchenko
Ivan Kurchenko

Reputation: 4063

One more option from my point of view, if you would like to keep this zipped approach - convert types from Option[Seq[Type]] to Seq[Option[Type]] with same length as names filled with None values in case if types is None as well:

val optionTypes: Seq[Option[Type]] = types.fold(Seq.fill(names.length)(None: Option[Type]))(_.map(Some(_)))
// Sorry, Did not find `zipped` for Tuple4 case
names.zip(values).zip(ts).zip(optionTypes).toList.map {
  case (((name, value), time), optionType) => Metric(name, value, time, optionType)
}

Hope this helps!

Upvotes: 3

Related Questions