OM | 强化学习 + 约束规划求解组合优化问题

news2024/9/21 23:43:49

组合优化在航空航天、交通规划以及经济学等众多学科领域中有广泛应用,其目标是在有限集中寻找最优解。然而状态空间过大的问题让目前组合优化变得棘手。在过去的几年中,使用深度强化学习(deep reinforcement learning,DRL)解决组合优化问题受到广泛关注。然而,现有的方法有两大缺点:

  1. 大部分工作主要集中在标准的TSP问题上,推广到其他问题并不容易。
  2. 只能提供一个近似最优解或者满意解,没有一个系统的方法来提升解的质量或者证明其最优性。

另一方面,约束规划(constraint programming,CP)是一种用于解决组合优化问题的有效方法,基于完全搜索的策略,如果时间足够,总可以找到最优解。分支决策是CP的关键选择,用于指导如何探索剩余的搜索空间。

本篇论文 [1] 的工作提出了一种基于DRL和CP的通用方法来解决组合优化问题。方法的核心是将动态规划作为连接两种方法的桥梁,首先通将组合优化问题建模成动态规划形式,再使用深度强化学习方法学习分支搜索方法,将学习到的agent用于CP的分支选取中,从而进一步提升CP的搜索效率提升算法性能。具体的结构如图1所示。

整体结构分为三个阶段,统一表示阶段是指将组合优化问题描述为动态规划模型,该动态规划模型既可以进一步描述为深度强化学习的训练形式用于训练agent,也可以进一步描述为CP模型并根据分支选择策略进行搜索求解。学习阶段主要通过随机生成的方法生成很多约束规划问题的样本,并根据样本进行求解训练。求解阶段将需要求解的问题实例通过动态规划描述为CP模型,并根据学习到的agent进行分支选择求解。图1中的蓝色模块为已有的成熟算法,绿色模块为本文的工作。
在这里插入图片描述图1 本篇论文总体方法架构

总的来说,本文结合了search-based和learning-based两种方法。基于搜索的方法可以保证最优解,但是往往不能从历史数据中得到经验,并且搜索过程有很大的提升空间。基于学习的方法可以利用历史经验,但是不能保证最优并且没有行之有效的提升手段。本文的方法结合了两者的优点,既可以保证最优解,又可以利用历史经验,同时还可以提升搜索效率。

将组合优化写为动态规划(DP)模型

动态规划是一种结合数学建模和编程的方法,通常用于解决复杂的优化问题。动态规划主要通过将问题分解为多阶段子问题,并通过状态转移方程将不同状态联系起来。通过最终的状态反向递推求解每个阶段的解。

考虑优化问题 Q : { max ⁡ f ( x ) : x ∈ X ⊆ Z n } \mathcal{Q}:\left\{\max f(x): x \in X \subseteq \mathbb{Z}^n\right\} Q:{maxf(x):xXZn}其中 x i x_i xi是决策变量,用于最小化目标函数 f ( x ) f(x) f(x)。将其改造后的动态规划由以下几部分组成 ⟨ S , X , T , R , V , P ⟩ \left \langle S,X,T,R,V,P\right \rangle S,X,T,R,V,P,现分别进行介绍。

S S S:系统状态:用于描述阶段 i , i ∈ { 1 , . . . , n } i,i \in \{1,...,n\} i,i{1,...,n}时的系统状态 s i s_i si

X X X:系统控制:用于描述阶段 i i i时系统实施的控制 x i x_i xi,其定义域是 D ( x i ) D(x_i) D(xi)

T T T:状态转移函数:用于描述阶段 i i i时系统状态 s i s_i si经过控制 x i x_i xi转移到下一阶段的状态 s i + 1 s_{i+1} si+1,即 T : S × X → S T:S\times X\rightarrow S T:S×XS

R R R:回报函数:用于描述阶段 i i i时系统状态 s i s_i si经过控制 x i x_i xi得到的回报 R ( s i , x i ) R(s_i,x_i) R(si,xi),即 R : S × X → R R:S\times X\rightarrow \mathbb{R} R:S×XR

V V V:可行条件:用于描述阶段 i i i时系统状态 s i s_i si是否可行,用于约束可行动作集,即 V : S × X → { 0 , 1 } V:S\times X\rightarrow \{0,1\} V:S×X{0,1}

P P P:支配条件:用于描述阶段 i i i时系统状态 s i s_i si是否被支配,同样用于约束可行动作集,即 P : S × X → { 0 , 1 } P:S\times X\rightarrow \{0,1\} P:S×X{0,1}

