问题描述
我正在做编码练习,并且设法解决了ASCII Art问题,虽然很长很困难,但是我做到了。 现在,我阅读了其中一种解决方案,它是用5行代码完成的! 我正在尝试理解它,但是我没有这样做,有人可以通过逐步的解释帮助我吗?
测试为您提供以下输入:
第1行:以ASCII艺术形式表示的字母的宽度L。所有字母的宽度相同。
第2行:以ASCII艺术形式表示的字母的高度H。所有字母的高度都相同。
第3行:由N个ASCII字符组成的文本T行。
以下几行:字符串ABCDEFGHIJKLMnopQRSTUVWXYZ?以ASCII艺术形式表示。
这是解决方案之一:
fun main(args : Array<String>) {
val L = readLine()!!.toInt()
val H = readLine()!!.toInt()
val T = readLine()!!.toupperCase().replace("[^A-Z]".toRegex(),"[")
val rows = (0 until H).map{readLine()!!}
(0 until H).map{h -> T.fold(""){ a,c -> a + rows[h].substring((c-'A')*L,(c-'A')*L+L) }}.forEach{println(it)}
}
非常感谢您的帮助:)
PS。我不确定 .fold 和 .map 是什么。
解决方法
让我们逐行分析。
val L = readLine()!!.toInt()
读取Int
并将其放入变量L
。
val H = readLine()!!.toInt()
读取另一个Int
并将其放入变量H
。
val T = readLine()!!
.toUpperCase()
.replace("[^A-Z]".toRegex(),"[")
读取一个String
,将其所有字母转换为大写字符(因此a
变成A
),用[
字符替换所有非字母的字符( [^A-Z]
语法来自RegEx,而不是Kotlin),并将其放入变量T
中。
val rows = (0 until H).map{ readLine()!! }
创建一个从0到H-1的数字列表,并针对每个数字将其转换为用户提供的String
(map
函数将转换类型为{{1} }转换为类型T1
的列表;在这种情况下,它将T2
创建的Int
的列表转换为0 until H
的列表,这是String
)。将列表放入变量readLine()!!
中。
rows
创建一个从0到H-1的列表,并为每个数字(0 until H).map{ h ->
T.fold(""){ a,c ->
a + rows[h].substring((c-'A')*L,(c-'A')*L+L)
}
}
.forEach{ println(it) }
将其转换为h
。
在解释如何创建String
之前,让我们首先分析String
内部的情况。
-
fold
接收者(即调用fold
时在.
之前的变量)是fold
,因为变量String
是T
。 -
有两个参数,
String
和a
,其中c
是a
(接收者的相同类型),String
是c
(因为Char
是String
的集合)。我们只能在集合中使用Char
,这样做时,第一个参数将是其接收者的相同类型,第二个参数将是其接收者元素的相同类型。 -
有一个隐式返回,这是参数
fold
与变量a
的第h
个元素的子字符串连接(自rows
起如上所示,它是rows
,该列表的任何元素都将是List<String>
;还应考虑a)此列表的第一个元素位于该列表的第0位,依此类推, b)符号String
是列表x[i]
的第i
个元素;这就是Kotlin管理列表访问的方式。 -
此子字符串始于位置
x
,结束于位置(c - 'A') * L
。要理解它,请将(c - 'A') * L + L
视为一个数字。因此,当您编写“ A”时,实际上是在编写65。这是一个名为ASCII的约定。因此,当您执行Char
时,您执行的是65-65,即0。'A' - 'A'
也是如此,这意味着66-65(因为'B' - 'A'
比{{1 }}(以ASCII表示),即1。由于'B'
也是A
,只需将其替换为表达式,最后得到一个L
。
但是Int
和Int
这两个参数是什么? a
只是一个循环。在第一个迭代中,c
(调用fold
后的括号之间的值)和a == ""
(即变量fold
的第一个c == T[0]
,即接收方)。返回一些值(在这种情况下,返回Char
的值与T
的子字符串连接),并在下一次迭代中返回a
和rows[h]
。在下一次迭代中,a == <returned value by a and T[0]>
和c == T[1]
。重复循环,直到处理a == <returned value by a and T[1]>
的所有元素为止。
最后,在c == T[2]
中,将打印T
创建的每个forEach
。如果仍然有疑问,只需在屏幕上打印每个变量并尝试了解:
String
希望您能明白。
编辑:我试图将回复放入评论中,但我认为它太长了,所以我进行了编辑。
为了更好地理解,让我们定义一个函数fold
,它是您的原始函数,但带有参数作为输入。如果您不知道函数是什么,请不要担心。
(0 until H).map{ h ->
println("h=$h")
T.fold(""){ a,c ->
println("a=$a,c=$c")
println("c - 'A' = ${c - 'A'}")
a + rows[h].substring((c-'A')*L,(c-'A')*L+L)
}
}
您可以调用以下函数:
printAscii
在这种情况下,fun printAscii(L: Int,H: Int,T: String,rows: List<String>) {
(0 until H).map{ h ->
T.fold(""){ a,c ->
a + rows[h].substring((c-'A')*L,(c-'A')*L+L)
}
}.forEach{ println(it) }
}
等于printAscii(L = 1,H = 2,T = "some string",rows = listOf("string1","string2"))
,L
等于1
,H
等于{{1} }和2
等于T
。现在,让我们玩吧:
"some string"
您会看到rows
变量定义了listOf("string1","string2")
的打印方式。
现在,让我们将printAscii(L = 1,H = 1,T = "ABCDE",rows = listOf("12345")) // the output is 12345
printAscii(L = 1,T = "AACDE",rows = listOf("12345")) // the output is 11345
printAscii(L = 1,T = "ABCBA",rows = listOf("12345")) // the output is 12321
printAscii(L = 1,T = "EDCBA",rows = listOf("12345")) // the output is 54321
printAscii(L = 1,T = "AAAAA",rows = listOf("12345")) // the output is 11111
变量更改为T
。
rows
现在您可以看到字符串L
中的字符2
与printAscii(L = 2,rows = listOf("1234567890")) // the output is 1234567890
printAscii(L = 2,rows = listOf("1234567890")) // the output is 1212567890
printAscii(L = 2,rows = listOf("1234567890")) // the output is 1234563412
printAscii(L = 2,rows = listOf("1234567890")) // the output is 9078563412
printAscii(L = 2,rows = listOf("1234567890")) // the output is 1212121212
字符串中的子字符串A
与字符T
相关12
中的rows
中的子字符串与B
中的子字符串T
相关,依此类推。当我们将34
更改为2时,rows
中的每个字符都与L
中大小为2的子字符串相关(如果需要,您可以尝试将T
等于3,但请确保在rows
的第一个元素中添加5个字符,即字符串L
的大小。)
通常来说,当rows
等于T
时,H
中的每个字符都与{{1}的第一个元素的大小1
的子字符串有关} (记住这一点!)。但是如何计算哪个子字符串属于某个字符呢?
请注意,
-
当
T
,L
与从rows
开始的子字符串相关,L == 1
与从A
开始的子字符串,{ {1}}与从0
开始的子字符串相关,依此类推; -
当
B
,1
与从C
开始的子字符串相关,2
与从L == 2
开始的子字符串,{ {1}}与从A
开始的子字符串相关,依此类推。
您能猜出0
的模式吗?模式很简单:计算某个字母到B
之间的距离(例如,2
和C
之间的距离为1,4
和{{1之间的距离}}是4)。将该距离乘以L == 3
并获得子字符串的起始位置。
例如,当A
,A
与以 4 开始的子字符串相关时。应用我们的方法,B
和A
之间的距离为2。乘以E
(即2),我们得到2 * 2 = 4 ,即子字符串的开始位置。
由于子串的大小为L
(如上所述),为了获得子串的最终位置,我们可以将L == 2
添加到开始位置。
根据上述信息,我们可以编写Kotlin代码。
-
如何计算某个字符
C
和C
之间的距离?答案:A
-
如何获取子字符串的起始位置?答案:
L
-
如何获取子字符串的结束位置?答案:
L
-
如何从
L
的第一个元素获取子字符串?答案:c
这是针对'A'
的,但是您可以提高其价值并得出相同的结论。
对于第二个问题,您可以查看this site,特别是否定字符类部分。 Regex与Kotlin无关,但是您可以在Kotlin代码中使用Regex。
简而言之,正则表达式中的c - 'A'
意味着“匹配不在A和Z之间的单个字符”,而(c - 'A') * L
意味着“匹配不在0和9之间的单个字符”。如果要匹配A和Z之间的单个字符,只需删除插入符号,即(c - 'A') * L + L
。
话虽这么说,rows
的意思是“匹配不在A和Z之间的单个字符,并将其替换为rows[0].substring((c - 'A') * L,(c - 'A') * L + L)
”。为什么H == 1
?还记得[^A-Z]
和[^0-9]
吗?如果您跟随火车,将会看到[A-Z]
。 ASCII表中紧接.replace("[^A-Z]".toRegex(),"[")
的字符是[
,所以[
。因此,不在A和Z之间的任何字符'B' - 'A' == 1
会将表达式'E' - 'A' == 4
评估为26。