我是一个相当新的函数式编程,所以我正在进行一些练习练习。我想编写一个函数,给定一个独特的自然矩阵,比如说5x5,返回一个较小尺寸的独特矩阵的集合,比如3x3,其中矩阵必须是完整的,即根据原始的相邻值创建。
01 02 03 04 05
06 07 08 09 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
简单。只需以3个为一组逐个滑动,然后向下滑动,即可获得如下内容:
01 02 03 | 02 03 04 | 03 04 05 | 06 07 08
06 07 08 | 07 08 09 | 08 09 10 | 11 12 13
11 12 13 | 12 13 14 | 13 14 15 | 16 17 18
或者,在Scala中,
List(List(1, 2, 3), List(6, 7, 8), List(11, 12, 13))
List(List(2, 3, 4), List(7, 8, 9), List(12, 13, 14))
List(List(3, 4, 5), List(8, 9, 10), List(13, 14, 15))
List(List(6, 7, 8), List(11, 12, 13), List(16, 17, 18))
依旧等......
所以我冒险使用Scala(我选择的语言,因为它允许我从命令式发展到功能性,而且我在Java中度过了最近几年。
val array2D = "01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25".grouped(3).map(_.trim.toInt).grouped(5)
val sliced = array2D.map(row => row.sliding(3, 1).toList).sliding(3, 1).toList
现在我有一个可以使用的数据结构,但我看不到功能方式。当然,我可以遍历sliced
的每一部分,创建一个var matrix = new ListBuffer[Seq[Int]]()
,然后必须创建一个包,然后我就完成了。
我希望使用Scala找到一个功能强大,理想情况下无点的方法,但我很难过。必须有一种方法可以用3或类似的方式压缩...我已经搜索过ScalaDocs并且似乎无法弄明白。
答案 0 :(得分:2)
你到了一半。事实上,我无法弄清楚如何做你已经做过的事情。我稍微分解了你的代码,以便更容易理解。另外,我将array2D
设为List
,因此我可以更轻松地使用代码。 : - )
val input = "01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25"
val intArray = (input split " " map (_.toInt) toList)
val array2D = (intArray grouped 5 toList)
val sliced = array2D.map(row => row.sliding(3, 1).toList).sliding(3, 1).toList
好的,所以你有一堆列表,每个都有点像这样:
List(List(List( 1, 2, 3), List( 2, 3, 4), List( 3, 4, 5)),
List(List( 6, 7, 8), List( 7, 8, 9), List( 8, 9, 10)),
List(List(11, 12, 13), List(12, 13, 14), List(13, 14, 15)))
你想要他们这样:
List(List(List(1, 2, 3), List(6, 7, 8), List(11, 12, 13)),
List(List(2, 3, 4), List(7, 8, 9), List(12, 13, 14)),
List(List(3, 4, 5), List(8, 9, 10), List(13, 14, 15)))
这对你感觉合适吗?三个子列表中的每一个都是一个矩阵:
List(List(1, 2, 3), List(6, 7, 8), List(11, 12, 13))
是
01 02 03
06 07 08
11 12 13
所以,基本上,你想要转置它们。接下来的步骤是:
val subMatrices = sliced map (_.transpose)
该东西的类型是List[List[List[Seq[Int]]]]
。让我们考虑一下...... 2D矩阵由序列序列表示,因此List[Seq[Int]]
对应于矩阵。让我们说:
type Matrix = Seq[Seq[Int]]
val subMatrices: List[List[Matrix]] = sliced map (_.transpose)
但是你想要一个矩阵列表,所以你可以压扁它:
type Matrix = Seq[Seq[Int]]
val subMatrices: List[Matrix] = (sliced map (_.transpose) flatten)
但是,唉,map
加上flatten
是flatMap
:
type Matrix = Seq[Seq[Int]]
val subMatrices: List[Matrix] = sliced flatMap (_.transpose)
现在,您需要唯一的子矩阵。这很简单:它是一套。
val uniqueSubMatrices = subMatrices.toSet
或者,如果您希望将结果保留为序列,
val uniqueSubMatrices = subMatrices.distinct
就是这样。完整代码只是为了说明:
type Matrix = Seq[Seq[Int]]
val input = "01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25"
val intArray = (input split " " map (_.toInt) toList)
val array2D: Matrix = (intArray grouped 5 toList)
val sliced: List[List[Matrix]] = (array2D map (row => row sliding 3 toList) sliding 3 toList)
val subMatrices: List[Matrix] = sliced flatMap (_.transpose)
val uniqueSubMatrices: Set[Matrix] = subMatrices.toSet
它可以写成单个表达式,但除非你将其分解为函数,否则读起来会很糟糕。而且您要么必须使用正向管道(|>
,而不是标准库中),要么将这些函数隐式添加到它们所处理的类型中,否则无论如何都难以阅读。
答案 1 :(得分:1)
编辑:好的,我想我终于明白了你的想法。我将展示一种有效的方式,而不是一种高性能的方式。 (这通常是类似Java的可变解决方案,但您已经知道如何做到这一点。)
首先,你真的,真的应该用你自己的2D合理的系列来做到这一点。使用一堆1D集合来模拟2D集合将导致不必要的混淆和复杂化。不要这样做。真。这是一个坏主意。
但是,好吧,不管怎样,让我们这样做。
val big = (1 to 25).grouped(5).map(_.toList).toList
这是您想要的整个矩阵。接着,
val smaller = (for (r <- big.sliding(3)) yield r.toList).toList
是您想要的行组。现在,您应该使用2D数据结构,因为您想要做一些不能很好地映射到1D操作的东西。但是:
val small = smaller.map(xss =>
Iterator.iterate(xss.map(_.sliding(3)))(identity).
takeWhile(_.forall(_.hasNext)).
map(_.map(_.next)).
toList
).toList
如果你小心翼翼地将它拆开,你会发现你正在创建一堆迭代器(xss.map(_.sliding(3))
),然后通过一步一步地保持那些相同的迭代器,在锁定步骤中迭代它们,停止时其中至少有一个是空的,并将它们映射到下一个值(这就是你向前走的方式)。
现在你已经有了矩阵,你可以根据需要存储它们。就个人而言,我已经将名单压扁了:
val flattened = small.flatten
你编写了一个并排有矩阵的结构,你也可以做一些努力(同样,因为从一维操作中创建2D操作并不总是那么简单):
val sidebyside = flattened.reduceRight((l,r) => (l,r).zipped.map(_ ::: _))
(注意reduceRight使其成为O(n)操作而不是O(n ^ 2) - 加入到长累积列表的末尾是一个坏主意 - 但是请注意,如果矩阵太多,这可能会溢出堆栈。)