CCF-201809-3-元素選擇器
題目很長,大家自行去官網看。
第三題還是一如既往的是大模擬,模擬css元素選擇器,有接觸過前端的同學對此不陌生了吧。
以前學css的時候就想過層疊樣式表的實現,但是也沒細究。ccf第三題有出過markdown轉html的,我就預測ccf還會再出前端類的題目,那時候猜可能會是css,沒想到真的出了。這些題外話了,還是講回題目吧,這題難度不算高,但是細節決定成敗。
思路:
結點類:
包含該結點的行數,標籤名,id名,父結點(前繼結點,儲存父結點的意義是為了後面的查詢,從後往前找,比從前往後找好,你想想每個結點有多個子節點,但只有唯一父結點,從前往後要找出對應的序列得巢狀多少次迴圈啊)和子節點列表
1.整理輸入:先把輸入的結構化文件由結點連成一棵樹,每個結點要臉上樹,就看前面有多少個點(點數/2==往下的級數),從根節點出發找到對應的父結點再連上
2.查詢:每個查詢都進行一次樹的遍歷(我用的是層次遍歷),找到對應的選擇器就開始往上找父元素。注意,選擇器從後往前匹配,比如 div a h1, 就在遍歷的時候找h1元素,從h1開始匹配,再往上找父元素a, div。再注意,這裡的父元素,是所有祖先元素,往上一級找不到就繼續往上一級找,直到選擇器組都匹配完就記錄這個結點的行數,次數加1。
3.細節:標籤在結構化文件中和查詢的選擇器中大小寫都有可能,而標籤名大小寫不敏感,所以遇到標籤要化為小寫。
新改動:
之前程式碼有個很難找的BUG,才90,現在終於找到問題所在了,終於AC100。這還得感謝我的一個朋友。
問題出在我的根結點,我的根結點的標籤是“root”,理論上這個root不應該被匹配到,而且剛剛好ccf的測試用例的選擇器也有root,所以選擇上了。答案應該是0才對。解決方法:把根節點的標籤(label)名換一個就是了,乾脆空字串"".
Java程式碼:
import java.util.ArrayList; import java.util.Comparator; import java.util.LinkedList; import java.util.Scanner; public class 元素選擇器 { static Scanner input = new Scanner(System.in); //static Node root = new Node(0, null, "root", ""); 修改前 90 static Node root = new Node(0, null, "", ""); //修改後 100 public static void main(String[] args) { int n = input.nextInt(); int m = input.nextInt(); //接收結構化文件,並整理好結構 input.nextLine(); getDateToTree(n); //對每個選擇器進行匹配,輸出匹配結果 printAns(m); } //接收結構化文件,並整理成樹結構 public static void getDateToTree(int n) { for (int i = 0; i < n; i++) { String[] line = input.nextLine().split(" "); int dotCount = 0; int idx = 0; for (int j = 0; j < line[0].length() && line[0].charAt(j) == '.'; j++) { dotCount++; idx++; } //把標籤轉為小寫 String label = line[0].substring(idx, line[0].length()).toLowerCase(); String id = line.length == 2 ? line[1] : ""; //指標每次從根結點開始找,找dotCount/2次 Node pointer = root; for (int j = 0; j < dotCount / 2; j++) pointer = pointer.subNodes.get(pointer.subNodes.size() - 1); pointer.subNodes.add(new Node(i + 1, pointer, label, id)); } } //對每個選擇器進行匹配,輸出匹配結果 public static void printAns(int m) { for (int i = 0; i < m; i++) { //該列表儲存的是選擇器匹配到的標籤們在結構化文件中的行數 ArrayList<Integer> lineNums = new ArrayList<>(); String[] line = input.nextLine().split(" "); match(lineNums, line); lineNums.sort(Comparator.comparing(Integer::byteValue)); System.out.print(lineNums.size()); for (int lineNum : lineNums) System.out.print(" " + lineNum); System.out.println(); } } //選擇器匹配,層次遍歷結構樹,對每個結點進行匹配 public static void match(ArrayList<Integer> lineNums, String[] line) { LinkedList<Node> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { Node tem = queue.poll(); //從選擇器最底端開始匹配 //如果選擇器是標籤就改為小寫 String seleter = line[line.length - 1].charAt(0) == '#' ? line[line.length - 1] : line[line.length - 1].toLowerCase(); if (seleter.equals(tem.label) || seleter.equals(tem.id)) { int lineIdx = line.length - 2; Node pointer = tem.preNode; //從最下級開始往上找父元素 while (pointer != null && lineIdx >= 0) { //如果選擇器是標籤就改為小寫 seleter = line[lineIdx].charAt(0) == '#' ? line[lineIdx] : line[lineIdx].toLowerCase(); if (pointer.label.equals(seleter) || pointer.id.equals(seleter)) { lineIdx--; } pointer = pointer.preNode; } if (lineIdx == -1) lineNums.add(tem.lineNum); } //子結點入隊 for (Node val : tem.subNodes) queue.offer(val); } } } //標籤元素結點類 class Node { int lineNum;//元素所在的行數 Node preNode; ArrayList<Node> subNodes = new ArrayList<>();//子結點列表 String label; String id; Node(int lineNum, Node preNode, String label, String id) { this.lineNum = lineNum; this.preNode = preNode; this.label = label; this.id = id; } }
python程式碼:
# 標籤元素結點類
class Node:
def __init__(self, lineNum, preNode, label, id):
self.lineNum = lineNum # 元素所在的行數
self.preNode = preNode # 父節點
self.label = label # 標籤
self.id = id # id
self.subNodes = [] # 子結點列表
# 接收結構化文件,並整理成樹結構
def get_date_to_tree(n, root):
for i in range(n):
line = input().split()
dotCount = 0
idx = 0
for j in range(len(line[0])):
if line[0][j] == '.':
dotCount += 1
else:
idx = j
break
# 把標籤轉為小寫
label = line[0][idx:len(line[0])].lower()
id = line[1] if len(line) == 2 else ""
# 指標每次從根結點開始找,找dotCount/2次
pointer = root
for j in range(int(dotCount / 2)):
pointer = pointer.subNodes[len(pointer.subNodes) - 1]
pointer.subNodes.append(Node(i + 1, pointer, label, id))
# 對每個選擇器進行匹配,輸出匹配結果
def print_ans(m, root):
for i in range(m):
# 該列表儲存的是選擇器匹配到的標籤們在結構化文件中的行數
lineNums = []
line = input().split()
match(lineNums, line, root)
print(len(lineNums), end=' ')
for lineNum in sorted(lineNums):
print(lineNum, end=' ')
print()
# 選擇器匹配,層次遍歷結構樹,對每個結點進行匹配
def match(lineNums, line, root):
queue = []
queue.append(root)
while queue:
tem = queue.pop(0)
# 從選擇器最底端開始匹配
# 如果選擇器是標籤就改為小寫
seleter = line[len(line) - 1] if line[len(line) - 1][0] == '#' else \
line[len(line) - 1].lower()
if seleter == tem.label or seleter == tem.id:
lineIdx = len(line) - 2
pointer = tem.preNode
# 從最下級開始往上找父元素
while (pointer != None and lineIdx >= 0):
# 如果選擇器是標籤就改為小寫
seleter = line[lineIdx] if line[lineIdx][0] == '#' else \
line[lineIdx].lower()
if (pointer.label == seleter or
pointer.id == seleter):
lineIdx -= 1
pointer = pointer.preNode
if (lineIdx == -1):
lineNums.append(tem.lineNum)
# 子節點入隊
for val in tem.subNodes:
queue.append(val)
# 主程式邏輯
# root = Node(0, None, "root", "") 修改前 90
root = Node(0, None, "", "") # 修改後 100
n, m = [int(val) for val in input().split()]
# 接收結構化文件,並整理好結構
get_date_to_tree(n, root)
# 對每個選擇器進行匹配,輸出匹配結果
print_ans(m, root)