【问题标题】:What function is applied when a List is printed?打印列表时应用什么功能?
【发布时间】:2021-06-24 11:29:39
【问题描述】:

当我执行这段代码时:

object CustomList extends App{

  sealed trait CustomList[+A]
  case object Nil extends CustomList[Nothing]
  case class Cons[+A](head: A, tail: CustomList[A]) extends CustomList[A]

  def apply[A](as: A*): CustomList[A] = // Variadic function syntax
    if (as.isEmpty) Nil
    else Cons(as.head, apply(as.tail: _*))

  val customList : CustomList[Int] = CustomList(1 ,2 ,3)
  println(customList)

  val list : List[Int] = List(1,2,3)
  println(list)
}

打印以下内容:

Cons(1,Cons(2,Cons(3,Nil)))
List(1, 2, 3)

CustomList 是我自己实现的列表数据结构,当函数println 应用于它时,我试图打印类似的输出。所以应该打印CustomList(1, 2, 3) 而不是Cons(1,Cons(2,Cons(3,Nil))) 打印

println 的参数将参数转换为字符串并打印:

/**
 * Prints an Object and then terminate the line.  This method calls
 * at first String.valueOf(x) to get the printed object's string value,
 * then behaves as
 * though it invokes {@link #print(String)} and then
 * {@link #println()}.
 *
 * @param x  The {@code Object} to be printed.
 */
public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

但我不确定如何创建正确的类型以以相同格式打印CustomList

是否有我需要实施的方法来实现相同的行为?

【问题讨论】:

  • 您将看到 ConsNil 案例类/对象提供的toString 的当前实现。您可以覆盖它,一种常见的方法是定义一个 mkString 方法,并在主要特征中执行此操作:override def toString(): String = this.mkString("CustomList(", ", ", ")") 这或多或少与 stdlib 列表相同。请注意,由于 String 连接在 JVM 中非常慢,这是恕我直言可变性非常受欢迎的情况之一,我将使用 StringBuilder 来实现 @987654333 @方法。
  • 啊我忘了,override 也应该是final 以避免意外。

标签: scala tostring


【解决方案1】:

您可能希望在打印之前将CustomList 转换为String

这是一种非常非最佳方式,您可以这样做:

def show[A](list: CustomList[A]): String = {
    def go(res: String)(as: CustomList[A]): String =
      as match {
        case Nil              => res ++ ")"
        case Cons(head, Nil)  => res ++ head.toString ++ ")"
        case Cons(head, tail) => go(res ++ head.toString ++ ", ")(tail)
      }

    go("CustomList(")(list)
  }

我发现 scala 中的大量字符串连接是一个可怕的想法(即解决项目欧拉问题并让我的机器花费 AGES 来完成计算)。但是我认为上述功能的精神可能会有所帮助?

【讨论】:

    【解决方案2】:

    您可以覆盖toString 方法

    Println 结果:

    // Empty
    CustomList()
    // One Element
    CustomList("Test")
    // Many Elements
    CustomList(false, false, true, true, false)
    

    对于Cons一个递归辅助方法可以构建字符串

    
    case object Nil extends CustomList[Nothing] {
        override def toString: String = "CustomList()"
    }
    
    case class Cons[+A](head: A, tail: CustomList[A]) extends CustomList[A] {
        def toStringAux: String = {
            tail match {
                case Nil => s"$head"
                case cons: Cons[_] => s"$head, ${cons.toStringAux}"
            }
        }
        
        override def toString: String = {
            s"CustomList($toStringAux)"
        }
    }
    

    对于较大的列表,像这样构建字符串会影响性能,并可能导致堆栈溢出。

    要解决这个问题,您可以对Cons 使用@tailrec 注释,但它需要稍微不同的签名:

    
    // In Cons
    @tailrec
    final def toStringAux(result: String = s"$head"): String = {
        tail match {
            case Nil => s"$result"
            case cons: Cons[A] => cons.toStringAux(s"$result, ${cons.head}")
        }
    }
    
    override def toString: String = {
        s"CustomList(${toStringAux()})"
    }
    
    

    这将自动展开递归,不会创建额外的堆栈。

    编辑:如果字符串创建是一个问题,只需将结果替换为StringBuilder

    // In Cons
    @tailrec
    final def toStringAux(result: StringBuilder = new StringBuilder()): String = {
        result.append(head)
        tail match {
            case Nil => result.mkString(", ")
            case cons: Cons[A] => cons.toStringAux(result)
        }
    }
    

    编辑 2:

    如果要保存字符串结果,即生成一次字符串,懒惰:

    sealed trait CustomList[+A] {
        lazy val stringValue: String
            // Define toString once, based on a lazy stringValue
        override def toString: String = stringValue
    }
    
    case object Nil extends CustomList[Nothing] {
            // Default String
        override lazy val stringValue: String = "CustomList()"
    }
    
    case class Cons[+A](head: A, tail: CustomList[A]) extends CustomList[A] {
            // Auxiliary String (all the values), with commas separating them
        lazy val auxStringValue: String = tail match {
            case Nil => s"$head"
            case cons: Cons[A] => s"$head, ${cons.auxStringValue}"
        }
    
        override lazy val stringValue: String = s"CustomList($auxStringValue)"
    }
    

    【讨论】:

      猜你喜欢
      • 2012-09-15
      • 2020-11-22
      • 2017-01-25
      • 2016-12-20
      • 1970-01-01
      • 1970-01-01
      • 2018-02-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多