少年易酔學難成

IT/技術的な話題について書きます

{1,2,3,5,7}みたいなリストを"1-3,5,7"に変換する

電車で移動中にタイムラインに流れて来てちょっと面白そうだということで実装してみた。

スマートに書けるかな、と思ったけどそんな事はなかった。

object ListConverter {
  /**
    * {1,2,3,5,7}みたいな配列(またはリスト)を“1-3,5,7”に変換する
    */
  def toSequenceString(list: List[Int]): String = {
    val sorted = list.sorted
    sorted.lastOption match {
      case None       => ""
      case Some(last) =>
        val (grouped, _) = sorted.init.foldRight((List(List(last)), last)) {
          case (cr, (h :: t, prv)) if prv - 1 == cr => ((cr :: h) :: t, cr)
          case (cr, (acc, _))                       => (List(cr) :: acc, cr)
        }
        grouped.map {
          case Nil      => ""
          case h :: Nil => h.toString
          case l        => s"${l.head}-${l.last}"
        }.mkString(",")
    }
  }
  /**
    * “1-3,5,7”をList(1,2,3,5,7)に変換する(toSequenceStringの逆変換)
    */
  def sequenceStringToList(str: String): Either[Throwable, List[Int]] = {
    def map2[E, A, B, C](e1: Either[E, A], e2: Either[E, B])(f: (A, B) => C): Either[E, C] = for {
      a <- e1
      b <- e2
    } yield f(a, b)

    str.split(",").map { s =>
      s.split("-") match {
        case Array(from, to) => Try((from.toInt to to.toInt).toList).toEither
        case Array(numStr)   => Try(List(numStr.toInt)).toEither
        case _               => Left(new IllegalArgumentException(s"パースできない文字列: $str"))
      }
    }.foldRight[Either[Throwable, List[Int]]](Right(List()))(map2(_, _)(_ ++ _))
  }
}

良いやり方を思い付いたら、また公開する。