设计模式第14讲——享元模式(Flyweight)

一、什么是享元模式

享元模式是一种结构型的设计模式。它的主要目的是通过共享对象来减少系统种对象的数量,其本质就是缓存共享对象,降低内存消耗

享元模式将需要重复使用的对象分为两个部分:内部状态和外部状态。

内部状态是不会变化的,可以被多个对象共享,而外部状态会随着对象的使用而改变。比如,连接池中的连接对象,保存在连接对象中的用户名、密码、连接URL等信息,在创建对象的时候就设置好了,不会随环境的改变而改变,这些为内部状态。而当每个连接要被回收利用时,我们需要将它标记为可用状态,这些为外部状态。

二、 角色组成

  • 抽象享元角色(FlyWeight):享元对象抽象基类或者接口,同时定义出对象的外部状态和内部状态的接口或实现;
  • 具体享元角色(ConcreteFlyWeight):实现抽象享元类中的方法,是需要共享的对象类
  • 享元工厂(FlyWeightFactory):维护一个享元对象的池,内部使用一个 Map 存储已经创建的享元对象

三、优缺点

优点:

  • 共享大量相似对象,可以避免大量无谓的内存分配和释放操作,缓解垃圾回收器的压力,提高吞吐量。
  • 避免对象的重复创建,尤其是在创建大量具有相同属性的对象时,使用享元模式可以大大提高系统的性能和效率,避免出现大量的重复对象占用内存空间,降低系统开销。
  • 享元模式的内部状态和外部状态是分离的,内部状态存储在内存中,外部状态可以根据需要进行传递,这样可以有效地降低对象间的耦合度。
  • 共享的对象能够在多个线程中安全地共享并发访问,不会有线程安全问题。

缺点:

  • 实现比较复杂。在实际开发中,需要对内部状态和外部状态进行较为繁琐的区分。
  • 过度共享。如果对象的内部和外部状态没有分清,或者没有合理地设计,可能会导致系统过度共享,导致共享的对象过多。
  • 增加代码的复杂度。程序员必须时刻根据系统的实际情况以及内部状态和外部状态的不同选择使用对象池或享元工厂来管理内存和共享对象,进一步增加了系统的复杂度。

四、应用场景

4.1 生活场景

  • 共享单车:在共享单车系统中,每辆车可以看做是一个享元对象。共享的部分是单车的基本属性,比如颜色、价格、型号等。相同型号、价格、颜色的单车可以共享同一个对象,不需要每次创建新的对象。
  • 车票预订:在一个车站的车票预订系统中,有许多订票窗口,每个窗口可以处理多个订票请求。每个订票请求都可以看作一个享元对象,而订票窗口可以看作是“享元工厂”。订票窗口根据请求的参数来创建或共享对象,避免创建过多的重复对象,提高了系统的性能。
  • 汉字输入法:在汉字输入法中,由于汉字数量很多,为了避免重复创建,使用享元模式,即相同的汉字只需要在内存中创建一次,这样就大大减少了内存的消耗,也加快了输入法的执行速度。

4.2 java场景

  • String 类型:内部维护了一个字符串对象池(即字符串常量池),相同的字符串只会在池中创建一次,之后它们会被多个对象共享,这样可以减少字符串对象的数量,降低内存的使用,并提升系统的效率。
  • Integer 类型:对常用的整数值(-127~128)进行了缓存,避免了每次创建新的 Integer 对象,提高了系统的性能。
  • 数据库连接池:数据库连接是非常消耗资源的,通过享元模式将连接池内部的连接对象进行复用,减少连接对象的创建和销毁,提高系统的效率和性能。
  • 线程池:通过享元模式将线程池中创建的线程对象进行复用,减少了线程的创建和销毁,从而提高系统的性能。

五、代码实现

下面以共享单车为例,解释一下享元模式。每一辆车可以看做是一个享元对象,共享的部分为颜色和价格。

  • 抽象享元角色:Bike
  • 具体享元角色:Mobike、MTBike
  • 享元工厂:BikeFactory

5.0 UML类图

 5.1 Bike——抽象享元类(FlyWeight)

