区分“选择器里的 #”和“属性值里的颜色代码”

1. 题目考点

这道题主要考:

  1. 正则表达式 re
  2. 字符串匹配
  3. 循环读取多行输入
  4. CSS 代码结构的简单判断
  5. 区分“选择器里的 #”和“属性值里的颜色代码”

这道题的关键不是单纯判断 #FFF 是否像颜色,而是要注意:有些 #XXX 是 CSS 选择器,不应该输出。

例如:

#BED
{
    color: #FfFdF8;
}

这里:

#BED

是选择器,不是颜色值,所以不能输出。

但:

color: #FfFdF8;

里面的 #FfFdF8 是颜色代码,要输出。


2. 审题

题目给你 N 行 CSS 代码。

你要从这些 CSS 代码中,找出所有合法的 HEX 颜色代码,并按照出现顺序输出。

合法 HEX 颜色代码满足:

  1. 必须以 # 开头
  2. 后面只能是十六进制字符:0-9 a-f A-F
  3. 后面字符数量只能是 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 规则块内部。

所以思路是:

  1. 读入 N
  2. 一行一行读取 CSS 代码
  3. 用一个变量 inside_block 记录当前是否在 {} 内部
  4. 只有在 {} 内部,才用正则表达式找颜色代码
  5. 找到一个就输出一个

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);
}

提示:

  1. #abc 是选择器,不要输出。
  2. {} 内部的颜色代码才需要判断。
  3. 注意 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_blockFalse,所以不执行。

第二段:

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;"

提示:

  1. 第一行没有进入 {},所以不应该搜索。
  2. 第二行应该只保留 {} 中间的内容。
  3. 第三行要看 inside_block 是否已经是 True
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