V , P V,P V,P都是用于约束可行动作集的,二者的区别是可行条件V是用于保证模型的可行性,而支配条件 P是用于动作集简化的效率提升手段。

本问题可以通过贝尔曼方程递归迭代求解,即 g i : X → R g_i:X\rightarrow \mathbb{R} gi:XR表示阶段i时的最优值函数,其递归迭代公式为 g i ( s i ) = max ⁡ { R ( s i , x i ) + g i + 1 ( T ( s i , x i ) ) } ∀ i ∈ { 1.. n }  s.t.  T ( s i , x i ) ≠ ⊥ g_i\left(s_i\right)=\max \left\{R\left(s_i, x_i\right)+g_{i+1}\left(T\left(s_i, x_i\right)\right)\right\} \quad \forall i \in\{1 . . n\} \text { s.t. } T\left(s_i, x_i\right) \neq \perp gi(si)=max{R(si,xi)+gi+1(T(si,xi))}i{1..n} s.t. T(si,xi)=

在最终的时刻回报函数是0,依次递推至初始时刻,该值就是Q的最优值,然后通过递推依次赋值给每个阶段的控制 x i x_i xi并进行存储,即可得到最优解。

但是通常动态规划问题面临维数灾挑战,通常的操作是对支配动作进行剪枝,如果满足以下两个条件之一,该动作会被剪枝:​

•根据递推公式,严格地比另一个动作差​
•无法产生一个可行的状态转移​

但是在实际问题中,虽然剪枝策略可以有效的减少搜索空间,但是由于这两个条件是problem-dependent,因此识别这两个条件开销比较大。此外,有的问题即使进行了剪枝,动作集的维数仍然很大。

RL Encoding

强化学习的主要目的是研究并解决智能体的序贯决策问题,可以用四元组<S,A,T,R>定义强化学习的行为。其中, S S S为状态集合, A A A为动作集合, T T T为转移函数, R R R为奖励函数。注意在累积奖励函数中,删除了discounting factor,这是针对优化问题这一具体应用场景的改进。主流强化学习方法包括两类,即value-basedpolicy-based。Value-based的代表性算法Deep Q-learning (DQN),其应用深度网络近似action-value function,即 Q ^ ( s , a , w ) ≈ Q ( s , a ) \hat{Q}(s,a,\boldsymbol{w})\approx Q(s,a) Q^(s,a,w)Q(s,a),进而选择对应值最大的动作。Policy-based的代表性算法Proximal policy gradient (PPO),其基于policy gradient theorem,即 ∇ π ( V π ( s 0 ) ) = E π [ ∇ w l n π ( a ∣ s , w ) Q π ( s , a ) ) ] \nabla_\pi(V^\pi(s_0))=\mathbb{E}_\pi[\nabla_{\boldsymbol{w}}ln\pi(a|s,\boldsymbol{w})Q^\pi(s,a))] π(Vπ(s0))=Eπ[wl(as,w)Qπ(s,a))]直接应用梯度下降优化智能体策略 π \pi π

由于前文已经将原优化问题建模为DP形式,并得到了对应的六元组。本节的关键在于:如何利用六元组和一个给定问题实例 Q p \mathcal{Q}_p Qp生成强化学习所需的四元组?

State

对于DP模型的每一个阶段 i i i,我们定义其状态为 ( Q p , s i ) (\mathcal{Q}_p,s_i) (Qp,si)。其中 s i s_i si是动态变化的,并根据DP模型进行每阶段的实时更新; Q p \mathcal{Q}_p Qp是静态的,在每一个episode保持不变。在实践中,需要设计良好的嵌入层(Embedding layer),以提取状态向量的特征,并进一步将嵌入处理的结果输入后续的神经网络。

Action

给定DP模型在第 i i i阶段的状态 x i x_i xi和控制量 x i x_i xi,当前阶段的动作 a i ∈ A a_i\in\mathsf{A} aiA x i x_i xi具备一一对应关系。当然,在选择某个动作之前,需要根据有效性条件(Validity conditions)、支配条件(Dominance conditions)等判定其可行性。具体公式表示为 A i = { v i ∣ v i ∈ D ( x i ) ∧ V ( s i , v i ) = 1 ∧ P ( s i , v i ) = 1 } \mathsf{A}_i=\{v_i|v_i\in D(x_i)\wedge V(s_i,v_i)=1\wedge P(s_i,v_i)=1\} Ai={viviD(xi)V(si,vi)=1P(si,vi)=1}

