Contents

ARST打卡第210周[210/521]

Algorithm

2441_与对应负数同时存在的最大正整数

直接用 unordered_set hash数组保存过往的数字,然后每次查一下以前是否有相反数,然后维护最大绝对值即可

题解还有排序+双指针和暴力枚举的做法就不多介绍了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
public:
    int findMaxK(vector<int>& nums) {
        unordered_set<int> hash_set;
        int ans = -1;
        // 参考题解发现题解一次性把插入解决了
        // unordered_set<int> s(nums.begin(), nums.end());
        hash_set.insert(nums[0]);
        for (int i=1; i<nums.size(); i++) {
            // if (s.count(-x))
            // 题解的判断方式也挺好
            if (hash_set.find(-nums[i]) != hash_set.end()) {
                ans = max(ans, abs(nums[i]));
            }
            hash_set.insert(nums[i]);
        }
        return ans;
    }
};

Review

【TED演讲】如何有目的生活

珍惜自己手上拥有的东西(才能,资源等),然后用这些东西去让世界变得更加美好

人的价值不在于拥有多少,而在于给世界创造了多少

Tips-如何阅读源代码(耗子叔的思路)

首先是阅读代码之前,最好先有以下了解:

  1. 基础知识:相关的语言和基础技术的知识;
  2. 软件功能:需要知道这个软件是做什么的、有哪些特性、哪些配置项,最好能够读一遍用户手册,然后让软件跑起来,自己先用一下感受一下;
  3. 相关文档:读一下相关的内部文档;
  4. 代码的组织结构:先简单看下源码的组织结构。

接下来,就是详细地看代码的实现,这里耗子叔分享了一个源代码阅读的经验:

  1. 接口抽象定义:任何代码都会有很多接口或抽象定义,其描述了代码需要处理的数据结构或者业务实体,以及它们之间的关系,理清楚这些关系是非常重要的;
  2. 模块粘合层:我们的代码有很多都是用来粘合代码的,比如中间件(middleware)、Promises 模式、回调(Callback)、代理委托、依赖注入等。这些代码模块间的粘合技术是非常重要的,因为它们会把本来平铺直述的代码给分裂开来,让你不容易看明白它们的关系;
  3. 业务流程:这是代码运行的过程。一开始,我们不要进入细节,但需要在高层搞清楚整个业务的流程是什么样的,在这个流程中,数据是怎么被传递和处理的。一般来说,我们需要画程序流程图或者时序处理图;
  4. 具体实现:了解上述的三个方面的内容,相信你对整个代码的框架和逻辑已经有了总体认识。这个时候,你就可以深入细节,开始阅读具体实现的代码了。对于代码的具体实现,一般来说,你需要知道下面一些事实,这样有助于你在阅读代码时找到重点。
  • 代码逻辑:代码有两种逻辑,一种是业务逻辑,这种逻辑是真正的业务处理逻辑;另一种是控制逻辑,这种逻辑只是用控制程序流转的,不是业务逻辑。比如:flag 之类的控制变量,多线程处理的代码,异步控制的代码,远程通讯的代码,对象序列化反序列化的代码等。这两种逻辑你要分开,很多代码之所以混乱就是把这两种逻辑混在一起了;
  • 出错处理:根据 2:8 原则,20% 的代码是正常的逻辑,80% 的代码是在处理各种错误,所以,你在读代码的时候,完全可以把处理错误的代码全部删除掉,这样就会留下比较干净和简单的正常逻辑的代码。排除干扰因素,可以更高效地读代码;
  • 数据处理:只要你认真观察,就会发现,我们好多代码就是在那里倒腾数据。比如 DAO、DTO,比如 JSON、XML,这些代码冗长无聊,不是主要逻辑,可以不理;
  • 重要的算法:一般来说,我们的代码里会有很多重要的算法,我说的并不一定是什么排序或是搜索算法,可能会是一些其它的核心算法,比如一些索引表的算法,全局唯一 ID 的算法,信息推荐的算法、统计算法、通读算法(如 Gossip)等。这些比较核心的算法可能会非常难读,但它们往往是最有技术含量的部分;
  • 底层交互:有一些代码是和底层系统的交互,一般来说是和操作系统或是 JVM 的交互。因此,读这些代码通常需要一定的底层技术知识,不然,很难读懂;
  1. 运行时调试:很多时候,代码只有运行起来了,才能知道具体发生了什么事,所以,我们让代码运行进来,然后用日志也好,debug 设置断点跟踪也好。实际看一下代码的运行过程,是了解代码的一种很好的方式。

