%%% ---------------------------------------------------------------------------- %%% pascaltriangle: Typeset Pascal triangle figure with LaTeX3 %%% Author : Nan Geng %%% Repository: https://github.com/registor/pascaltriangle or https://gitee.com/nwafu_nan/pascaltriangle %%% License : The LaTeX Project Public License 1.3c %%% ---------------------------------------------------------------------------- \NeedsTeXFormat{LaTeX2e} \RequirePackage{expl3} \ProvidesExplPackage{pascaltriangle}{2022-01-28}{v1.0.1} {Typeset Pascal triangle figure with LaTeX3} \RequirePackage{xparse} %% \tl_if_eq:NnTF 与texlive 2020的兼容性设置 \cs_if_exist:NF \tl_if_eq:NnTF { \tl_new:N \l__tblr_backport_b_tl \prg_new_protected_conditional:Npnn \tl_if_eq:Nn #1 #2 { T, F, TF } { \group_begin: \tl_set:Nn \l__tblr_backport_b_tl {#2} \exp_after:wN \group_end: \if_meaning:w #1 \l__tblr_backport_b_tl \prg_return_true: \else: \prg_return_false: \fi: } \prg_generate_conditional_variant:Nnn \tl_if_eq:Nn { c } { TF, T, F } } \cs_if_exist:NF \seq_map_indexed_function:NN { \cs_set_eq:NN \seq_map_indexed_function:NN \seq_indexed_map_function:NN } %% 解决TikZ库无法在Expl3中载入问题 \RequirePackage {amsmath, tikz, etoolbox} \ExplSyntaxOff \patchcmd {\tcb@input@library@in} {% \input\tcbpkgprefix#1\relax% } {% \@pushfilename \input\tcbpkgprefix#1\relax% \@popfilename } {}{} \patchcmd {\pgfutil@InputIfFileExists} {\input #1} {% \@pushfilename \xdef\@currname{#1}% \input #1 % \@popfilename } {}{} \ExplSyntaxOn \usetikzlibrary{shapes.geometric} %% 定义变量 \bool_new:N \l__pascal_num_cell_bool % 是否绘制行列号 \bool_new:N \l__pascal_binom_cell_bool % 是否仅绘制二项式表达式 \tl_new:N \l__pascal_cell_color_tl % 单元格填充颜色 \tl_new:N \l__pascal_shift_col_pt_tl % 列变换坐标 \tl_new:N \l__pascal_shift_row_pt_tl % 行变换坐标 \tl_new:N \l__pascal_font_size_tl % 文件大小 \int_new:N \l__pascal_shape_type_int % 形状(1---等腰,2---直角) \int_new:N \l__pascal_row_idx_int % 行索引(从0计数) \int_new:N \l__pascal_col_idx_int % 列索引(从0计数) \int_new:N \l__pascal_row_top_int % 各列最大行数 \int_new:N \l__pascal_total_idx_int % 索引总数 \int_new:N \l__pascal_fill_row_int % 填充行号(最大值与当前列数有关,从顶向下从0计数) \int_new:N \l__pascal_fill_col_int % 填充列号(从0计数) \dim_new:N \l__pascal_cell_radius_dim % 单元格大小(中心到顶点的半径) %% 选项设计 \keys_define:nn { pascal } { % 是否需要行列编号 withnum .bool_set:N = \l__pascal_num_cell_bool, withnum .default:n = true, withnum .initial:n = false, % 是否仅绘制二项式表达式 binom .bool_set:N = \l__pascal_binom_cell_bool, binom .default:n = true, binom .initial:n = false, % 形状选择(iso---等腰三角形, rt---直角三角形) shape .choice:, shape .value_required:n = true, shape .choices:nn = { iso, rt } { \int_set_eq:NN \l__pascal_shape_type_int \l_keys_choice_int }, shape .initial:n = iso, % 单元格半径(中心到顶点) radius .dim_set:N = \l__pascal_cell_radius_dim, radius .initial:n = 0.5cm, % 文字大小 fontsize .tl_set:N = \l__pascal_font_size_tl, fontsize .initial:n = \small, % 要填充单元格行数(按列从顶向下,从0开始计数) fillr .int_set:N = \l__pascal_fill_row_int, fillr .initial:n = 1 , % 要填充单元格列数(从左向右,从0开始计数) fillc .int_set:N = \l__pascal_fill_col_int, fillc .initial:n = 1 , fillrc .meta:n = { fillr = #1 , fillc = #1 }, unknown .code:n = { \__pascal_error:n { unknown-option } } } \msg_new:nnn { pascal } { unknown-option } { package~ option~ "\l_keys_key_tl"~ is~ unknown. } %% 参数设置用户接口 \NewDocumentCommand \pascalset { m } { \keys_set:nn { pascal } {#1} } %% 利用LaTeX3的fact阶乘计算函数计算组合数 \cs_set:Npn \__pascal_binomcoeff:nn #1#2 { \fp_eval:n { fact(#1) / (fact(#2) * fact(#1 - #2)) } } %% 组合数计算用户接口 \NewDocumentCommand{\binomc}{m m} { \__pascal_binomcoeff:nn{#1}{#2} } %% 绘制Pascal三角形 %% 注意":"无法使用造成极坐标不能用的问题,以下网站给出了解决方案 %% https://tex.stackexchange.com/questions/501333/how-to-use-colon-as-part-of-tikz-syntax-while-in-explsyntaxon-environment %% 将":"用\c_colon_str代替 %% 根据形状类型计算列变换坐标(极坐标) \cs_set:Npn \__pascal_shift_col_pt:n #1 { \int_case:nn { \l__pascal_shape_type_int } { {1}{ \tl_set:Nn \l__pascal_shift_col_pt_tl { -60 \c_colon_str \fp_eval:n {sqrt(3) * \l__pascal_cell_radius_dim * #1}pt } } {2}{ \tl_set:Nn \l__pascal_shift_col_pt_tl { -45 \c_colon_str \fp_eval:n {2 * \l__pascal_cell_radius_dim * #1}pt } } } } %% 根据形状类型计算行变换坐标(极坐标) \cs_set:Npn \__pascal_shift_row_pt:n #1 { \int_case:nn { \l__pascal_shape_type_int } { {1}{ \tl_set:Nn \l__pascal_shift_row_pt_tl { -120 \c_colon_str \fp_eval:n {sqrt(3) * \l__pascal_cell_radius_dim * #1}pt } } {2}{ \tl_set:Nn \l__pascal_shift_row_pt_tl { -90 \c_colon_str \fp_eval:n {sqrt(2) * \l__pascal_cell_radius_dim * #1}pt } } } } %% 正六边形闭合路径代码 \cs_set:Npn \__pascal_hex_cell: { (30 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (90 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (150 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (210 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (270 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (330 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- cycle (90 \c_colon_str 0) } %% 正方形闭合路径代码 \cs_set:Npn \__pascal_square_cell: { (45 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (134 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (225 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- (-45 \c_colon_str \fp_eval:n{\l__pascal_cell_radius_dim}pt) -- cycle (90 \c_colon_str 0) } %% 绘制行列编号标记单元格 \cs_set:Npn \__pascal_num_draw:nnn #1#2#3 { \int_case:nn { \l__pascal_shape_type_int } { {1}{% 正六边形 \int_compare:nNnT { #1 } = {0}% 最左列 { \int_compare:nNnT { #2 } = { #3 - 1 }% 最底行 { \begin{scope}[shift={(-150\c_colon_str {\fp_eval:n{3*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_hex_cell: node[rotate=-45] {$m,n$}; \end{scope} } } \int_compare:nNnT { #1 } = { 0 }% 最左列 { \begin{scope}[shift={(-180\c_colon_str {\fp_eval:n{sqrt(3)*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_hex_cell: node{$(\int_eval:n {#2})$}; \end{scope} } \int_compare:nNnT { #2 } = { #3 - 1 }% 最底行 { \begin{scope}[shift={(-120\c_colon_str {\fp_eval:n{sqrt(3)*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_hex_cell: node {$(\int_eval:n {#1})$}; \end{scope} } } {2}{% 正方形 \int_compare:nNnT { #1 } = {0}% 最左列 { \int_compare:nNnT { #2 } = { #3 - 1 }% 最底行 { \begin{scope}[shift={(-135\c_colon_str {\fp_eval:n{2*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_square_cell: node[scale=0.8,rotate=-45] {$m,n$}; \end{scope} } } \int_compare:nNnT { #1 } = { 0 }% 最左列 { \begin{scope}[shift={(-180\c_colon_str {\fp_eval:n{sqrt(2)*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_square_cell: node{$(\int_eval:n {#2})$}; \end{scope} } \int_compare:nNnT { #2 } = { #3 - 1 }% 最底行 { \begin{scope}[shift={(-90\c_colon_str {\fp_eval:n{sqrt(2)*\l__pascal_cell_radius_dim}pt})}] \draw[dashed, top~color=gray!10,bottom~color=gray!20] \__pascal_square_cell: node {$(\int_eval:n {#1})$}; \end{scope} } } } } %% 绘制三角形单元格 \cs_set:Npn \__pascal_cell_draw:nn #1#2 { \int_case:nn { \l__pascal_shape_type_int } { {1}{% 正六边形 \draw[top~color=\l__pascal_cell_color_tl!20,bottom~color=\l__pascal_cell_color_tl!60] \__pascal_hex_cell: \bool_if:NTF \l__pascal_binom_cell_bool { node {$\binom{\int_eval:n {#1}}{\int_eval:n {#2}}$}; } { node {$\__pascal_binomcoeff:nn {#1}{#2}$}; } } {2}{% 正方形 \draw[top~color=\l__pascal_cell_color_tl!20,bottom~color=\l__pascal_cell_color_tl!60] \__pascal_square_cell: \bool_if:NTF \l__pascal_binom_cell_bool { node {$\binom{\int_eval:n {#1}}{\int_eval:n {#2}}$}; } { node {$\__pascal_binomcoeff:nn {#1}{#2}$}; } } } } %% 绘制Pascal三角形 % 等腰三角形的绘制原理参考了 https://texample.net/tikz/examples/pascal-triangle/ % Author: M.H. Ahmadi \cs_set:Npn \__pascal_triangle_draw:n #1 { % 设置正常单元格为灰色 \tl_set:Nn \l__pascal_cell_color_tl {gray} % 记录绘制列数 \int_set:Nn \l_tmpa_int {#1} % 使用TikZ绘制 \begin{tikzpicture}[font=\l__pascal_font_size_tl] % 列循环 \int_step_inline:nn {\l_tmpa_int} { % 调整为从0计数,以便于后续计算 \int_set:Nn \l__pascal_col_idx_int { ##1 - 1 } % 计算列变换坐标(极坐标) \__pascal_shift_col_pt:n {\l__pascal_col_idx_int} % 用scope环境变换各列到指定位置 \begin{scope}[shift={(\l__pascal_shift_col_pt_tl)}] % 计算每列最多行数 \int_set:Nn \l__pascal_row_top_int {\l_tmpa_int - ##1 + 1} % 行循环 \int_step_inline:nn { \l__pascal_row_top_int } { % 调整为从0计数,以便于后续计算 \int_set:Nn \l__pascal_row_idx_int { ####1 - 1 } % 计算组合数计算中的行数 \int_zero:N \l__pascal_total_idx_int \int_add:Nn \l__pascal_total_idx_int { \l__pascal_row_idx_int } \int_add:Nn \l__pascal_total_idx_int { \l__pascal_col_idx_int } % 为指定行设置绘制颜色 \int_case:nn { \l__pascal_col_idx_int } { {0}{ \tl_set:Nn \l__pascal_cell_color_tl { pink } } {1}{ \tl_set:Nn \l__pascal_cell_color_tl { yellow } } {2}{ \tl_set:Nn \l__pascal_cell_color_tl { blue } } {3}{ \tl_set:Nn \l__pascal_cell_color_tl { green } } {\l__pascal_fill_col_int - 1}{ \int_compare:nNnTF{\l__pascal_row_idx_int} < { \l__pascal_fill_row_int + 1 } {\tl_set:Nn \l__pascal_cell_color_tl { purple }} {\tl_set:Nn \l__pascal_cell_color_tl { gray }} } {\l__pascal_fill_col_int}{ \int_compare:nNnTF{\l__pascal_row_idx_int} = { \l__pascal_fill_row_int } {\tl_set:Nn \l__pascal_cell_color_tl { purple }} {\tl_set:Nn \l__pascal_cell_color_tl { gray }} } } % 计算行变换坐标(极坐标) \__pascal_shift_row_pt:n {\l__pascal_row_idx_int} % 用scope环境变换各行到指定位置 \begin{scope}[shift={(\l__pascal_shift_row_pt_tl)}] % 是否绘制行列编号 \bool_if:NT \l__pascal_num_cell_bool { \__pascal_num_draw:nnn { \l__pascal_col_idx_int } { \l__pascal_row_idx_int } { \l__pascal_row_top_int } } % 绘制一个单元格 \__pascal_cell_draw:nn{\l__pascal_total_idx_int}{\l__pascal_col_idx_int} \end{scope} } \end{scope} } \end{tikzpicture} } %% 绘制Pascal三角形用户接口 \NewDocumentCommand{\pascal}{O{} m} { \group_begin: \keys_set:nn { pascal } {#1} \__pascal_triangle_draw:n{#2} \group_end: } \endinput