Transition

RL的状态转移过程与DP一致,具体公式表示为 s i + 1 = T ( s i , a i ) = ( Q p , T ( s i , a i ) ) = ( Q p , T ( s i , v i ) ) s_{i+1}=\mathsf{T}(s_i,a_i)=(\mathcal{Q}_p,T(s_i,a_i))=(\mathcal{Q}_p,T(s_i,v_i)) si+1=T(si,ai)=(Qp,T(si,ai))=(Qp,T(si,vi))

Reward

奖励函数的设计对于RL来说至关重要,其提供了关键的训练反馈信息。本文指出,在具体的优化问题场景中,首先找到可行解(Feasible solution)相对于最大化奖励值,要处在更高的优先级上。基于此,本文提出了两条奖励函数设计时需要遵守的性质:(1)如果episode e 1 e_1 e1得到的解比episode e 2 e_2 e2的差,那么episode e 1 e_1 e1收集的奖励应该相对更少;(2)存在不可行解的episode收集的奖励,应比得到可行解的episode收集的奖励更少。由此,奖励函数使得智能体首先找到可行解,然后再不断提升可行解的质量。奖励函数具体为 R ( s , a ) = ρ × ( 1 + ∣ U B ( Q p ) ∣ + R ( s , a ) ) \mathsf{R}(s,a)=\rho\times(1+|\mathsf{UB}(\mathcal{Q}_p)|+R(s,a)) R(s,a)=ρ×(1+UB(Qp)+R(s,a))其中, U B ( Q p ) \mathsf{UB}(\mathcal{Q}_p) UB(Qp)表示组合优化问题 Q p \mathcal{Q}_p Qp目标函数值的上界(Upper bound)。缩放因子 ρ \rho ρ将奖励值的空间进行压缩,到更接近于0的区间值。

Network Architecture and Learning Algorithm

为了保证所提出框架的普适性和高效性,针对优化问题的特点,本文引入了两种深度网络架构,以对输入状态进行高质量的嵌入(Embedding)表征。第一个架构是图注意力网络(Graph attention network, GAT),这是由于许多组合优化问题天然地具备图结构,例如旅行商问题、车辆路径规划问题等。第二个架构是Set Transformer,这是由于在诸如投资组合优化问题中,我们希望保证输入变量顺序的不变性,例如编码的输入变量 { x 1 , x 2 , x 3 } \{x_1, x_2, x_3\} {x1,x2,x3} { x 3 , x 2 , x 1 } \{x_3, x_2, x_1\} {x3,x2,x1}应生成一致的输出。

无论是应用DQN还是PPO训练架构,本文均首先随机生成一系列训练样本,并使得训练样本的分布与我们试图解决问题的分布一致,这也是目前绝大多数应用机器学习求解NP-hard优化问题的研究的做法。

CP Encoding

约束规划(constraint programming,CP)是一种泛用性比较好的算法框架,在实际应用中常用于处理复杂的组合问题。因为应用场景和算法框架的相似,但却不像后者应用范围如此之广,所以人们常常将CP与整数规划(LP)进行比较,来更好的理解CP的特点与优势,如下图所示,虽然两种算法的框架都是系统性的搜索,但是在搜索中,CP不需要同时保证所有约束成立,而是对约束逐一分析,即约束传播(constraint propagation),再对变量的值域进行过滤,这使得CP在更复杂的组合问题(可行域连续性较差)中经常有超过LP的表现。
在这里插入图片描述
本文中的 CP 问题,可以使用一个四元组进行定义,其中 X X X 是所有变量构成的集合, D D D 是所有变量的值域构成的集合, C C C 是所有约束条件的集合, O O O 是目标函数。本节将会讲解如何通过 DP 模型的六元组得到 CP 所需的四元组并使用该四元组构建 CP 求解模型。

X X X:CP 问题中所需的变量分为两类,与 DP 相同,共有 n 各阶段,每个阶段有两个变量,分别为辅助变量 X i s X_i^s Xis 代表 i 阶段当前模型的状态,决策变量 X i a X_i^a Xia 代表 i 阶段所能够执行的决策动作,值得注意的是求解过程中只对决策变量进行搜索,辅助变量的值可在决策变量确定后通过约束得到。

D D D: CP 问题求解需要所有变量的值域,本文中两类变量的值域分别为 DP 中 S S S X X X 的值域集合。

