14

I have a requirement to concatenate two potentially empty address lines into one (with a space in between the two lines), but I need it to return a None if both address lines are None (this field is going into an Option[String] variable). The following command gets me what I want in terms of the concatenation:

Seq(myobj.address1, myobj.address2).flatten.mkString(" ")

But that gives me an empty string instead of a None in case address1 and address2 are both None.

1
  • They are both Option[String] and the resultant value will also go into an Option[String]. Commented Feb 4, 2015 at 21:49

9 Answers 9

16

This converts a single string to Option, converting it to None if it's either null or an empty-trimmed string:

(kudos to @Miroslav Machura for this simpler version)

Option(x).filter(_.trim.nonEmpty)

Alternative version, using collect:

Option(x).collect { case x if x.trim.nonEmpty => x }
Sign up to request clarification or add additional context in comments.

1 Comment

This is definitely most idiomatic approach. One tiny simplification could be: Option(x).filter(_.trim.nonEmpty)
6

Assuming:

val list1 = List(Some("aaaa"), Some("bbbb"))
val list2 = List(None, None)

Using plain Scala:

scala> Option(list1).map(_.flatten).filter(_.nonEmpty).map(_.mkString(" "))
res38: Option[String] = Some(aaaa bbbb)

scala> Option(list2).map(_.flatten).filter(_.nonEmpty).map(_.mkString(" "))
res39: Option[String] = None

Or using scalaz:

import scalaz._; import Scalaz._
scala> list1.flatten.toNel.map(_.toList.mkString(" "))
res35: Option[String] = Some(aaaa bbbb)

scala> list2.flatten.toNel.map(_.toList.mkString(" "))
res36: Option[String] = None

Comments

4

Well, In Scala there is Option[ T ] type which is intended to eliminate various run-time problems due to nulls.

So... Here is how you use Options, So basically a Option[ T ] can have one of the two types of values - Some[ T ] or None

// A nice string
var niceStr = "I am a nice String"

// A nice String option
var noceStrOption: Option[ String ] = Some( niceStr )

// A None option
var noneStrOption: Option[ String ] = None

Now coming to your part of problem:

// lets say both of your myobj.address1 and myobj.address2 were normal Strings... then you would not have needed to flatten them... this would have worked..
var yourString = Seq(myobj.address1, myobj.address2).mkString(" ")

// But since both of them were Option[ String ] you had to flatten the Sequence[ Option[ String ] ] to become a Sequence[ String ]
var yourString = Seq(myobj.address1, myobj.address2).flatten.mkString(" ")

//So... what really happens when you flatten a Sequence[ Option[ String ] ] ?

// Lets say we have Sequence[ Option [ String ] ], like this
var seqOfStringOptions = Seq( Some( "dsf" ), None, Some( "sdf" ) )

print( seqOfStringOptions )
// List( Some(dsf), None, Some(sdf))

//Now... lets flatten it out...
var flatSeqOfStrings = seqOfStringOptions.flatten

print( flatSeqOfStrings )
// List( dsf, sdf )

// So... basically all those Option[ String ] which were None are ignored and only Some[ String ] are converted to Strings.

// So... that means if both address1 and address2 were None... your flattened list would be empty.

// Now what happens when we create a String out of an empty list of Strings...

var emptyStringList: List[ String ] = List()
var stringFromEmptyList = emptyStringList.mkString( " " )
print( stringFromEmptyList ) 
// ""
// So... you get an empty String

// Which means we are sure that yourString will always be a String... though it can be empty (ie - "").

// Now that we are sure that yourString will alwyas be a String, we can use pattern matching to get out Option[ String ] .

// Getting an appropriate Option for yourString
var yourRequiredOption: Option[ String ] = yourString match {
    // In case yourString is "" give None.
    case "" => None
    // If case your string is not "" give Some[ yourString ] 
    case someStringVal => Some( someStringVal )
}

6 Comments

The second-to-last line in your solution should have parentheses instead of square brackets.
why are you using vars instead of vals?
@dk14 for no reason at all... :p
@SarveshKumarSingh I need to rephrase it: "OMG! why are you using var instead of val??!!!oneone 0_o". All you can respond to that is "For Great Justice!" :)
@dk14 For justice indeed... all those poor "vars"... hated by all scala programmers...
|
0

You might also use the reduce method here:

val mySequenceOfOptions = Seq(myAddress1, myAddress2, ...)

mySequenceOfOptions.reduce[Option[String]] {
    case(Some(soFar), Some(next)) => Some(soFar + " " + next)
    case(None, next) => next
    case(soFar, None) => soFar
}

Comments

0

Here's a function that should solve the original problem.

def mergeAddresses(addr1: Option[String],
                   addr2: Option[String]): Option[String] = {
  val str = s"${addr1.getOrElse("")} ${addr2.getOrElse("")}"
  if (str.trim.isEmpty) None else Some(str)
}

Comments

0

the answer from @dk14 is actually incorrect/incomplete because if list2 has a Some("") it will not yield a None because the filter() evaluates to an empty list instead of a None ( ScalaFiddle link)

val list2 = List(None, None, Some(""))

// this yields Some()
println(Option(list2).map(_.flatten).filter(_.nonEmpty).map(_.mkString(" ")))  

but it's close. you just need to ensure that empty string is converted to a None so we combine it with @juanmirocks answer (ScalaFiddle link):

val list1 = List(Some("aaaa"), Some("bbbb"))
val list2 = List(None, None, Some(""))

// yields Some(aaaa bbbbb)
println(Option(list1.map(_.collect { case x if x.trim.nonEmpty => x }))
  .map(_.flatten).filter(_.nonEmpty).map(_.mkString(" ")))
 
// yields None 
println(Option(list2.map(_.collect { case x if x.trim.nonEmpty => x }))
  .map(_.flatten).filter(_.nonEmpty).map(_.mkString(" ")))

Comments

0

I was searching a kind of helper function like below in the standard library but did not find yet, so I defined in the meantime:

 def string_to_Option(x: String): Option[String] = {
    
    if (x.nonEmpty)
      Some(x)
    else 
      None    
  }

with the help of the above you can then:

import scala.util.chaining.scalaUtilChainingOps

object TEST123 {

  def main(args: Array[String]): Unit = {

    val address1 = ""
    val address2 = ""

    val result =
      Seq(
        address1 pipe string_to_Option,
        address2 pipe string_to_Option
      ).flatten.mkString(" ") pipe string_to_Option

    println(s"The result is «${result}»") 
    // prints out: The result is «None»
  }
}

Comments

0

With Scala 2.13:

Option.unless(address.isEmpty)(address)

For example:

val address = "foo" 
Option.unless(address.isEmpty)(address) // Some("foo")
val address = "" 
Option.unless(address.isEmpty)(address) // None

Comments

0

A solution that looks analogous to existing methods like toIntOption etc:

implicit class EmptyToNone(s: String):
  def toOption: Option[String] = if s.isEmpty then None else Some(s)

Examples:

scala> "".toOption
val res0: Option[String] = None

scala> "foo".toOption
val res1: Option[String] = Some(foo)

scala> "foo".toIntOption
val res2: Option[Int] = None

scala> "42".toIntOption
val res3: Option[Int] = Some(42)

(tested with Scala 3.2.2)

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.