BUAA-OO pre3-正则表达式

Pre3-正则表达式

基础知识

正则表达式

正则表达式定义了字符串的模式,可以用来搜索、编辑或处理文本。

语法基础

字符
  • \d \D

    • \d匹配一个数字字符。例如,使用\d可以匹配0或者1等其他单个数字字符。等效于[0-9]
    • \D\d的补集,匹配一个非数字字符。等效于 [^0-9]
  • \s \S

    • \s匹配一个空白字符,包括空格、制表符、换页符等。与[\f\n\r\t\v]等效。
    • \S\s的补集,匹配一个非空白字符,与[^\f\n\r\t\v] 等效。
  • \w \W

    • \w匹配英文字母、数字、下划线组成的字符集中的字符。与[A-Za-z0-9_]等效。
    • \W\w的补集,匹配英文字母、数字、下划线组成的字符集之外的字符。与[^A-Za-z0-9_]等效。
  • \f \n \r \t \v

    这些字符与大家在C语言课程中学习的转义字符具有相同的含义,分别表示匹配换页符、换行符、回车符、水平制表符、垂直制表符。

格式

^ $

这两个字符用于指定匹配位置。

  • ^匹配字符串的起始位置,常用于匹配字符串的前缀。比如,想要匹配以一个数字开头的字符串,可以在正则表达式开头写^\d
  • $正好相反,匹配字符串的结尾位置,以一个数字结尾的字符串,可以写成\d$,常用于匹配后缀。

? * +

这三个字符用于对其前面的表达式内容的出现次数进行描述。

  • ?,零次或一次匹配前面的字符或子表达式。比如要匹配一个单词apple的单数和复数形式,可以写成apple(s)?
  • *,零次或多次匹配前面的字符或子表达式。例如,a*可以匹配空串(即没有任何字符的表达式),aaaaaa等等。
  • +,一次或多次匹配前面的字符或子表达式。比如我们上面的例子,匹配任意长度的无符号整数(长度为不小于1的整数,且允许前导0),就可以写成\d+

{}

对于上面的? * +三个符号的进一步扩充,对其前面的表达式内容的出现次数进行描述。

  • {n},正好匹配n次,还是之前那个例子,匹配四位无符号整数(允许有前导0),这时就不用写\d\d\d\d了,\d{4}即可。
  • {n,},至少匹配n次。之前的a*可以写作a{0,},而之前的\d+可以写成\d{1,}
  • {n,m},匹配至少n次,至多m次,要求n<=m。还记得使用Google进行搜索时,页面底部的Gooooooooooogle么,我们现在匹配Google,要求Ggle中间的o个数大于等于2,小于等于20,那么可以写成Go{2,20}gle

|

类似于逻辑运算中的或,比如ClassA|ClassB,可以匹配ClassA或者ClassB

[]

再灵活一点!我们上面都是将一个固定的字符或者表达式片段重复一定的次数,现在,我们要让我们选择的那个字符或者表达式片段不再固定,而是可以由各种元素组成。

  • [xyz],字符集,用于匹配任意一个在字符集中的字符,比如上面Class的例子,可以改写成Class[AB]
  • [^xyz],反向字符集,与上面正好相反,用于匹配任意一个字符集之外的字符。
  • [a-z],字符范围,匹配任意一个属于这个范围的字符,上面Class的例子,如果想要匹配ClassAClassZ,可以写成Class[A-Z]
  • [^a-z],反向字范围,匹配任意一个不属于这个范围的字符。

()

  • 将字符串的内容捕获以用于提取。
运算符说明
\转义符
(), (?:), (?=), []圆括号和方括号
*, +, ?, {n}, {n,}, {n,m}限定符
^, $, 任何元字符(即上述的\d\w等)、任何字符定位点和序列
\|

以上优先级从上到下由高到低。

转义字符

遇到转移字符时,需要用\\限制,例如要匹配字符.,就需要写成\\.

正则表达式生成思路