C C C:包含所有 CP 模型中的约束条件,是 CP 建模的核心,具体的约束如下:

X i s = ϵ ( 1 ) X_i^s = \epsilon \quad(1) Xis=ϵ1
X i s + 1 = T ( X i s , X i a ) ∀ i ∈ { 1 , . . . , n } ( 2 ) X_i^{s+1} = T(X_i^s, X_i^a)\quad \forall i \in\{1, . . .,n\}\quad(2) Xis+1=T(Xis,Xia)i{1,...,n}(2)
v a l i d i t y C o n d i t i o n ( X i s , X i a ) ∀ i ∈ { 1 , . . . , n } ( 3 ) validityCondition(X_i^s, X_i^a) \quad\forall i \in\{1, . . .,n\}\quad (3) validityCondition(Xis,Xia)i{1,...,n}(3)
d o m i n a n c e C o n d i t i o n ( X i s , X i a ) ∀ i ∈ { 1 , . . . , n } ( 4 ) dominanceCondition(X_i^s, X_i^a) \quad\forall i \in\{1, . . .,n\}\quad(4) dominanceCondition(Xis,Xia)i{1,...,n}(4)

约束(1)代表初始状态设置为一个值(例如 ϵ \epsilon ϵ)(2)是通过 DP 的转换函数 T T T 将每个阶段的状态链接到前一个阶段 (3)使用 DP 中的 V V V 检验每个阶段状态的可行性 (4)添加 DP 中的 P P P 增加冗余约束加快收敛

O O O:给出 CP 问题的目标方程,本文中通过将每个阶段 CP 的决策变量与环境变量的取值对应到 DP 问题中得到每个阶段的回报 R R R 并求和。

max ⁡ x a ∑ i = 1 n R ( X i s , X i a ) \max_{x^a}\sum_{i=1}^{n} R(X_i^s, X_i^a) xamaxi=1nR(Xis,Xia)

Search Strategy

从前文中,我们已经将同一个DP模型同时转换为RL模型和CP模型,这使得我们可以将RL中训练得出的agent用于CP求解的搜索过程中,针对于不同的应用场景和不同的RL模型,本文提供了三种不同的CP搜索策略,深度优先分枝定界depth-first-branch-and-bound search (BaB),有限差异束搜索iterative limited discrepancy search (ILDS) 基于数值优化进行搜索,更适合于DQN训练出的Agent,而重启搜索restart based search需要一定的策略随机性,更适合policy-based的强化学习训练模型。

实验结果

为了评估算法的性能,本文在两个应用场景上分别测试了三种基于RL的CP搜索策略的算法,RL和CP不结合各自单独求解的算法以及一些工业求解器的算法,其中第一个测试场景为the travelling salesman problem with time windows (TSPTW),是包含非线性约束的常见组合优化问题,另一个测试场景为4-moments portfolio optimization problem (PORT),则是目标为非线性函数的经典组合优化问题。为确保公平性,训练时常都限制再48小时之内,内存占用在1Gb之内,都使用同一台GPU (Tesla V100-SXM2-32GB)执行所有算法。

TSPTW 实验结果

Table1中,我们可以看在城市的数量增长后 OR-Tools、CP-model 和 DQN 的结果明显差于各种RL和CP的混合方法。在混合方法中,基于 DQN 的搜索在寻找解决方案和证明最优性方面都给出了最好的结果。同时使用cache能够明显的提升混合算法的效率。这是因为RL训练得到模型调用次数极多,使用缓存会节约大量的搜索时间。

Table 1 主要实验结果1
在这里插入图片描述

PORT实验结果

Table2中,首先考虑目标函数连续的实例,其中size最小的实例,我们观察到 B a B − D Q N ⋆ BaB-DQN^{\star} BaBDQN, I L D S − D Q N ⋆ ILDS-DQN^{\star} ILDSDQN, 和 C P CP CP模型取得了最好的结果,但只有 B a B − D Q N ⋆ BaB-DQN^{\star} BaBDQN找到了所有的最优解。对于size较大的实例,非线性求解器可获得最佳结果,但紧随其后的是 R B S − P P O ⋆ RBS-PPO^{\star} RBSPPO。目标函数不连续时,非线性求解器由于难以利用求导来求解,而不再占有优势甚至APOPT不再支持这种问题的求解。但混合方法不受此影响,因为事先没有对 DP 公式进行连续性假设。从结果上看,各种混合式的求解算法效果更为优秀。