总结一下,阅读代码的方法如下。

  • 一般采用自顶向下,从总体到细节的【剥洋葱皮】的读法;
  • 画图是必要的,程序流程图,调用时序图,模块组织图;
  • 代码逻辑归一下类,排除杂音,主要逻辑才会更清楚;
  • debug 跟踪一下代码是了解代码在执行中发生了什么的最好方式。

另外这里也有个其他见解 https://www.codedump.info/post/20200605-how-to-read-code-v2020/ 兼听则明

转载自: https://wanghenshui.github.io/2021/05/28/code-review.html

Share-C++编程中的工厂模式

在C++编程中,工厂模式(Factory Pattern)是一种创建对象的设计模式。它提供了一种将对象的创建与使用代码解耦的方式,使得代码更具灵活性和可维护性。工厂模式通常包括以下几个关键组件:

  1. 抽象产品(Abstract Product):定义产品的共同接口或抽象基类。抽象产品可以是一个纯虚类,只声明了接口而没有实现。
  2. 具体产品(Concrete Product):实现抽象产品接口的具体类。每个具体产品对应工厂模式中的一个产品类型。
  3. 抽象工厂(Abstract Factory):定义了创建抽象产品的接口。它可以是一个纯虚类或者接口类,提供了创建产品的方法。
  4. 具体工厂(Concrete Factory):实现抽象工厂接口,负责创建具体产品的实例。

使用工厂模式的一般步骤如下:

  1. 定义抽象产品接口:创建一个抽象产品类或接口,声明产品的公共方法。
  2. 实现具体产品:针对每个具体产品类型,创建一个具体产品类,实现抽象产品接口的方法。
  3. 定义抽象工厂接口:创建一个抽象工厂类或接口,声明用于创建产品的方法。
  4. 实现具体工厂:针对每个具体产品类型,创建一个具体工厂类,实现抽象工厂接口的方法。在每个方法中,根据需要创建对应的具体产品实例,并返回抽象产品接口。

通过使用工厂模式,客户端代码可以通过与抽象工厂和抽象产品进行交互,而无需直接与具体产品进行耦合。这样可以使得代码更加灵活,可以轻松地替换具体产品的实现或者扩展产品的种类,而无需修改客户端代码。

工厂模式还有一些变体,如简单工厂模式、工厂方法模式和抽象工厂模式,它们在实现方式和应用场景上有所不同,但都遵循了工厂模式的基本原则。根据具体的需求和设计目标,选择适合的工厂模式变体来实现对象的创建和管理。

以下是一个简单的工厂模式的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>

// 抽象产品
class Product {
public:
    virtual void use() = 0;
    virtual ~Product() {}
};

// 具体产品A
class ConcreteProductA : public Product {
public:
    void use() override {
        std::cout << "Using ConcreteProductA" << std::endl;
    }
};

// 具体产品B
class ConcreteProductB : public Product {
public:
    void use() override {
        std::cout << "Using ConcreteProductB" << std::endl;
    }
};

// 抽象工厂
class Factory {
public:
    virtual Product* createProduct() = 0;
    virtual ~Factory() {}
};

// 具体工厂A,用于创建具体产品A
class ConcreteFactoryA : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductA();
    }
};

// 具体工厂B,用于创建具体产品B
class ConcreteFactoryB : public Factory {
public:
    Product* createProduct() override {
        return new ConcreteProductB();
    }
};

int main() {
    // 使用具体工厂A创建产品
    Factory* factoryA = new ConcreteFactoryA();
    Product* productA = factoryA->createProduct();
    productA->use();

    // 使用具体工厂B创建产品
    Factory* factoryB = new ConcreteFactoryB();
    Product* productB = factoryB->createProduct();
    productB->use();

    // 释放资源
    delete factoryA;
    delete productA;
    delete factoryB;
    delete productB;

    return 0;
}

在上述示例中,我们定义了一个抽象产品 Product ,具有一个纯虚函数 use() 。然后,我们定义了两个具体产品 ConcreteProductAConcreteProductB ,它们分别实现了 Product 接口。

接下来,我们定义了抽象工厂 Factory ,具有一个纯虚函数 createProduct() ,用于创建产品对象。然后,我们创建了两个具体工厂 ConcreteFactoryAConcreteFactoryB ,它们分别实现了 Factory 接口,并在其 createProduct() 方法中创建了对应的具体产品对象。

最后,在 main() 函数中,我们使用具体工厂 ConcreteFactoryAConcreteFactoryB 创建了具体产品 ConcreteProductAConcreteProductB 的实例,并调用了它们的 use() 方法进行使用。

通过工厂模式,客户端代码与具体产品的创建过程解耦,只与抽象工厂和抽象产品进行交互。这样,如果需要替换产品的实现或者扩展产品的种类,只需要修改工厂的具体实现,而不需要修改客户端代码。

1
clang++ factory.cpp -std=c++11 -o factory.out