好的正则表达式也需要层次化划分,这样可以使正则表达式更明晰易读、出现错误更好定位,同时也避免了过度匹配带来的爆栈等风险。(当然因为自己第一遍写的时候毫无这个意识所以写出来的非常丑

  1. 分析字符串的层次结构
  2. 从底层开始写,不断使用前一层的“子表达式”,(即使用有意义的变量名代替单纯的表达式,类似于主函数调用子函数)
  3. 对于有捕获组的表达式,对捕获组进行命名
实例:

以pre3中的输入格式为例:


邮件信息是符合邮件信息格式的字符串。

根据时间精度的不同,可能出现以下四种认定为正确的邮件信息格式:

  1. username@domain-yyyy-mm-dd

    例:lethean@buaa.edu.cn-2020-12-02

  2. username@domain-yyyy-mm-dd-hh

    例:myname-lethean@buaa.edu.cn-2020-12-02-15

  3. username@domain-yyyy-mm-dd-hh:mimi

    例:Lethean@buaa.edu.cn-2020-12-02-15:01

  4. username@domain-yyyy-mm-dd-hh:mimi:ss

    例:myname--lethean@buaa.edu.cn-2020-12-20-15:01:20

其中

  • username@domain 为

    邮件发送者的邮箱地址

    • username 为用户名,domain 为域名
  • yyyy-mm-dd / yyyy-mm-dd-hh / yyyy-mm-dd-hh:mimi / yyyy-mm-dd-hh:mimi:ss 为

    发送时间

    • ‘y’ 代表一位年份数字,‘m’ 代表一位月份数字,‘d’ 代表一位日期数字,‘h’ 代表一位小时数字,‘mi’ 代表一位分钟数字,‘s’ 代表一位秒数数字
  • username 为只包含大小写字母、- 的长度不为零的字符串,对于大小写不敏感

  • domain 为只包含大小写字母、数字、. 的长度不为零的字符串,对大小写敏感

域名类别

域名中第一个 ‘.’ 字符(不含)之前的字符串(保证域名含有 ‘.’ 字符且域名类别不为空)

例如:

  • @126.com 域名类别: “126” (不含引号)
  • @buaa.edu.cn 域名类别: “buaa”

层次:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IB2VYOXf-1614600694232)(D:\QQ\DATA\2987805870\FileRecv\MobileFile\965C12CD166E98030B4BB01B0E96FB13.png)]

  • 第一层:
    • String email = username+"@"+domain+"-"+time;
  • 第二层:
    • String username = [A-Za-z-]+
    • String domain =[A-Za-z0-9.]+
    • String time = date + exacttime;
  • 第三层:
    • String date =[0-9]{4}[-][0-9]{2}[-][0-9]{2};
    • String exacttime =([-][0-9]{2})?([:][0-9]{2})?([:][0-9]{2})?;

Pattern类

  • Pattern.compile() 由于Pattern类没有public的构造方法,所以要想生成pattern对象实例,使用compile,返回值为pattern对象实例,参数即正则表达式。
  • Pattern.match() 故名思义,使用match来进
  • 行匹配,第一个参数是我们上面生成的pattern,第二个参数是一个待匹配的字符串,返回值为boolean类型,表示是否成功匹配。

Matcher 类

索引方法

索引方法提供了有用的索引值,精确表明输入字符串中在哪能找到匹配:

序号方法及说明
1public int start() 返回以前匹配的初始索引。
2public int start(int group) 返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引
3public int end() 返回最后匹配字符之后的偏移量。
4public int end(int group) 返回在以前的匹配操作期间,由给定组所捕获子序列的最后字符之后的偏移量。

Start 方法返回在以前的匹配操作期间,由给定组所捕获的子序列的初始索引,end 方法最后一个匹配字符的索引加 1。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class RegexMatches
{
    private static final String REGEX = "\\bcat\\b";
    private static final String INPUT =
                                    "cat cat cat cattie cat";
 
    public static void main( String[] args ){
       Pattern p = Pattern.compile(REGEX);
       Matcher m = p.matcher(INPUT); // 获取 matcher 对象
       int count = 0;
 
       while(m.find()) {
         count++;
         System.out.println("Match number "+count);
         System.out.println("start(): "+m.start());
         System.out.println("end(): "+m.end());
      }
   }
}

以上实例编译运行结果如下:

Match number 1
start(): 0
end(): 3
Match number 2
start(): 4
end(): 7
Match number 3
start(): 8
end(): 11
Match number 4
start(): 19
end(): 22
查找方法

查找方法用来检查输入字符串并返回一个布尔值,表示是否找到该模式:

序号方法及说明
1public boolean lookingAt() 尝试将从区域开头开始的输入序列与该模式匹配。
2public boolean find() 尝试查找与该模式匹配的输入序列的下一个子序列。
3public boolean find(int start) 重置此匹配器,然后尝试查找匹配该模式、从指定索引开始的输入序列的下一个子序列。
4public boolean matches() 尝试将整个区域与模式匹配。

matches 和 lookingAt 方法都用来尝试匹配一个输入序列模式。它们的不同是 matches 要求整个序列都匹配,而lookingAt 不要求。lookingAt 方法虽然不需要整句都匹配,但是需要从第一个字符开始匹配。这两个方法经常在输入字符串的开始使用。

import java.util.regex.Matcher;
import java.util.regex.Pattern;
 