Table 2 主要实验结果2
在这里插入图片描述
Table 2 各算法在100个算例上的运行结果,其中高亮的是表现最好的算法,Sol代表算法给出的最优平均收益,Opt代表达到最优解的算例个数

总结

约束规划算法在组合优化中应用广泛且在复杂组合优化中表现出色,但性能受限于变量的搜索策略,本文通过动态规划作为桥梁联通强化学习和约束规划,将DQN和PPO等强化学习策略和BaB、RBS等搜索策略相结合,使得强化学习训练得到的经验能够帮助更好的进行CP搜索,同时在TSPTW和PORT两个普通组合优化算法表现不佳的非线性应用场景中进行了测试,展现了混合算法的显著提升。

参考文献

[1] Cappart, Q., Moisan, T., Rousseau, L. M., Prémont-Schwarz, I., & Cire, A. A. (2021, May). Combining reinforcement learning and constraint programming for combinatorial optimization. In Proceedings of the AAAI Conference on Artificial Intelligence (Vol. 35, No. 5, pp. 3677-3687).
[2] Burak Gökgür, Brahim Hnich & Selin Özpeynirci (2018) Parallel machine scheduling with tool loading: a constraint programming approach, International Journal of Production Research, 56:16, 5541-5557, DOI: 10.1080/00207543.2017.1421781
[3] Kanet, John J.; Ahire, Sanjay L.; and Gorman, Michael F., “Constraint Programming for Scheduling” (2004). MIS/OM/DS Faculty Publications. Paper 1.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/341790.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SharpSCCM:一款利用SCCM实现横向渗透的强大工具

关于SharpSCCM SharpSCCM是一款功能强大的横向渗透和凭证收集工具&#xff0c;该工具主要利用了微软终端配置管理器&#xff08;SCCM&#xff09;来实现其功能&#xff0c;并能够通过访问SCCM管理终端GUI来实现横向渗透和凭证收集。 功能介绍 1、后渗透功能&#xff0c;支持横…

QT入门Input Widgets之QSlider

目录 一、界面布局功能 1、界面位置介绍 2、控件界面基本属性 2.1horizontalSlider界面属性 2.2设置步进 2.3打开或关闭滑块跟踪&#xff08;setTracking&#xff09; 二、属性功能介绍 1、常用信号 2、调用信号 3、鼠标点击QSlider滑块定在点击位置 三、Demo展示 …

2.8、调度算法的评价指标

1、CPU 利用率 由于早期的 CPU 造价极其昂贵&#xff0c; 因此人们会希望让CPU尽可能多地工作\color{red}希望让 \texttt{CPU} 尽可能多地工作希望让CPU尽可能多地工作 CPU利用率\color{red}\texttt{CPU}利用率CPU利用率&#xff1a;指 CPU “忙碌” 的时间占总时间的比例。 利…

动态规划之01背包问题和完全背包问题

01背包的问题描述&#xff1a;&#xff08;内容参考代码随想录&#xff09;有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。问题示例&#…

Echarts 设置折线图拐点的颜色,边框等样式,hover时改变颜色

第014个点击查看专栏目录上一篇文章我们讲到了如何设置拐点大小,图形类型&#xff0c;旋转角度&#xff0c;缩放同比&#xff0c;位置偏移等&#xff0c;这篇文章介绍如何设置拐点的颜色、边框大小颜色等样式。hover轴线时候&#xff0c;拐点的填充颜色改变文章目录示例效果示例…

Zookeeper安装部署

文章目录Zookeeper安装部署Zookeeper安装部署 将Zookeeper安装包解压缩&#xff0c; [rootlocalhost opt]# ll 总用量 14032 -rw-r--r--. 1 root root 12392394 10月 13 11:44 apache-zookeeper-3.6.0-bin.tar.gz drwxrwxr-x. 6 root root 4096 10月 18 01:44 redis-5.0.4 …

什么时候用MQ、MQ 的作用、延迟消息

本文主要参考沈剑大佬的消息队列系列的四篇博文和博文评论&#xff0c;以及刘海丰老师的《架构设计面试精讲》&#xff0c;文末是完整参考。 1、什么时候用MQ、MQ 的作用 MQ是一个互联网架构中常见的解耦利器。 1.1 MQ 的组成 Producer&#xff1a;消息的生产者&#xff1b; Br…

浅谈函数式编程和命令式编程的区别

一、函数式编程 ------------------------------------------------------------------------------------------------------------------------------------------ 函数式编程用一个英文单词来说的话就是“What?” 它关注结果 定义 把某个功能的具体实现&#xff0c;封装…