/**
 * @author Created by njy on 2023/6/21
 * 1.抽象享元类(FlyWeight):单车
 * 单车有很多共同的信息比如:价格、颜色
 */
public abstract class Bike {

    protected String color;

    protected int price;

    public Bike(String color,int price){
        this.color=color;
        this.price=price;
    }

    //展示单车信息
    public abstract void show();

}

 5.2 具体享元类(ConcreteFlyWeight)

/**
 * @author Created by njy on 2023/6/21
 * 2.具体享元类(ConcreteFlyWeight):摩拜单车
 */
public class Mobike extends Bike{

    public Mobike(String color,int price){
        super(color,price);
    }

    @Override
    public void show() {
        System.out.println("生产成功:摩拜单车——"+color+","+"起步价"+price+"元");
    }
}
/**
 * @author Created by njy on 2023/6/21
 * 2.具体享元类(ConcreteFlyWeight):美团单车
 */
public class MTBike extends Bike{

    public MTBike(String color,int price){
        super(color,price);
    }

    @Override
    public void show() {
        System.out.println("生产成功:小黄车——"+color+","+"起步价"+price+"元");
    }
}

5.3 BikeFactory——享元工厂(FlyWeightFactory)

/**
 * @author Created by njy on 2023/6/21
 * 3.享元工厂类(FlyWeightFactory):单车工厂
 */
public class BikeFactory {
    //map缓存
    public static Map<String,Bike> bikeMap=new HashMap<>();
    public static Bike getBike(String color,String type){
        String key=color+"_"+type;
        if(bikeMap.containsKey(key)){
            //如果已经有该颜色和类型的单车,直接返回
            return bikeMap.get(key);
        }else {
            Bike bike=null;
            //没有,创建并放入缓存
            if("mobike".equals(type)){
                bike=new Mobike(color,1);
            }else if ("MT".equals(type)){
                bike=new MTBike(color,2);
            }else {
                System.out.println("抛异常!没有该类型的单车");
            }
            //放入缓存
            bikeMap.put(key,bike);
            return bike;
        }
    }
}

5.4 testFlyWeight

/**
 * @author Created by njy on 2023/6/21
 * 享元模式测试类
 */
@SpringBootTest
public class TestFlyweight {
    @Test
    void testFlyweight(){
        //摩拜单车
        Bike bike1 = BikeFactory.getBike("黑色", "mobike");
        //美团单车
        Bike bike2 = BikeFactory.getBike("黑色", "MT");
        bike1.show();
        bike2.show();
        System.out.println("总共有"+BikeFactory.bikeMap.size()+"种类型单车");

        System.out.println("--------创建相同类型和颜色的单车----------");
        Bike bike3 = BikeFactory.getBike("黑色", "mobike");
        bike3.show();
        //再次看有多少类型
        System.out.println("总共有"+BikeFactory.bikeMap.size()+"种类型单车");
    }
}

六、总结

享元模式其实就是工厂模式的一个改进机制,享元模式同样要求创建一个或一组对象,并且就是通过工厂方法生成对象的,不同的地方在于享元模式增加了缓存的功能。

主要的使用场景:

  1. 当程序中需要创建大量的相似对象时,可以使用享元模式来共享相同的内部状态,减少对象的重复创建,节省内存开销。
  2. 当系统运行效率较低,存在大量的内存开销时,可以采用享元模式来优化内存使用,加快程序的执行速度。
  3. 当系统中的对象访问比较频繁时,可以使用享元模式来优化系统性能,减少重复对象的创建和销毁,提高程序的运行效率。
  4. 当系统中需要共享一些公共对象或静态数据时,可以使用享元模式来降低系统的开销,提升系统的性能。
  5. 当系统需要考虑加强安全控制时,可以使用享元模式来保证对对象访问的控制,实现权限控制、缓存控制等功能。

从上述不难看出,享元模式适用于需要创建大量相似的对象以及共享公共信息的场景,同时也适用于需要考虑性能优化共享控制的系统。但是使用之前一定要考虑清楚,还是那句话,不要为了使用设计模式而去使用设计模式。

END:更多设计模式的介绍,推荐移步至👉 23种设计模式学习导航(Java完整版)👈 

