ASCII 艺术练习,但我不明白这个解决方案
ASCII art exercise, but I don't understand this solution
我正在做编码游戏练习,我设法完成了 ASCII 艺术问题,它又长又难,但我做到了。
现在我阅读了其中一个解决方案,它是用 5 行代码完成的!
我试图理解它,但我没有这样做,有人可以帮助我一步一步地解释吗?
测试给你一个输入:
第 1 行:以 ASCII 艺术表示的字母的宽度 L。所有字母的宽度相同。
第2行:ASCII艺术表示的字母的高度H。所有字母高度相同。
第3行:文本T行,由N个ASCII字符组成。
以下几行:字符串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
函数转换类型为 T1
到 T2
类型的列表;在这种情况下,它将 0 until H
创建的 Int
列表转换为 String
列表,这是readLine()!!
的结果)。将列表放入变量 rows
.
(0 until H).map{ h ->
T.fold(""){ a, c ->
a + rows[h].substring((c-'A')*L, (c-'A')*L+L)
}
}
.forEach{ println(it) }
创建一个包含从 0 到 H - 1 的数字的列表,并且对于每个数字 h
,将其转换为 String
。
在解释这个String
是如何创建的之前,我们先分析一下fold
里面发生了什么。
fold
接收者(即调用fold
时.
之前的变量)是一个String
,因为变量T
是 String
.
有两个参数,a
和c
,其中a
是一个String
(相同类型的接收器)和c
是一个 Char
(因为 String
是 Char
的集合)。我们只能在集合中使用 fold
,这样做时,第一个参数将与其接收方元素类型相同,第二个参数将与其接收方元素类型相同。
有一个隐含的return,它是参数a
与变量h
的第rows
个元素的子字符串连接] (因为 rows
是一个 List<String>
,正如我们在上面看到的,这个列表的任何元素都将是一个 String
;还要考虑 a)这个列表的第一个元素在第 0 个此列表的位置等等,以及 b) 符号 x[i]
是列表 x
的第 i
个元素;这就是 Kotlin 管理列表访问的方式。
该子字符串从位置 (c - 'A') * L
开始,到位置 (c - 'A') * L + L
结束。要理解它,请将 Char
视为一个数字。所以,当你写 'A' 时,实际上你写的是 65。这是一个名为 ASCII 的约定。所以当你做 'A' - 'A'
时,你做的是 65 - 65,那是 0。同样适用于 'B' - 'A'
,这意味着 66 - 65(因为 'B'
比 A
在 ASCII 中),即 1。由于 L
也是一个 Int
,只需将其替换为表达式,您将得到一个 Int
.
但这两个参数是什么,a
和 c
? fold
只不过是一个循环。在第一次迭代中,a == ""
(调用fold
后括号内的值)和c == T[0]
(即变量T
的第一个Char
,即接收者)。某些值被 returned(在这种情况下,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
中,打印了fold
创建的每个String
。如果还有疑问,就在屏幕上打印每个变量并尝试理解:
(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
将等于 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, H = 1, T = "AACDE", rows = listOf("12345")) // the output is 11345
printAscii(L = 1, H = 1, T = "ABCBA", rows = listOf("12345")) // the output is 12321
printAscii(L = 1, H = 1, T = "EDCBA", rows = listOf("12345")) // the output is 54321
printAscii(L = 1, H = 1, T = "AAAAA", rows = listOf("12345")) // the output is 11111
您可以看到 T
变量定义了 rows
的打印方式。
现在,让我们将 L
变量更改为 2
。
printAscii(L = 2, H = 1, T = "ABCDE", rows = listOf("1234567890")) // the output is 1234567890
printAscii(L = 2, H = 1, T = "AACDE", rows = listOf("1234567890")) // the output is 1212567890
printAscii(L = 2, H = 1, T = "ABCBA", rows = listOf("1234567890")) // the output is 1234563412
printAscii(L = 2, H = 1, T = "EDCBA", rows = listOf("1234567890")) // the output is 9078563412
printAscii(L = 2, H = 1, T = "AAAAA", rows = listOf("1234567890")) // the output is 1212121212
现在可以看到字符串T
中的字符A
与[=38=中字符串中的子串12
相关,字符B
in T
与 rows
中的子字符串 34
相关,依此类推。当我们将 L
更改为 2 时,T
中的每个字符都与 rows
中大小为 2 的子字符串相关(如果你愿意,你可以尝试 L
等于 3,但一定要在 rows
的第一个元素中添加 5 个字符,这是字符串 T
).
的大小
一般来说,当H
等于1
时,T
中的每个字符都与[=第一个元素的大小为L
的子串相关38=](记住这一点!)。但是如何计算哪个子串属于某个字符呢?
请注意,
当L == 1
时,A
与0
开始的子串相关,B
与[=97开始的子串相关=], C
与从 2
开始的子串相关,所以上;
当L == 2
、A
与[=129=开始的子串相关时]、B
与[=99开始的子串相关=], C
与从 4
开始的子串相关,依此类推。
你能猜出 L == 3
的模式吗?模式很简单:计算某个字母到A
的距离(例如,A
和B
之间的距离为1,A
和[=146之间的距离=] 是 4)。将这个距离乘以L
,得到子串的起始位置。
因此,例如,当 L == 2
时,C
与从 4 开始的子字符串相关。应用我们的方法,C
和 A
之间的距离为 2。乘以 L
,即 2,我们有 2 * 2 = 4 , 这是子串的起始位置。
由于子串的大小为L
,正如我们上面发现的,为了得到子串的最终位置,我们可以在开始位置加上L
。
根据以上信息,我们可以编写Kotlin代码了。
如何计算某个字符c
和'A'
之间的距离?答案:c - 'A'
如何获取子串的起始位置?答案:(c - 'A') * L
如何获取子串的结束位置?答案:(c - 'A') * L + L
如何从rows
的第一个元素中得到一个子串?答案:rows[0].substring((c - 'A') * L, (c - 'A') * L + L)
这是给H == 1
的,但你可以增加它的值,得出同样的结论。
关于你的第二个问题,你可以看一下this site,特别是否定字符类部分。 Regex 与 Kotlin 无关,但您可以在 Kotlin 代码中使用 Regex。
简单来说,Regex中的[^A-Z]
表示“匹配一个不在A和Z之间的字符”,[^0-9]
表示“匹配一个不在0和9之间的字符” ”。如果要匹配 A 和 Z 之间的单个字符,只需删除插入符号,即 [A-Z]
.
话虽这么说,.replace("[^A-Z]".toRegex(), "[")
的意思是“匹配一个不在 A 和 Z 之间的字符,并用 [
替换这个字符”。为什么 [
?还记得 'B' - 'A' == 1
和 'E' - 'A' == 4
吗?如果你跟着火车,你会看到 'Z' - 'A' == 25
。 ASCII table 中紧接着 'Z'
的字符是 [
,因此 '[' - 'A' == 26
。因此,任何不在 A 和 Z 之间的字符 c
都会将表达式 c - 'A'
计算为 26.
我正在做编码游戏练习,我设法完成了 ASCII 艺术问题,它又长又难,但我做到了。 现在我阅读了其中一个解决方案,它是用 5 行代码完成的! 我试图理解它,但我没有这样做,有人可以帮助我一步一步地解释吗?
测试给你一个输入:
第 1 行:以 ASCII 艺术表示的字母的宽度 L。所有字母的宽度相同。
第2行:ASCII艺术表示的字母的高度H。所有字母高度相同。
第3行:文本T行,由N个ASCII字符组成。
以下几行:字符串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
函数转换类型为 T1
到 T2
类型的列表;在这种情况下,它将 0 until H
创建的 Int
列表转换为 String
列表,这是readLine()!!
的结果)。将列表放入变量 rows
.
(0 until H).map{ h ->
T.fold(""){ a, c ->
a + rows[h].substring((c-'A')*L, (c-'A')*L+L)
}
}
.forEach{ println(it) }
创建一个包含从 0 到 H - 1 的数字的列表,并且对于每个数字 h
,将其转换为 String
。
在解释这个String
是如何创建的之前,我们先分析一下fold
里面发生了什么。
fold
接收者(即调用fold
时.
之前的变量)是一个String
,因为变量T
是String
.有两个参数,
a
和c
,其中a
是一个String
(相同类型的接收器)和c
是一个Char
(因为String
是Char
的集合)。我们只能在集合中使用fold
,这样做时,第一个参数将与其接收方元素类型相同,第二个参数将与其接收方元素类型相同。有一个隐含的return,它是参数
a
与变量h
的第rows
个元素的子字符串连接] (因为rows
是一个List<String>
,正如我们在上面看到的,这个列表的任何元素都将是一个String
;还要考虑 a)这个列表的第一个元素在第 0 个此列表的位置等等,以及 b) 符号x[i]
是列表x
的第i
个元素;这就是 Kotlin 管理列表访问的方式。该子字符串从位置
(c - 'A') * L
开始,到位置(c - 'A') * L + L
结束。要理解它,请将Char
视为一个数字。所以,当你写 'A' 时,实际上你写的是 65。这是一个名为 ASCII 的约定。所以当你做'A' - 'A'
时,你做的是 65 - 65,那是 0。同样适用于'B' - 'A'
,这意味着 66 - 65(因为'B'
比A
在 ASCII 中),即 1。由于L
也是一个Int
,只需将其替换为表达式,您将得到一个Int
.
但这两个参数是什么,a
和 c
? fold
只不过是一个循环。在第一次迭代中,a == ""
(调用fold
后括号内的值)和c == T[0]
(即变量T
的第一个Char
,即接收者)。某些值被 returned(在这种情况下,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
中,打印了fold
创建的每个String
。如果还有疑问,就在屏幕上打印每个变量并尝试理解:
(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
将等于 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, H = 1, T = "AACDE", rows = listOf("12345")) // the output is 11345
printAscii(L = 1, H = 1, T = "ABCBA", rows = listOf("12345")) // the output is 12321
printAscii(L = 1, H = 1, T = "EDCBA", rows = listOf("12345")) // the output is 54321
printAscii(L = 1, H = 1, T = "AAAAA", rows = listOf("12345")) // the output is 11111
您可以看到 T
变量定义了 rows
的打印方式。
现在,让我们将 L
变量更改为 2
。
printAscii(L = 2, H = 1, T = "ABCDE", rows = listOf("1234567890")) // the output is 1234567890
printAscii(L = 2, H = 1, T = "AACDE", rows = listOf("1234567890")) // the output is 1212567890
printAscii(L = 2, H = 1, T = "ABCBA", rows = listOf("1234567890")) // the output is 1234563412
printAscii(L = 2, H = 1, T = "EDCBA", rows = listOf("1234567890")) // the output is 9078563412
printAscii(L = 2, H = 1, T = "AAAAA", rows = listOf("1234567890")) // the output is 1212121212
现在可以看到字符串T
中的字符A
与[=38=中字符串中的子串12
相关,字符B
in T
与 rows
中的子字符串 34
相关,依此类推。当我们将 L
更改为 2 时,T
中的每个字符都与 rows
中大小为 2 的子字符串相关(如果你愿意,你可以尝试 L
等于 3,但一定要在 rows
的第一个元素中添加 5 个字符,这是字符串 T
).
一般来说,当H
等于1
时,T
中的每个字符都与[=第一个元素的大小为L
的子串相关38=](记住这一点!)。但是如何计算哪个子串属于某个字符呢?
请注意,
当
L == 1
时,A
与0
开始的子串相关,B
与[=97开始的子串相关=],C
与从2
开始的子串相关,所以上;当
L == 2
、A
与[=129=开始的子串相关时]、B
与[=99开始的子串相关=],C
与从4
开始的子串相关,依此类推。
你能猜出 L == 3
的模式吗?模式很简单:计算某个字母到A
的距离(例如,A
和B
之间的距离为1,A
和[=146之间的距离=] 是 4)。将这个距离乘以L
,得到子串的起始位置。
因此,例如,当 L == 2
时,C
与从 4 开始的子字符串相关。应用我们的方法,C
和 A
之间的距离为 2。乘以 L
,即 2,我们有 2 * 2 = 4 , 这是子串的起始位置。
由于子串的大小为L
,正如我们上面发现的,为了得到子串的最终位置,我们可以在开始位置加上L
。
根据以上信息,我们可以编写Kotlin代码了。
如何计算某个字符
c
和'A'
之间的距离?答案:c - 'A'
如何获取子串的起始位置?答案:
(c - 'A') * L
如何获取子串的结束位置?答案:
(c - 'A') * L + L
如何从
rows
的第一个元素中得到一个子串?答案:rows[0].substring((c - 'A') * L, (c - 'A') * L + L)
这是给H == 1
的,但你可以增加它的值,得出同样的结论。
关于你的第二个问题,你可以看一下this site,特别是否定字符类部分。 Regex 与 Kotlin 无关,但您可以在 Kotlin 代码中使用 Regex。
简单来说,Regex中的[^A-Z]
表示“匹配一个不在A和Z之间的字符”,[^0-9]
表示“匹配一个不在0和9之间的字符” ”。如果要匹配 A 和 Z 之间的单个字符,只需删除插入符号,即 [A-Z]
.
话虽这么说,.replace("[^A-Z]".toRegex(), "[")
的意思是“匹配一个不在 A 和 Z 之间的字符,并用 [
替换这个字符”。为什么 [
?还记得 'B' - 'A' == 1
和 'E' - 'A' == 4
吗?如果你跟着火车,你会看到 'Z' - 'A' == 25
。 ASCII table 中紧接着 'Z'
的字符是 [
,因此 '[' - 'A' == 26
。因此,任何不在 A 和 Z 之间的字符 c
都会将表达式 c - 'A'
计算为 26.