public class RegexMatches
{
    private static final String REGEX = "foo";
    private static final String INPUT = "fooooooooooooooooo";
    private static final String INPUT2 = "ooooofoooooooooooo";
    private static Pattern pattern;
    private static Matcher matcher;
    private static Matcher matcher2;
 
    public static void main( String[] args ){
       pattern = Pattern.compile(REGEX);
       matcher = pattern.matcher(INPUT);
       matcher2 = pattern.matcher(INPUT2);
 
       System.out.println("Current REGEX is: "+REGEX);
       System.out.println("Current INPUT is: "+INPUT);
       System.out.println("Current INPUT2 is: "+INPUT2);
 
 
       System.out.println("lookingAt(): "+matcher.lookingAt());
       System.out.println("matches(): "+matcher.matches());
       System.out.println("lookingAt(): "+matcher2.lookingAt());
   }
}

Current REGEX is: foo
Current INPUT is: fooooooooooooooooo
Current INPUT2 is: ooooofoooooooooooo
lookingAt(): true
matches(): false
lookingAt(): false
替换方法

替换方法是替换输入字符串里文本的方法:

序号方法及说明
1public Matcher appendReplacement(StringBuffer sb, String replacement) 实现非终端添加和替换步骤。
2public StringBuffer appendTail(StringBuffer sb) 实现终端添加和替换步骤。
3public String replaceAll(String replacement) 替换模式与给定替换字符串相匹配的输入序列的每个子序列。
4public String replaceFirst(String replacement) 替换模式与给定替换字符串匹配的输入序列的第一个子序列。
5public static String quoteReplacement(String s) 返回指定字符串的字面替换字符串。这个方法返回一个字符串,就像传递给Matcher类的appendReplacement 方法一个字面字符串一样工作。

实用技巧

  • 捕获组的编号:0是一个特殊的捕获组,代表整个正则表达式,从1往后,按照左括号的出现顺序依次标号,举个例子:

     A      (       ( B C ) ( D   ( E ) ) )
    0(all)  1(BCDE) 2(BC)   3(DE) 4(E)
    
  • 命名捕获组:

    • (content)改写为(?<name>content)即可,其中name不需要写成加双引号的字符串。
    • 然后在使用group时,使用m.group("exponent")即可(这里要加双引号)。
    • 实例:比如上面的username就可以改写为String username = "(?<username>[A-Za-z-]+)";

HashMap

key-value键值对组成的散列表,可以实现快速查找。

无序的,即不会记录插入的顺序。当出现同个key时会自动覆盖之前的数据。

Java HashMap 常用方法列表如下:

方法描述
clear()删除 hashMap 中的所有键/值对
clone()复制一份 hashMap
isEmpty()判断 hashMap 是否为空
size()计算 hashMap 中键/值对的数量
put()将键/值对添加到 hashMap 中
putAll()将所有键/值对添加到 hashMap 中
putIfAbsent()如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。
remove()删除 hashMap 中指定键 key 的映射关系
containsKey()检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue()检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace()替换 hashMap 中是指定的 key 对应的 value。
replaceAll()将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
get()获取指定 key 对应对 value
getOrDefault()获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
forEach()对 hashMap 中的每个映射执行指定的操作。
entrySet()返回 hashMap 中所有映射项的集合集合视图。
keySet()返回 hashMap 中所有 key 组成的集合视图。
values()返回 hashMap 中存在的所有 value 值。
merge()添加键值对到 hashMap 中
compute()对 hashMap 中指定 key 的值进行重新计算
computeIfAbsent()对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中
computeIfPresent()对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。

作业简介

task1

明明只需要几行的一个task困扰我好久orz

在网上找了很久都没有找到用多个分隔符进行分割的方法(如果有dl知道了可以告我一下),所以!此处我们应该先将空格替换为逗号,再利用正则表达式进行分割。

import java.util.Scanner;

public class Main {
    public static void main(String[] argv) {
        Scanner in = new Scanner(System.in);
        String str = in.nextLine();
        str = str.replace(","," ");//将空格替换为逗号
        String[] inputList = str.split("\\s+");//\\s是空格类字符,+表示有一个或者很多个这样的表达式
        System.out.println(inputList.length);
    }
}

task2

新建一个Email类,类中数据域包含username,domain,time等,利用邮件原有的字符串和正则表达式的匹配即可得到上述数据;

再新建一个ArrayList<Email>,将生成的Email对象依次加入ArrayList中。

for (Email s : out) {
    String front = "\\b*[A-Za-z-]+@[A-Za-z0-9.]+";
    Pattern rfront = Pattern.compile(front);
    Matcher mfront = rfront.matcher(s.getStr());
    if (mfront.find()) {
        System.out.print(mfront.group(0) + " ");
    }
    String rear;
    rear = "[0-9]{4}[-][0-9]{2}[-][0-9]{2}([-][0-9]{2})?([:][0-9]{2})?([:][0-9]{2})?\\b*";
    Pattern rrear = Pattern.compile(rear);
    Matcher mrear = rrear.matcher(s.getStr());
    if (mrear.find()) {
        System.out.println(mrear.group(0));
    }
}

