Olcay Karaduman
Olcay Karaduman

Reputation: 39

Get each field as an option of an optional object

I have a case class looks like this

case class EmotionData(
      fearful: Double,
      angry: Double,
      sad: Double,
      neutral: Double,
      disgusted: Double,
      surprised: Double,
      happy: Double
    )

I receive an Option[EmotionData] and I need each emotion data as an Option[Double].

What I did was:

val (fearful, angry, sad, neutral, disgusted, surprised, happy) = videoResult.emotion match {
        case Some(e) => (Some(e.fearful), Some(e.angry), Some(e.sad), Some(e.neutral), Some(e.disgusted), Some(e.surprised), Some(e.happy))
        case None => (None, None, None, None, None, None, None)
      }

This way I have each field as an Option[Double] value.

But isn't there a way to do this in Scala where I can iterate through all fields of an object and extract them without rewriting each field?

Upvotes: 1

Views: 249

Answers (4)

jwvh
jwvh

Reputation: 51271

Here's a slightly different approach that might be, perhaps, a little more palatable.

val vidEmo :Option[EmotionData] = videoResult.emotion

val (fearful, angry, sad, neutral, disgusted, surprised, happy) =
  (vidEmo.map(_.fearful)
  ,vidEmo.map(_.angry)
  ,vidEmo.map(_.sad)
  ,vidEmo.map(_.neutral)
  ,vidEmo.map(_.disgusted)
  ,vidEmo.map(_.surprised)
  ,vidEmo.map(_.happy))

But really, you should just keep vidEmo around and extract what you need when you need it.

Upvotes: 4

Alejandro Alcalde
Alejandro Alcalde

Reputation: 6220

Maybe this?

case class EmotionData(
                        fearful: Double,
                        angry: Double,
                        sad: Double,
                        neutral: Double,
                        disgusted: Double,
                        surprised: Double,
                        happy: Double
                      )

val s = Some(EmotionData(1,2,3,4,5,6,7))
val n:Option[EmotionData] = None

val emotionsOpt = s.map { x =>
  x.productIterator.toVector.map(x => Some(x.asInstanceOf[Double]))
}.getOrElse(List.fill(7)(None))

// Or if you want an iterator:
val emotionsOptItr = n.map { x =>
  x.productIterator.map(x => Some(x.asInstanceOf[Double]))
}.getOrElse(List.fill(7)(None))


println(emotionsOpt)
println(emotionsOptItr)

Which results in:

Vector(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7))
List(None, None, None, None, None, None, None)

Upvotes: 0

Tim
Tim

Reputation: 27356

Yes, there is a way to iterate through the fields of an object by using productIterator. It would look something like this:

val List(fearful, angry, sad, neutral, disgusted, surprised, happy) =
  videoResult.emotion.map(_.productIterator.map(f => Some(f.asInstanceOf[Double])).toList)
    .getOrElse(List.fill(7)(None))

As you can see, this isn't much better than what you already have, and is more prone to error. The problem is that the number and order of fields is explicit in the result you have specified, so there are limits to how much this can be automated. And this only works because the type of all the fields is the same.

Personally I would keep the value as Option[EmotionData] as long as possible, and pick out individual values as needed, like this:

val opt = videoResult.emotion

val fearful = opt.map(_.fearful) // Option[Double]
val angry = opt.map(_.angry) // Option[Double]
val sad = opt.map(_.sad) // Option[Double]

val happy = opt.fold(0)(_.happy) // Double, default is 0 if opt is None

val ok = opt.forall(e => e.happy > e.sad) // True if emotion not set or more happy than sad

val disgusted = opt.exists(_.disgusted > 1.0) // True if emotion is set and disgusted value is large

Upvotes: 1

Raman Mishra
Raman Mishra

Reputation: 2686

You can do Something like that:

val defaultEmotionData=(0.0,0.0,0.0,0.0,0.0,0.0,0.0)

object Solution1 extends App{
 case class EmotionData(
                          fearful: Double,
                          angry: Double,
                          sad: Double,
                          neutral: Double,
                          disgusted: Double,
                          surprised: Double,
                          happy: Double
                        )

  case class EmotionDataOption(
                                fearfulOpt: Option[Double],
                                angryOpt: Option[Double],
                                sadOpt: Option[Double],
                                neutralOpt: Option[Double],
                                disgustedOpt: Option[Double],
                                surprisedOpt: Option[Double],
                                happyOpt: Option[Double]
                              )

  val emotion = Some(EmotionData(1.2, 3.4, 5, 6, 7.8, 3, 12))

  val ans: EmotionDataOption = emotion.getOrElse(defaultEmotionData).toOption

  implicit class ToOption(emotionData: EmotionData) {
    def toOption = EmotionDataOption(Some(emotionData.fearful), Some(emotionData.angry), Some(emotionData.sad), Some(emotionData
        .neutral), Some(emotionData.disgusted), Some(emotionData.surprised), Some(emotionData.happy))
  }
}

Now where ever you will have an object of type EmotionData you can use toOption on that and it will convert it's values into EmotionDataOption which will have values Option[Double].

If you will return Tuple7 then it will be tough to access values, that's why I think converting it into another case class EmotionDataOption is a good idea and you will be able to access the values easily with the parameter name.

Upvotes: -1

Related Questions