
1. 题目考点
这道题主要考:
- 正则表达式
re - 字符串匹配
- 循环读取多行输入
- CSS 代码结构的简单判断
- 区分“选择器里的 #”和“属性值里的颜色代码”
这道题的关键不是单纯判断 #FFF 是否像颜色,而是要注意:有些 #XXX 是 CSS 选择器,不应该输出。
例如:
#BED
{
color: #FfFdF8;
}
这里:
#BED
是选择器,不是颜色值,所以不能输出。
但:
color: #FfFdF8;
里面的 #FfFdF8 是颜色代码,要输出。
2. 审题
题目给你 N 行 CSS 代码。
你要从这些 CSS 代码中,找出所有合法的 HEX 颜色代码,并按照出现顺序输出。
合法 HEX 颜色代码满足:
- 必须以
#开头 - 后面只能是十六进制字符:
0-9 a-f A-F - 后面字符数量只能是 3 位或 6 位
所以合法例子:
#FFF
#025
#F0A1FB
#aef
非法例子:
#fffabg
#abcf
#12365erff
容易忽略的地方是:
#Cab
虽然它看起来符合 # 加 3 个十六进制字符,但它是 CSS 选择器,不是属性值里的颜色,所以不能输出。
3. 思路提示
这道题可以分成两个问题:
第一个问题:什么样的字符串算合法颜色代码?
可以用正则表达式描述:
#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\b
它表示:
# + 3 个十六进制字符 + 可选的另外 3 个十六进制字符
所以它可以匹配 3 位或 6 位颜色代码。
第二个问题:在哪里找颜色代码?
不能在整份 CSS 代码里无脑找,因为选择器里也可能有 #BED、#Cab 这种东西。
CSS 大致结构是:
selector
{
property: value;
}
真正的颜色代码一般出现在 { 和 } 之间,也就是 CSS 规则块内部。
所以思路是:
- 读入
N - 一行一行读取 CSS 代码
- 用一个变量
inside_block记录当前是否在{}内部 - 只有在
{}内部,才用正则表达式找颜色代码 - 找到一个就输出一个
4. 完整设计思路
第一步:准备正则表达式
我们要找的是:
#FFF
#ffffff
#FfFdF8
正则可以写成:
r'#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\b'
逐段解释:
| 正则部分 | 含义 |
|---|---|
# | 必须以 # 开头 |
[0-9a-fA-F] | 一个十六进制字符 |
{3} | 正好出现 3 次 |
(?: ... )? | 这一组可有可无 |
[0-9a-fA-F]{3} | 再来 3 个十六进制字符 |
\b | 单词边界,防止错误截断匹配 |
为什么不用:
#[0-9a-fA-F]{3,6}
因为这样会允许 4 位、5 位,例如:
#abcf
但题目只允许 3 位或 6 位。
所以更准确的写法是:
#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?
意思是:
先来 3 位,如果后面还有,就必须再来 3 位。
这样总长度只能是 3 或 6。
第二步:判断当前是否在 CSS 块内部
我们用变量:
inside_block = False
它的含义是:
False:当前不在 { } 内部
True :当前在 { } 内部
遇到 {,说明进入 CSS 块:
inside_block = True
遇到 },说明离开 CSS 块:
inside_block = False
第三步:只在 CSS 块内部查找颜色代码
比如:
#BED
{
color: #FfFdF8; background-color:#aef;
}
程序处理时:
#BED 不在 {} 内部,不查找
{ 进入 {} 内部
color: #FfFdF8... 在 {} 内部,查找
} 离开 {} 内部
这样就不会误输出 #BED。
5. 代码实现
import re
n = int(input())
pattern = re.compile(r'#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\b')
inside_block = False
for _ in range(n):
line = input()
search_area = ""
# 如果本来就在 CSS 块内部,这一整行都可能包含颜色代码
if inside_block:
search_area = line
# 如果这一行出现 {,说明从 { 后面开始进入 CSS 块
if "{" in line:
inside_block = True
search_area = line.split("{", 1)[1]
# 如果搜索区域里出现 },说明只查 } 前面的内容
if "}" in search_area:
search_area = search_area.split("}", 1)[0]
inside_block = False
colors = pattern.findall(search_area)
for color in colors:
print(color)
6. 代码解释
这一句读取输入的行数:
n = int(input())
可以理解为:
input() 读取一行字符串
int() 把字符串转成整数
这一句创建正则对象:
pattern = re.compile(r'#[0-9a-fA-F]{3}(?:[0-9a-fA-F]{3})?\b')
以后可以用:
pattern.findall(search_area)
从某一段字符串里找出所有匹配的颜色代码。
例如:
search_area = "color: #FfFdF8; background-color:#aef;"
匹配结果是:
['#FfFdF8', '#aef']
7. 运行演示
假设输入中有这一段:
#BED
{
color: #FfFdF8; background-color:#aef;
font-size: 123px;
background: -webkit-linear-gradient(top, #f9f9f9, #fff);
}
程序逐行判断:
| 当前行 | 是否在 {} 内部 | 是否查找 | 找到的内容 |
|---|---|---|---|
#BED | 否 | 否 | 不找 |
{ | 进入内部 | 是 | 无 |
color: #FfFdF8; background-color:#aef; | 是 | 是 | #FfFdF8、#aef |
font-size: 123px; | 是 | 是 | 无 |
background: ... #f9f9f9, #fff); | 是 | 是 | #f9f9f9、#fff |
} | 离开内部 | 否 | 无 |
所以输出:
#FfFdF8
#aef
#f9f9f9
#fff
8. 方法总结
这类题不要一上来就写代码,先问自己两个问题:
第一,题目要找的目标长什么样?
这里目标是:
# + 3 位或 6 位十六进制字符
第二,目标出现在哪里才算有效?
这里不能匹配选择器里的 #id,只能匹配 CSS 代码块 {} 内部的颜色值。
所以这道题的核心思路是:
先判断位置,再用正则匹配格式。
下次遇到类似题目,可以按这个套路思考:
1. 我要找什么?
2. 它的格式有什么限制?
3. 它出现在哪里才有效?
4. 有没有看起来像答案但其实不能算的干扰项?
9. 小练习
请判断下面输入应该输出哪些颜色代码。
8
#abc
{
border-color: #ABCDEF;
color: #123;
}
.test
{
background: linear-gradient(#fff, #000000);
}
提示:
#abc是选择器,不要输出。{}内部的颜色代码才需要判断。- 注意 3 位和 6 位都合法。
补充
这两句还是没有看懂search_area = line.split(“{“, 1)[1], search_area = search_area.split(“}”, 1)[0],以及这里的为什么要这样子设计先后顺序?# 如果这一行出现 {,说明从 { 后面开始进入 CSS 块
if “{” in line:
inside_block = True
search_area = line.split(“{“, 1)[1]
# 如果搜索区域里出现 },说明只查 } 前面的内容
if "}" in search_area:
search_area = search_area.split("}", 1)[0]
inside_block = False
这两句的核心作用是:把一整行 CSS 切成“真正需要搜索颜色代码的那一段”。
先看 split() 本身。
1. line.split("{", 1)[1] 是什么意思
假设这一行是:
line = "body { color: #fff; background: #000000; }"
执行:
line.split("{", 1)
结果是一个列表:
["body ", " color: #fff; background: #000000; }"]
意思是:从第一个 { 的位置切开,只切一次。
所以:
line.split("{", 1)[0]
得到 { 前面的内容:
"body "
而:
line.split("{", 1)[1]
得到 { 后面的内容:
" color: #fff; background: #000000; }"
我们为什么要 [1]?
因为 { 前面通常是 CSS 选择器,例如:
body
#BED
.main
这些地方即使有 #BED,也不是颜色代码。
真正要找颜色代码的是 { 后面的属性区域:
color: #fff;
background: #000000;
所以这句:
search_area = line.split("{", 1)[1]
可以理解成:
如果这一行出现了 {,那么只搜索 { 后面的内容。
2. search_area.split("}", 1)[0] 是什么意思
假设现在:
search_area = " color: #fff; background: #000000; }"
执行:
search_area.split("}", 1)
结果是:
[" color: #fff; background: #000000; ", ""]
所以:
search_area.split("}", 1)[0]
得到 } 前面的内容:
" color: #fff; background: #000000; "
我们为什么要 [0]?
因为 } 表示 CSS 规则块结束。} 后面的内容已经不属于这一块的属性内容了,不应该继续搜索。
所以这句:
search_area = search_area.split("}", 1)[0]
可以理解成:
如果搜索区域里出现了 },那么只保留 } 前面的内容。
3. 用一句话理解这两句
这两句配合起来,就是在做这件事:
只拿 { 和 } 中间的内容去找颜色代码。
例如这一行:
body { color: #fff; background: #000000; }
第一步,遇到 {,只保留 { 后面:
color: #fff; background: #000000; }
第二步,遇到 },只保留 } 前面:
color: #fff; background: #000000;
最后只在这部分里面找颜色代码。
4. 为什么要这样设计先后顺序
原代码是:
if inside_block:
search_area = line
if "{" in line:
inside_block = True
search_area = line.split("{", 1)[1]
if "}" in search_area:
search_area = search_area.split("}", 1)[0]
inside_block = False
这个顺序是有原因的。
第一步:如果本来就在 {} 里面,就先把整行当作搜索区域
比如:
color: #fff;
这一行没有 {,也没有 },但它是在 CSS 块内部。
所以如果:
inside_block == True
就说明当前行可能是属性行,应该搜索整行:
search_area = line
第二步:如果当前行有 {,说明搜索应该从 { 后面开始
比如:
body { color: #fff;
如果我们直接搜索整行,就会把 body 选择器部分也算进去。
更明显的例子:
#BED { color: #fff; }
这里:
#BED
是选择器,不是颜色。
如果搜索整行,可能会错误输出:
#BED
#fff
但正确结果只应该是:
#fff
所以遇到 { 后,要重新设置:
search_area = line.split("{", 1)[1]
也就是只搜索 { 后面的内容。
第三步:如果搜索区域里有 },说明搜索应该到 } 前面结束
比如:
color: #fff; } #abc
虽然这种写法不常见,但逻辑上 } 后面已经不属于属性区域。
所以要切掉 } 后面的内容:
search_area = search_area.split("}", 1)[0]
然后把状态改成:
inside_block = False
表示已经离开 CSS 块。
5. 为什么不能先处理 } 再处理 {
假设有一行:
body { color: #fff; }
正确的搜索区域应该是:
color: #fff;
现在按原顺序:
line = "body { color: #fff; }"
先处理 {:
search_area = line.split("{", 1)[1]
得到:
color: #fff; }
再处理 }:
search_area = search_area.split("}", 1)[0]
得到:
color: #fff;
这正好是我们想要的结果。
如果先处理 },会比较别扭。因为一开始 search_area 还不一定是正确的搜索区域。我们应该先确定“从哪里开始搜索”,再确定“到哪里停止搜索”。
也就是:
先找开始位置 {
再找结束位置 }
这更符合 CSS 块的结构。
6. 手动模拟一遍
看这一行:
line = "#BED { color: #fff; }"
一开始:
inside_block = False
search_area = ""
第一段:
if inside_block:
search_area = line
因为 inside_block 是 False,所以不执行。
第二段:
if "{" in line:
inside_block = True
search_area = line.split("{", 1)[1]
这一行有 {,所以执行。
inside_block = True
search_area = " color: #fff; }"
第三段:
if "}" in search_area:
search_area = search_area.split("}", 1)[0]
inside_block = False
search_area 里面有 },所以执行。
search_area = " color: #fff; "
inside_block = False
最后只在:
color: #fff;
里面找颜色代码,所以只会找到:
#fff
不会错误找到:
#BED
7. 用更直观的变量名理解
这段代码其实是在做:
if inside_block:
要搜索的内容 = 当前整行
if 当前行里有 "{":
进入 CSS 块
要搜索的内容 = "{" 后面的内容
if 要搜索的内容里有 "}":
要搜索的内容 = "}" 前面的内容
离开 CSS 块
换成中文逻辑就是:
如果已经在大括号里面,就先假设这一整行都要查。
如果这一行新出现了左大括号,就只查左大括号后面。
如果搜索区域里出现了右大括号,就只查右大括号前面。
8. 小练习
你可以手动判断下面三行代码中,search_area 最后分别是什么。
line = "#abc"
line = "#abc { color: #fff; }"
line = "background: #000000;"
提示:
- 第一行没有进入
{},所以不应该搜索。 - 第二行应该只保留
{和}中间的内容。 - 第三行要看
inside_block是否已经是True。



