【问题标题】:Cartesian product by pattern matching模式匹配的笛卡尔积
【发布时间】:2021-06-17 07:59:28
【问题描述】:

我是大学的学生,正在学习 Scala。我有一个练习是使用模式匹配而不是使用列表上的任何操作来制作笛卡尔积。

def find[A,B](aList: List[A], bList: List[B]): Set[(A,B)] = {
  def findHelper[A,B](aList: List[A], bList: List[B], acc: Set[(A,B)]): Set[(A,B)] = {
    (aList, bList) match {
      case (head1 :: tail1, head2::tail2) => {
        findHelper[A, B](tail1, tail2, acc ++ Set((head1, head2)))
      }
      case (List(), head2::tail2) => acc
      case (List(), List()) => acc
    }
  }
  findHelper(aList, bList, Set())
}
println(find(List(1,2,3,4,5), List(7,8,9,10,11)))

结果我得到的只是(1,7), (2,8) 等。 我显然知道为什么,但我不知道如何将每一对与自身结合起来。我不知道该怎么办,:: 操作后我的第一个列表为空。

【问题讨论】:

  • 您期望输入的回报是多少?
  • 是的,它应该删除重复的对。我的预期输出是每一对 (a,b),其中 a 来自 aList,b 来自 bList
  • @squall 你的问题是你同时迭代两个列表,而你应该为第一个列表中的每个元素迭代第二个列表一次。
  • 您可能是对的,但我不知道如何以其他方式进行迭代。我也尝试过 head1::tail1, bList,但无论如何我都无法使用它。
  • @squall 你需要两个递归函数,一个用于外部列表,一个用于内部列表。

标签: scala pattern-matching cartesian


【解决方案1】:

如 cmets 中所述,您只需要遍历一个 List 一次,但对于第一个 List 中的每个项目,另一个 List 将遍历一次。

这是一种解决方法。

def cartPrd[A,B](aList: List[A], bList: List[B]): Set[(A,B)] = {
  def getAs(as: List[A]): List[(A,B)] = as match {
    case Nil => Nil
    case hd::tl => getBs(hd, bList) ++ getAs(tl)
  }
  def getBs(a: A, bs: List[B]): List[(A,B)] = bs match {
    case Nil => Nil
    case hd::tl => (a,hd) :: getBs(a,tl)
  }
  getAs(aList).toSet
}

cartPrd(List(1,2,1), List('A','B','B'))
//res0: Set[(Int, Char)] = Set((1,A), (1,B), (2,A), (2,B))

通过简单的for 理解,这一切变得容易得多。

【讨论】:

    【解决方案2】:

    正如 cmets 中所述,问题在于您同时迭代两个列表,而您需要为第一个列表的每个元素迭代第二个列表一次。

    def cartesianProduct[A, B](as: List[A], bs: List[B]): Set[(A, B)] = {
      @annotation.tailrec
      def loop(remainingAs: List[A], remainingBs: List[B], acc: Set[(A, B)]): Set[(A, B)] =
        (remainingAs, remainingBs) match {      
          case (remainingAs @ (a :: _), b :: tailB) =>
            loop(remainingAs, remainingBs = tailB, acc + (a -> b))
          
          case (_ :: tailA, Nil) =>
            loop(remainingAs = tailA, remainingBs = bs, acc)
          
          case (Nil, _) =>
            acc
        }
      
      loop(remainingAs = as, remainingBs = bs, acc = Set.empty)
    }
    

    那条线是什么意思? " case (remainingAs @ (a :: ), b :: tailB) " 我的意思是,"@" 和 (a :: _) 是做什么的?

    语法case foo @ bar 表示如果您的模式匹配与模式bar 匹配,则将其分配给新变量foo

    所以,在这种情况下,我是说如果 as 的列表不为空 (即是一个缺点 :: 然后将其头部作为一个新变量 a 和整个列为新变量remainingAs。请注意,在这种情况下,根本不需要它,因为我可以使用之前的 remainingAs 进行模式匹配,它还包含整个列表;我个人只是喜欢在case 部分定义我将要使用的所有变量,但您可以只使用case ((a :: _), b :: tailB),代码将按预期编译和工作。

    你可能做了我需要的事情:remainingAs 和 as 不同的值,你只需将完整的 List 保存在 as/bs 值中,当它为空时,你只是再次使用完整的?例如这里:“case (::tailA, Nil) => loop(remainingAs = tailA, remainingBs = bs, acc)”

    我不完全确定我是否理解你在说什么,但你是对的,我会跟踪原始的第二个列表,以便当我用完它时,我可以从头开始。

    所以,正如你所看到的,代码有三种情况,可以或多或少地阅读为:

    1. 虽然第一个列表不为空,但请抓住它的头。
    2. 然后通过获取第二个列表的头部并将两个头部的对添加到集合中来迭代第二个列表,并使用第二个列表的尾部继续该过程。
    3. 当您到达第二个列表的尾部时,然后从第一个列表的尾部重新开始,并将第二个列表重新启动为其原始形式。
    4. 继续该过程直到第一个列表为空,此时返回当前累加器。

    注意:我个人认为有两个递归函数的版本更容易理解。因为这看起来更像是两个循环,第二个循环嵌套在第一个循环中,这就是您在命令式语言中所做的。


    其他解决方案包括:

    两个递归函数:

    def cartesianProduct[A, B](as: List[A], bs: List[B]): Set[(A, B)] = {  
      @annotation.tailrec
      def outerLoop(remaining: List[A], acc: Set[(A, B)]): Set[(A, B)] =
        remaining match {
          case a :: tail =>
            @annotation.tailrec
            def innerLoop(remaining: List[B], acc: Set[(A, B)]): Set[(A, B)] =
              remaining match {
                case b :: tail =>
                  innerLoop(remaining = tail, acc + (a -> b))
                
                case Nil =>
                  acc
              }
          
            val newAcc = innerLoop(remaining = bs, acc)
            outerLoop(remaining = tail, newAcc)
          
          case Nil =>
            acc
        }
    
      outerLoop(remaining = as, acc = Set.empty)
    }
    

    或高阶函数:
    (您也可以使用for 语法编写此代码)

    def cartesianProduct[A, B](as: List[A], bs: List[B]): Set[(A, B)] =
      as.iterator.flatMap { a =>
        bs.iterator.map { b =>
          a -> b
        }
      }.toSet
    

    可以看到Scastie中运行的代码

    【讨论】:

    • @squall 是的,您可以在 Scala 中使用命名参数,这有助于明确您传递的内容,但您也可以忽略这只是我个人的偏好。是的,您的理解是正确的,我们的函数保留了对原始列表的引用,这样我们每次用完第二个列表时都可以重新开始迭代。实际上你的原始函数也可以做到这一点,问题是因为你为内部函数变量使用了相同的名称,所以你隐藏了它们。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-13
    • 2016-04-20
    • 1970-01-01
    • 2021-05-06
    相关资源
    最近更新 更多