<think>好的,我现在需要解决用户的问题,就是在Jupyter Notebook中用Python清洗CSV文件的name列,去除特定标点符号,并保存为UTF-8编码。首先,我得理清用户的具体需求,然后一步步思考如何实现。 用户的要求有几个关键点:只处理name列,移除纯标点符号的单元格,删除指定的标点符号,统计清洗的行数,保存为UTF-8到桌面。首先,我需要确定如何读取CSV文件,并只处理name列。可能用pandas库,因为它处理数据比较方便。 接下来,去除指定的标点符号。用户列出了一堆符号,比如!,逗号,引号,等号,问号等等。需要将这些符号从name列的每个单元格中移除。可能需要用正则表达式,替换掉这些字符。例如,使用str.replace方法,配合正则表达式,将所有指定符号替换为空字符串。 然后,处理那些单元格内容全是标点的情况。也就是说,如果一个单元格在去除标点后变成空字符串,那么需要将这个单元格所在的行删除。这时候可能需要用布尔索引,找出这些空值,并过滤掉这些行。 统计清洗的行数,可以在处理前后分别计算数据框的行数,差值就是被删除的行数。或者,在处理后统计被删除的行数。 保存为UTF-8编码,需要注意在to_csv时指定encoding参数为'utf-8',同时可能还要处理路径的问题,比如保存到桌面,可能需要用os.path.expanduser('~/Desktop')来获取路径。 现在需要详细考虑每一步的代码实现: 1. 导入必要的库,主要是pandas。 2. 读取CSV文件,使用pd.read_csv,确保只处理name列,可能用usecols参数指定列。 3. 定义需要移除的标点符号,可能需要将这些符号转义,放入正则表达式的字符集中。例如,对于符号如$, ^, ]等,正则表达式中有些需要转义,比如用反斜杠。 4. 使用str.replace替换掉这些字符。例如,pattern = r'[!“”,""",,=?\$¥。;@·&\#↑<>【】]'(可能需要检查是否遗漏或需要转义的符号)。 5. 处理后的name列可能会有空字符串,需要删除这些行,用df.dropna或df[df['name'].str.strip() != '']。 6. 统计清洗前后的行数差异,得到删除的行数。 7. 保存到桌面,使用to_csv,路径拼接正确,编码设为utf-8,可能还需要index=False。 需要测试一下正则表达式是否正确,特别是特殊符号的转义是否正确。例如,方括号【】可能需要转义,或者是否在正则表达式中正确表示。另外,逗号在字符集中是否需要转义?在正则表达式的字符集里,大部分符号不需要转义,除了如^、-、]等需要特殊处理。 例如,用户列出的符号包括“, ”(中文引号),可能需要正确放在字符集里。另外,注意有些符号可能重复,比如用户写了两个逗号,可能需要检查是否影响正则表达式。 另外,确保在清洗时,仅处理name列,其他列不受影响。但根据用户的要求,可能CSV文件中只有name列需要处理,或者用户希望只处理name列,其他列保留。需要明确这一点。 然后,关于去除纯标点符号的单元格,即在清洗后,name列为空字符串的行要删除。所以,在替换完所有标点后,用布尔条件筛选非空的行。 另外,统计清洗的行数,可以用原始数据行数减去处理后的行数,得到被删除的行数,或者在处理过程中标记这些行再统计。 现在,把这些步骤写成代码,并添加详细注释。 可能的代码步骤: 1. 导入pandas库。 2. 读取CSV文件,假设文件名为data.csv,并且name列的列名正确。 3. 定义需要去除的标点符号的正则表达式模式。 4. 应用str.replace到name列,替换所有指定符号为空。 5. 去除清洗后name列为空的行。 6. 计算删除的行数:原始行数 - 处理后行数。 7. 保存处理后的数据到桌面,路径正确,编码utf-8。 需要处理可能的错误,比如文件路径是否正确,列名是否存在等,但用户可能希望代码简洁,所以假设文件存在且列名正确。 现在检查正则表达式中的符号是否正确转义。例如,美元符号$在正则表达式中表示行尾,所以需要转义为\$,或者放在字符集的最前面或最后面。例如,正则表达式模式应为r'[!“”,"",=?\$¥。;@·&#↑<>【】]'。可能用户提到的符号包括中文标点,如“,”和“””,需要注意编码是否正确,但正则表达式中使用这些字符即可,不需要额外处理,只要Python文件保存为UTF-8编码。 另外,用户提到的符号中有“,”,可能需要确认在正则表达式中是否需要处理,比如中文逗号和英文逗号是否都被包含,或者用户是否希望都去除。例如,用户可能将中英文逗号都列出来了,所以在正则表达式中应包含这些符号。 比如,用户列出的标点包括:“!”、“,”(中文逗号)、“”(可能中文引号?)、“”(可能打错了?需要确认),然后是",,=,?,$,¥,。,;,@,·,&,#,↑,<,>,<>,【,】等。可能需要将所有这些符号放入正则表达式的字符集中。 例如,可能的正则表达式模式为: pattern = r'[!“”,""",=?\$¥。;@·&#↑<><>【】]' 但需要注意重复的符号,比如用户可能误写,例如<,>和<>,可能需要处理。但正则表达式中,字符集里的符号是单个匹配,所以如果用户想去除单独的<和>以及<>字符串,可能需要不同的处理方式。但根据用户描述,可能只是单独的符号,所以字符集中包括<和>即可,这样会匹配单独的<或>,而<>会被拆分成两个符号,分别被替换。 所以,正确的正则表达式应该包括所有列出的符号,每个作为单独的字符。例如: 符号列表:!,“,”,“”,",,=,?,$,¥,。,;,@,·,&,#,↑,<,>,<>,【,】 可能用户这里存在打字错误,比如“,”是中文逗号,“””可能是指中文的引号,例如“和”。所以可能需要将中文的“、”、英文的引号等正确包含进去。 例如,正则表达式中的符号应包括: !, “, ”, ,(中文逗号), ",(英文双引号),逗号,等号,问号,美元符号,¥,句号,分号,@,·,&,#,↑,<,>,【,】。 可能用户提到的“,”是中文逗号,所以在正则表达式中要包含中文的逗号和引号。例如,中文左双引号“和右双引号”可能需要正确输入到正则表达式中。因此,正确的正则表达式字符集应包括:[!“”‘’,,=?\$¥。;@·&#↑<>【】]等符号。需要注意这些符号在正则表达式中是否正确表示。 可能用户给出的符号中有些重复,比如用户提到了两个逗号,但可能只是示例。比如用户列出的标点符号包括“!,“,”,“”,",,=,?,$,¥,。,;,@,·,&,#,↑,<,>,<>,【,】等”,这可能存在排版问题,需要正确解析每个符号。例如,用户可能指的是!、“,”(中文逗号)、各种引号、逗号、等号、问号、美元符号、¥符号、句号、分号、@、·、&、#、↑、<、>、<>、【、】。但“<>”可能是指单独的<和>符号,而不是字符串<>。所以正则表达式中的字符集应包含单独的<和>,这样任何出现的<或>都会被替换。 现在,整理这些符号,并正确放入正则表达式的字符集中。注意有些符号在正则表达式中有特殊含义,比如$、^、]等,需要转义或用位置处理。 例如,$符号在正则表达式中表示行尾,所以需要转义为\$。而^如果出现在字符集中,并且不在开头的话,可能需要转义,或者放在非开头的位置。例如,字符集[abc^]中的^不是作为排除符号,而是作为普通字符。而字符集[^abc]中的^在开头表示排除。 因此,正确的正则表达式模式可能是: pattern = r'[!“”,"‘’=?,/$¥。;@·&#↑<>\^\[\]]' 但需要仔细检查每个符号是否需要转义。例如,方括号【和】可能需要用\]和\[]来表示?或者,在正则表达式中,字符集里的]需要用\]转义,而[不需要。例如,要匹配【和】,在字符集中应该写作【】,但需要注意在字符集中,]需要转义,除非它在第一个位置(在^之后)或者被转义。例如,正则表达式中的字符集应该为[...\]...],即如果要包含],需要用反斜杠转义,或者把它放在字符集的最前面。例如: 比如,要包含【和】,可以写成[\【\]],因为]在字符集中需要转义。或者,写成[]abc],这会导致错误,因为第一个]会被视为字符集的结束。正确的写法是[\]abc],这样]被转义,字符集包含], a, b, c。 因此,用户提到的【和】符号,在正则表达式中需要转义吗?在Python的正则表达式中,如果字符集中包含],必须用反斜杠转义,或者放在字符集的最前面。例如,模式中的【】应该写作【】的话,在字符集中可能需要正确放置。 例如,要匹配【和】,正则表达式中的字符集应写作[【\]],其中】被转义为\]。或者,写作[]【],这会导致错误,因为第一个]会结束字符集。所以正确的写法是[【\]] 因此,用户提到的符号中的【和】应该被正确转义。例如,在正则表达式中,字符集部分应该包括【和】,需要写作[\u3010\u3011],如果直接输入中文符号有问题的话,或者直接输入,但确保在Python字符串中正确编码。 假设在Jupyter Notebook中,文件的编码是UTF-8,可以直接在正则表达式中使用这些中文符号,不需要转义。因此,正则表达式模式可以写成r'[!“”,""",=?\$¥。;@·&#↑<>【】]',其中【和】直接放在字符集中,不需要转义,只要确保它们在字符集中正确出现,且]的位置正确。 例如,如果字符集是[...【]...],这样会导致错误,因为]在中间出现而没有转义。但如果是[...【\]...],则]被转义,可以正确匹配。或者,用户可能想匹配【和】,所以在字符集中写作【】,但需要考虑]是否需要转义。例如: 假设用户的标点符号包括【和】,那么在正则表达式的字符集中,应该写成[【\]],这样]被转义,从而字符集包含【和]。 或者,如果用户提供的标点符号列表中的【和】是分开的,比如用户写了【,】,那么可能在字符集中写作【】。但根据用户的问题描述,用户列出的符号包括【,】。所以,在字符集中应该包含【和】两个字符。此时,正确的写法是将】放在字符集中,并用转义符,或者调整顺序。 例如,正则表达式中的字符集可以写成[...【\]...],这样】被转义,作为普通字符。或者,将】放在字符集的最前面,如[]...],但这会被认为是空的字符集,导致错误。 因此,正确的处理方式是将】用反斜杠转义,即正则表达式中的模式为[...\\]...],在Python的原始字符串中,写作r'[...\]...]'。例如,模式中的【和】写作【和\],所以在字符集中应该包含【和\],即r'[!\“”,...【\]...]'。这样就能正确匹配这两个符号。 现在,将所有符号整理到正则表达式的字符集中: 符号列表:!,“,”,“”,",,=,?,$,¥,。,;,@,·,&,#,↑,<,>,<>,【,】 这可能存在排版错误,需要确认每个符号。例如,“,”可能指中文逗号,“””可能是指中文的左右引号。例如,“是左双引号,”是中文逗号,”“可能是左右引号连续出现?或者用户可能希望去除所有的中文引号、英文引号等。 可能需要将符号列表明确为: 需要移除的标点符号包括: !, “, ”, ,, ", ,, =, ?, $, ¥, ., ;, @, ·, &, #, ↑, <, >, 【, 】 注意这里可能存在用户输入时的符号重复或排版问题,例如用户提到的",,=,?中的两个逗号,可能是指英文逗号和中文逗号?或者可能用户误输入了多个逗号。需要尽可能明确用户的意图。 假设用户希望去除以下所有符号: !、 “(左双引号)、 ”(右双引号)、 ,(中文逗号)、 "(英文双引号)、 ,(英文逗号)、 =、 ?、 $、 ¥、 。(中文句号)、 ;、 @、 ·、 &、 #、 ↑、 <、 >、 【、 】 所以,正则表达式中的字符集应包含这些符号。将这些符号放入字符集中,注意转义特殊字符: 在正则表达式中,需要转义的字符有:$, ^, *, +, ?, ., |, \, (, ), [, ], {, }等。但是在字符集内部,只有^、-、\、]有特殊含义,其他字符不需要转义,除非需要表示字面量。例如,$在字符集中不需要转义,会被当作普通字符处理。但是,在字符集外部,$表示行尾,但在内部,它就是一个普通字符。 所以,字符集可以写成: pattern = r'[!“”",,=?$¥。;@·&#↑<>【\]\[ ]' 注意: - 左方括号[和右方括号]在字符集中需要处理。例如,要包含[,必须转义为\[,或者在字符集中放在不冲突的位置。但用户需要去除的是【和】,这两个是中文的方括号,Unicode字符为【(U+3010)和】(U+3011),所以不需要与正则表达式中的方括号冲突。因此,在字符集中直接包含【和】即可,不需要转义。例如: pattern = r'[!“”,"",=?$¥。;@·&#↑<>【】]' 这里假设“,”是中文逗号,而用户列出的符号中的“,”可能被正确输入。同时,英文逗号, 也需要包含在字符集中。因此,字符集中应该包含中文逗号,、英文逗号,,以及其他符号。 现在,假设正确的正则表达式模式是: pattern = r'[!“”,"",=?$¥。;@·&#↑<>【】]' 这里可能的问题是中英文逗号是否都被包含。中文逗号是“,”,而英文逗号是,,所以如果用户希望去除两者,那么应该在字符集中包含这两个符号。因此,字符集应该同时包含中文逗号和英文逗号,即: pattern = r'[!“”,,"=?$¥。;@·&#↑<>【】]' 这里,中文逗号“,”和英文逗号,都被包含在字符集中。此外,用户提到的标点可能包括英文双引号",所以在字符集中需要包含"符号,需要转义吗?在Python的字符串中,如果使用双引号定义字符串,那么内部的双引号需要转义,但这里使用原始字符串r'',所以可以直接放入,不需要转义,只要它被包含在字符集中。例如,字符集中的"会被当作普通字符处理,不需要转义。 总结,正则表达式模式应该包含所有用户指定的符号,正确转义必要的字符,其他符号直接放入字符集。 现在,编写代码时,确保正确应用这个正则表达式,替换掉所有指定符号。例如: df['name'] = df['name'].str.replace(pattern, '', regex=True) 然后,处理后的name列中可能有些单元格变为空字符串,需要删除这些行: df_cleaned = df[df['name'].str.strip() != ''] 或者: df_cleaned = df[df['name'].ne('')] 但需要注意,替换后可能还有空格或其他空白字符,所以使用str.strip()处理。 统计清洗的行数: original_count = len(df) cleaned_count = len(df_cleaned) removed_count = original_count - cleaned_count 最后,保存到桌面,路径可以用: import os desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop') output_path = os.path.join(desktop_path, 'cleaned_data.csv') df_cleaned.to_csv(output_path, index=False, encoding='utf-8') 现在,将这些步骤整合成代码,并添加详细注释: 确保代码在Jupyter中可运行,用户可能需要修改文件名,所以用变量表示。同时,处理可能的NaN值,比如原始数据中name列为空的情况,可能需要先dropna()。 例如,在读取CSV时,使用: df = pd.read_csv('input.csv', usecols=['name']) 或者,如果文件中有其他列,但只需要处理name列: df = pd.read_csv('input.csv') df = df[['name']] # 只保留name列 或者根据用户需求,可能其他列也需要保留,但用户的问题描述中说是“仅清洗仅包含name列”,所以可能假设CSV中只有name列,或者只处理name列,其他列保留。需要明确用户意图,但根据问题描述,用户想清洗name列,其他列可能保留,但代码中需要确保只处理name列。例如,在清洗时,不影响其他列,但用户的问题中可能CSV只有name列,所以代码处理整个文件,仅对name列进行清洗,删除该列为空的行。 现在,综合所有考虑,写出最终的代码,并添加注释: 可能需要处理的情况: 1. 读取CSV文件,可能使用pd.read_csv,指定正确的文件名。 2. 定义正则表达式模式,正确包含所有需要替换的符号。 3. 应用替换,处理后的name列。 4. 删除name列空的行。 5. 统计并输出清洗的行数。 6. 保存文件。 可能的代码如下: ```python # 导入所需库 import pandas as pd import os # 读取CSV文件(假设文件名为input.csv,根据实际情况修改) file_path = 'input.csv' df = pd.read_csv(file_path, usecols=['name']) # 仅读取name列 # 定义需要移除的标点符号正则表达式模式 pattern = r'[!“”,"",=?$¥。;@·&#↑<>【】]' # 清洗name列:移除指定标点符号 df['name_cleaned'] = df['name'].str.replace(pattern, '', regex=True) # 去除前后空格并替换空字符串为NaN df['name_cleaned'] = df['name_cleaned'].str.strip().replace('', pd.NA) # 删除name_cleaned为空的记录(原name列纯标点的情况) df_cleaned = df.dropna(subset=['name_cleaned']).copy() # 移除临时列,保留清洗后的name列 df_cleaned['name'] = df_cleaned['name_cleaned'] df_cleaned.drop(columns=['name_cleaned'], inplace=True) # 统计清洗情况 original_count = len(df) cleaned_count = len(df_cleaned) removed_count = original_count - cleaned_count # 输出清洗结果 print(f'原始行数:{original_count}') print(f'清洗后行数:{cleaned_count}') print(f'移除行数:{removed_count}') # 保存清洗后的文件到桌面 desktop = os.path.join(os.path.expanduser('~'), 'Desktop') output_path = os.path.join(desktop, 'cleaned_data.csv') df_cleaned.to_csv(output_path, index=False, encoding='utf-8') print('文件已保存至:', output_path) ``` 解释: - 使用usecols参数确保只读取name列。 - 创建name_cleaned列,存储清洗后的结果。 - 使用str.replace替换所有匹配的标点符号。 - 使用strip()去除前后空格,并将空字符串替换为NaN,方便后续删除。 - 使用dropna删除name_cleaned为NaN的行。 - 统计清洗前后的行数差异。 - 保存文件到桌面,使用UTF-8编码。 可能的问题: 1. 如果原始数据中的name列存在NaN,如何处理?此时,在清洗时,这些行会被保留吗?根据代码,清洗后的name_cleaned如果是NaN,会被删除。因此,原始中的NaN会被替换为空,然后被strip()后变成空字符串,再被替换为pd.NA,最后被dropna删除。因此,原始中的NaN行会被移除。 2. 是否需要处理其他列?根据用户的问题,可能只处理name列,其他列如果存在的话,会被保留吗?如果CSV有其他列,而代码只读取name列,那么其他列不会被保留。因此,如果用户需要保留所有列,但仅清洗name列,那么应该读取所有列,但只对name列进行处理,并删除name列清洗后为空的行的所有列。因此,代码可能需要调整: 例如: df = pd.read_csv(file_path) # 读取所有列 # 清洗name列,然后删除name列为空的行,保留其他列 在这种情况下,代码中的其他列会被保留,但仅当name列清洗后为空时,该行被删除。 因此,可能需要修改读取的部分: file_path = 'input.csv' df = pd.read_csv(file_path) # 读取所有列 # 清洗name列 df['name_cleaned'] = df['name'].str.replace(pattern, '', regex=True).str.strip().replace('', pd.NA) df_cleaned = df.dropna(subset=['name_cleaned']).copy() df_cleaned['name'] = df_cleaned['name_cleaned'] df_cleaned.drop(columns=['name_cleaned'], inplace=True) 这样,其他列会被保留,只有name列被清洗,并且删除清洗后name为空的整行。 用户的问题中提到“仅清洗指定标点符号”,所以可能应保留其他列。因此,正确的做法是读取所有列,仅处理name列,删除清洗后name为空的整行。 所以,之前的代码可能存在问题,即只读取了name列,导致其他列丢失。因此,需要修改代码,读取所有列,仅处理name列: ```python import pandas as pd import os # 读取CSV文件(包含所有列) file_path = 'input.csv' df = pd.read_csv(file_path) # 定义需要移除的标点符号正则表达式模式 pattern = r'[!“”,"",=?$¥。;@·&#↑<>【】]' # 清洗name列:移除指定标点符号 df['name_cleaned'] = df['name'].str.replace(pattern, '', regex=True) # 去除前后空格并替换空字符串为NaN df['name_cleaned'] = df['name_cleaned'].str.strip().replace('', pd.NA) # 删除name_cleaned为空的记录(原name列纯标点的情况) df_cleaned = df.dropna(subset=['name_cleaned']).copy() # 恢复原始列名并移除临时列 df_cleaned['name'] = df_cleaned['name_cleaned'] df_cleaned.drop(columns=['name_cleaned'], inplace=True) # 统计清洗情况 original_count = len(df) cleaned_count = len(df_cleaned) removed_count = original_count - cleaned_count # 输出清洗结果 print(f'原始行数:{original_count}') print(f'清洗后行数:{cleaned_count}') print(f'移除行数:{removed_count}') # 保存清洗后的文件到桌面 desktop = os.path.join(os.path.expanduser('~'), 'Desktop') output_path = os.path.join(desktop, 'cleaned_data.csv') df_cleaned.to_csv(output_path, index=False, encoding='utf-8') print('文件已保存至:', output_path) ``` 这样,处理后的数据将保留所有列,仅清洗name列,并删除清洗后name为空的整行。 现在,检查正则表达式是否正确。例如,符号中的<和>是否被正确包含,是否会导致问题。在字符集中,<和>不需要转义,所以直接放入即可。 测试例子: 假设name列的一个单元格是“测试!商品<名称>”,应用替换后应该变成“测试商品名称”。 另一个例子是“¥100.00;”,替换后变成“10000”。 但可能用户希望将标点全部去除,仅保留文字和数字。但根据用户的要求,仅移除指定的标点符号,所以其他字符如字母、数字、未被列出的标点将保留。 因此,代码中的正则表达式是正确的。 最后,确保代码在Jupyter Notebook中可以运行,用户只需修改文件名即可。同时,路径部分是否正确,比如保存到桌面,不同操作系统可能路径不同,但os.path.expanduser('~')应该能正确获取用户主目录,再拼接Desktop。</think>以下是分步解决方案,帮助您在Jupyter Notebook中完成CSV文件的清洗工作: ### 1. 导入必要库 ```python import pandas as pd import os ``` ### 2. 读取CSV文件 ```python # 读取文件(根据实际路径修改) file_path = 'your_file.csv' df = pd.read_csv(file_path) print(f'原始数据样例:\n{df.head(2)}') ``` ### 3. 定义清洗规则 ```python # 需要移除的标点符号正则表达式模式 pattern = r'[!“”,"",=?$¥。;@·&#↑<>【】]' # 包含所有指定符号 ``` ### 4. 执行清洗操作 ```python # 创建临时列存储清洗结果 df['name_cleaned'] = ( df['name'] .str.replace(pattern, '', regex=True) # 移除指定符号 .str.strip() # 去除首尾空格 .replace('', pd.NA) # 空字符串转为NaN ) # 删除无效记录 df_cleaned = df.dropna(subset=['name_cleaned']).copy() # 恢复原始列结构 df_cleaned['name'] = df_cleaned['name_cleaned'] df_cleaned = df_cleaned.drop(columns=['name_cleaned']) ``` ### 5. 统计清洗结果 ```python print(f'原始行数:{len(df)}') print(f'清洗后行数:{len(df_cleaned)}') print(f'移除行数:{len(df) - len(df_cleaned)}') ``` ### 6. 保存清洗文件 ```python # 生成桌面路径 desktop_path = os.path.join(os.path.expanduser('~'), 'Desktop') output_path = os.path.join(desktop_path, 'cleaned_data.csv') # 保存文件(UTF-8编码) df_cleaned.to_csv(output_path, index=False, encoding='utf-8') print(f'文件已保存至:{output_path}') ``` ### 代码说明 1. **正则表达式模式**:`r'[!“”,"",=?$¥。;@·&#↑<>【】]'` 使用字符集匹配所有指定符号,`regex=True`启用正则表达式替换 2. **空值处理**:通过`.replace('', pd.NA)`将空字符串转为NaN,便于后续使用`dropna`删除无效行 3. **路径处理**:`os.path.expanduser('~')`自动适配不同操作系统的用户目录 ### 注意事项 1. 如果CSV中包含其他需要保留的列,代码会自动保留 2. 清洗后的数字、字母和未指定符号会保留 3. 中文标点如`「」`若需要处理,可添加到正则表达式中
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橡 皮 人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值