[英]Scala: Most concise conversion of a CSS color string to RGB integers
我試圖獲取CSS顏色字符串的RGB值,想知道我的代碼有多好:
object Color {
def stringToInts(colorString: String): Option[(Int, Int, Int)] = {
val trimmedColorString: String = colorString.trim.replaceAll("#", "")
val longColorString: Option[String] = trimmedColorString.length match {
// allow only strings with either 3 or 6 letters
case 3 => Some(trimmedColorString.flatMap(character => s"$character$character"))
case 6 => Some(trimmedColorString)
case _ => None
}
val values: Option[Seq[Int]] = longColorString.map(_
.foldLeft(Seq[String]())((accu, character) => accu.lastOption.map(_.toSeq) match {
case Some(Seq(_, _)) => accu :+ s"$character" // previous value is complete => start with succeeding
case Some(Seq(c)) => accu.dropRight(1) :+ s"$c$character" // complete the previous value
case _ => Seq(s"$character") // start with an incomplete first value
})
.flatMap(hexString => scala.util.Try(Integer.parseInt(hexString, 16)).toOption)
// .flatMap(hexString => try {
// Some(Integer.parseInt(hexString, 16))
// } catch {
// case _: Exception => None
// })
)
values.flatMap(values => values.size match {
case 3 => Some((values.head, values(1), values(2)))
case _ => None
})
}
}
// example:
println(Color.stringToInts("#abc")) // prints Some((170,187,204))
您可以在https://scastie.scala-lang.org上運行該示例
我最不確定的那部分代碼是
foldLeft
的match
foldLeft
(使用字符串插值是一個好主意,還是可以在沒有字符串插值的情況下將代碼寫得更短?) Integer.parseInt
與
try
結合使用(我可以在Scala中使用更漂亮的替代方法嗎?)
但是我希望代碼的大部分內容都是可改進的。 除了com.itextpdf
之外,我不想引入新的庫來縮短我的代碼,但是可以選擇使用com.itextpdf
函數。 ( stringToInts
的結果將被轉換為new com.itextpdf.kernel.colors.DeviceRgb(...)
,因此無論如何我已經安裝了com.itextpdf
。)
定義預期功能的測試:
import org.scalatest.{BeforeAndAfterEach, FunSuite}
class ColorTest extends FunSuite with BeforeAndAfterEach {
test("shorthand mixed case color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#Fa#F")
val expected = (255, 170, 255)
assert(actual === Some(expected))
}
test("mixed case color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a06")
val expected = (29, 154, 6)
assert(actual === Some(expected))
}
test("too short long color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a6")
assert(actual === None)
}
test("too long shorthand color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9a")
assert(actual === None)
}
test("invalid color") {
val actual: Option[(Int, Int, Int)] = Color.stringToInts("#1D9g06")
assert(actual === None)
}
}
我想出了這個有趣的答案(未經測試)。 我想對您最大的幫助是使用foldLeft
sliding(2,2)
而不是foldLeft
。
def stringToInts(colorString: String): Option[(Int, Int, Int)] = {
val trimmedString: String => String = _.trim.replaceAll("#", "")
val validString: String => Option[String] = s => s.length match {
case 3 => Some(s.flatMap(c => s"$c$c"))
case 6 => Some(s)
case _ => None
}
val hex2rgb: String => List[Option[Int]] = _.sliding(2, 2).toList
.map(hex => Try(Integer.parseInt(hex, 16)).toOption)
val listOpt2OptTriple: List[Option[Int]] => Option[(Int, Int, Int)] = {
case Some(r) :: Some(g) :: Some(b) :: Nil => Some(r, g, b)
case _ => None
}
for {
valid <- validString(trimmedString(colorString))
rgb = hex2rgb(valid)
answer <- listOpt2OptTriple(rgb)
} yield answer
}
在編寫此答案時,其他答案不能正確處理rgb()
, rgba()
和命名顏色的情況。 以哈希( #
)開頭的顏色字符串只是交易的一部分。
由於將iText7
作為依賴項,而iText7
具有pdfHTML
附加組件,因此,解析CSS
顏色的邏輯顯然必須在iText7
並且更重要的是,它必須處理各種CSS顏色案例。 問題僅在於找到正確的地方。 幸運的是,此API是公開的且易於使用。
你感興趣的方法是WebColors.getRGBAColor()
從包com.itextpdf.kernel.colors
它接受一個CSS
顏色字符串返回一個4元件陣列R
G
, B
, A
值(最后一個代表α,即透明度)。
您可以使用這些值立即創建顏色(Java代碼):
float[] rgbaColor = WebColors.getRGBAColor("#ababab");
Color color = new DeviceRgb(rgbaColor[0], rgbaColor[1], rgbaColor[2]);
在Scala中,它一定類似於
val rgbaColor = WebColors.getRGBAColor("#ababab");
val color = new DeviceRgb(rgbaColor(0), rgbaColor(1), rgbaColor(2));
這是您功能的可能實現
def stringToInts(css: String): Option[(Int, Int, Int)] = {
def cssColour(s: String): Int = {
val v = Integer.parseInt(s, 16)
if (s.length == 1) v*16 + v else v
}
val s = css.trim.replaceAll("#", "")
val l = s.length/3
if (l > 2 || l*3 != s.length) {
None
} else {
Try{
val res = s.grouped(l).map(cssColour).toSeq
(res(0), res(1), res(2))
}.toOption
}
}
如果實現返回Option[List[Int]]
或什至Try[List[Int]]
以在失敗的情況下保留錯誤,則實現會更干凈。
如果您正在尋找簡明扼要的方法,那么也許此解決方案可以完成任務(以效率為代價,稍后再介紹):
import scala.util.Try
def parseLongForm(rgb: String): Try[(Int, Int, Int)] =
Try {
rgb.replace("#", "").
grouped(2).toStream.filter(_.length == 2).
map(Integer.parseInt(_, 16)) match { case Stream(r, g, b) => (r, g, b) }
}
def parseShortForm(rgb: String): Try[(Int, Int, Int)] =
parseLongForm(rgb.flatMap(List.fill(2)(_)))
def parse(rgb: String): Option[(Int, Int, Int)] =
parseLongForm(rgb).orElse(parseShortForm(rgb)).toOption
簡而言之,這里的每個功能實際上都是單行代碼(如果您現在正在尋找的是這種功能)。
核心是函數parseLongForm
,它試圖通過以下方式解析長6個字符的長格式:
#
字符 parseLongForm
表示使用Try
失敗的可能性,這使我們可以在parseInt
或模式匹配失敗時正常失敗。
parse
調用parseLongForm
,如果結果失敗( orElse
),則調用parseShortForm
,在將每個字符加倍后,它將嘗試相同的方法。
它成功通過了您提供的測試(榮譽,這使解決問題變得更加容易)。
這種方法的主要問題是,即使從一開始就很清楚它是行不通的,您仍將嘗試解析長格式。 因此,如果這可能是您使用案例的性能瓶頸,則不建議使用此代碼。 另一個問題是,盡管這或多或少是隱藏的,但我們在流程控制中使用異常(這也會損害性能)。
美好的事情是簡潔,我認為是可讀性(就像我說的那樣,代碼以相當簡單的方式映射到問題上,但是可讀性當然是旁觀者的定義)。
您可以在Scastie上找到此解決方案。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.