坑点:

  • username需要小写

  • 根据username来排序

    • 因此需要自己写一个比较函数,再调用Collections.sort()进行排序。

    • //main中:
      public static void sortEmail(ArrayList<Email> list) {
              Collections.sort(list);
          }
      
      //Email中:
      public class Email implements Comparable<Email> {
          private String str;
          private String name;
      
          public int compareTo(Email one) {
              return this.getName().compareTo(one.getName());
          }
      }
      

task3

就,再给出指定信息的正则表达式并且匹配就好了:(此处我使用了static类型防止正则表达式信息被误改)

private static String regexdomain = "@[A-Za-z0-9.]+";
    private static String regextime1 = "[-][0-9]{4}[-][0-9]{2}[-][0-9]{2}";
    private static String regextime4 = "[0-9]{2}[:][0-9]{2}[:][0-9]{2}";
    private static String regextime3 = "[0-9]{2}[:][0-9]{2}";
    private static String regextime2 = "[-][0-9]{4}[-][0-9]{2}[-][0-9]{2}[-][0-9]{2}";

//不同的正则表达式对应不同的匹配方法,采用上述方法需要注意匹配顺序,如下:
pattern = Pattern.compile(regextime4);
        m = pattern.matcher(str);
        if (m.find()) {
            this.hour = m.group(0).split(":")[0];
            this.minute = m.group(0).split(":")[1];
            this.second = m.group(0).split(":")[2];
        } else {
            this.second = "";
            pattern = Pattern.compile(regextime3);
            m = pattern.matcher(str);
            if (m.find()) {
                this.hour = m.group(0).split(":")[0];
                this.minute = m.group(0).split(":")[1];
            } else {
                this.minute = "";
                pattern = Pattern.compile(regextime2);
                m = pattern.matcher(str);
                if (m.find()) {
                    this.hour = m.group(0).split("-")[4];
                } else {
                    this.hour = "";
                }
            }
        }

task4

根据给定信息查询就好:

private static String regexA = "a{2,3}b{2,4}a{2,4}c{2,3}";
private static String regexB = "a{2,3}(ba){0,100}(bc){2,4}";
private static String regexC = "a{2,3}(ba){0,100}(bc){2,4}";
private static String regexDprefix = "^(a){0,3}b{1,100}c{2,3}";//前缀,以^开头
private static String regexDsuffix = "b{1,2}a{1,2}c{0,3}$";  //后缀,以&结束
private static String regexE = "a(.*)b(.*)b(.*)c(.*)b(.*)c(.*)c(.*)";

坑点:

  • 字符串需要加(),比如是(ba){2}而不是ba{2}
  • 前缀后缀的写法
  • C类是lower(s) = a(x) + ba(y) + bc(z),但是自己判断是时候用的是str.toLowerCase方法
  • D类后缀也是小写
  • E类是子序列不是子串,所以用贪心匹配,分别列出每个字符至少出现的次数。

task5

在task4的基础上,利用ArrayList中的Email生成<Email.getname(),Email>的键值对,删除和查找操作按照Hashmap的操作进行即可。

public class Mailbox {
    private HashMap<String, Email> sites = new HashMap<String, Email>();

    //生成HashMap
    public Mailbox(ArrayList<Email> list) {
        for (Email mail : list) {
            sites.put(mail.getName(), mail);
        }
    }

    //对HashMap的keyset进行排序
    public Object[] sort() {
        Set set = sites.keySet();
        Object[] arr = set.toArray();
        return arr;
    }

   //删除和查找的方法
}

坑点:

  • 此处要求输出方式和task2一样,所以要对HashMap进行排序操作(本质即是对keyset进行排序操作)

task6

输入形式变了就变一下正则表达式的顺序呗。。。别的都是一样的吧。。。

写这个task的时候我只想快点写完所以偷懒了)就在task5的基础上直接写了,新增的数据place多进行一次判断就好了,本质不变。

//以按名字删除为例
public void deletebyName(String name, String place) {
        if (sites.containsKey(name)) {
            if (sites.get(name).getPlace().equals(place)) {
                sites.remove(name);
            }
        }
    }

注意不能直接重新生成一个以<Email.getPlace(),Email>为键值对的HashMap,因为存在不同的书在同一个place,所以新的书会覆盖原有图书。

可以考虑双重key,即将name和place同时作为key进行生成,但是我懒了所以也没有去查,有兴趣的同学可以查来看看hhh。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值