一文带你搞懂,Python语言运算符

Python语言支持很多种运算符&#xff0c;我们先用一个表格为大家列出这些运算符&#xff0c;然后选择一些马上就会用到的运算符为大家进行讲解。 说明&#xff1a;上面这个表格实际上是按照运算符的优先级从上到下列出了各种运算符。所谓优先级就是在一个运算的表达式中&#x…

【C语言必经之路——第12节】结构体初阶

一、结构体的声明结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。结构体的声明struct tag{member-list;}variable-list;struct&#xff1a;是结构体定义必不可少的关键字tag&#xff1a;结构名member-list&#xff1a;成员列表(标量、…

2.16、生产者-消费者问题

系统中有一组生产者进程和一组消费者进程&#xff0c;生产者进程每次生产一个产品放入缓冲区&#xff0c;消费者进程每次从缓冲区中取出一个产品并使用。&#xff08;注:这里的“产品”理解为某种数据&#xff09; 生产者、消费者共享一个初始为空、大小为 n 的缓冲区。 只有…

Linux删除软链接

不防大家试试 unlink 命令 首先我们先来创建一个文件 #mkdir test_chk #touch test_chk/test.txt #vim test_chk/test.txt (这一步随便在这个test.txt里写点东东即可) 下面我们来创建test_chk目录 的软链接 #ln-s test_chk test_chk_ln 软链接创建好了&#xff0c;我们来…

应用安全系列之三十八:注入问题的成因以及预防原理

自从有了OWASP TOP的排名依赖&#xff0c;注入问题就一直排名前三&#xff0c;这就说明了注入问题对系统的影响是十分严重的&#xff0c;而且&#xff0c;注入问题一般比较容易被利用。注入问题产生的根本原因就是程序在接受到请求中的参数时&#xff0c;没有经过严格的验证和正…

前端工程师leetcode算法面试必备-二分搜索算法(下)索算法(下)

一、287. 寻找重复数 给定一个包含 n 1 个整数的数组 nums&#xff0c;其数字都在 1 到 n 之间&#xff08;包括 1 和 n&#xff09;&#xff0c;可知至少存在一个重复的整数。假设只有一个重复的整数&#xff0c;找出这个重复的数。 1、HashMap 在没有其它附加条件的情况下&…

如何处理“WLAN没有有效的IP配置”这一问题?

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;暂无 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对我最大的…

svg.js使用教程

在日常web开发过程中&#xff0c;我们会需要显示一些图形化的元素&#xff0c;使用divcss、ps图片是常见的实现方式。 但使用svg来绘制可能更加合适&#xff0c;SVG是可缩放矢量图形&#xff0c;有一些预定义的形状元素&#xff0c;可被开发者使用和操作&#xff1a; 矩形(rec…

图解LeetCode——剑指 Offer 50. 第一个只出现一次的字符

一、题目 在字符串 s 中找出第一个只出现一次的字符。如果没有&#xff0c;返回一个单空格。 s 只包含小写字母。 二、示例 2.1> 示例 1: 【输入】s "abaccdeff" 【输出】b 2.2> 示例 2: 【输入】s "" 【输出】 限制&#xff1a; 0 < s 的…

Swift(5)

目录 集合类型 数组 ​编辑 合集 合集操作 字典 Where 集合类型 Swift提供了三种主要的集合类型&#xff1a;组合&#xff0c;合集&#xff0c;字典。 数组是有序的值的集合。 合集是唯一值的无序集合。 字典是无序的键值对集合。 数组 Swift数组的类型的完整写法是…

总结如何设计一款营销低代码可视化海报平台

背景 我所在的部门负责的是活动业务&#xff0c;每天都有很多的营销活动&#xff0c;随之而来的就是大量的H5活动页面。而这些H5活动已经沉淀出了比较固定的玩法交互&#xff0c;我们开发大多数的工作也只是在复制粘贴这种大量的重复工作。 在基于此背景下我开始了低代码平台…

【手写 Vuex 源码】第十篇 - Vuex 命名空间的实现

一&#xff0c;前言 上一篇&#xff0c;主要介绍了 Vuex 响应式数据和缓存的实现&#xff0c;主要涉及以下几个点&#xff1a; Vuex 的响应式实现原理&#xff1b;响应式核心方法 resetStoreVM&#xff1b;commit 和 dispatch 的处理&#xff1b; 本篇&#xff0c;继续介绍 …