% Author : C. Pierquet % licence : Released under the LaTeX Project Public License v1.3c or later, see http://www.latex-project.org/lppl.txt \NeedsTeXFormat{LaTeX2e} \ProvidesExplPackage{commalists-tools-l3}{2026-02-20}{0.20a}{Basic operations for numeral comma separated lists} %------History % 0.20a New commands (sublist / transform / slice / merge / intersection / ...) % 0.1.9 Improvements with latex3 (tks to ankaa3908) % 0.1.8 Get index % 0.1.7 Improvements with latex3 % 0.1.6 Initial version (prefixed macros due to legacy package) %------Variables \clist_new:N \l__commalists_var_clist \clist_new:N \l__commalists_varb_clist \fp_new:N \l__commalists_tmpa_fp %------Show list \NewDocumentCommand\ctshowlist{ O { , } m } % #1 = sep % #2 = list { \clist_set:Ne \l__commalists_var_clist { #2 } \clist_use:Nn \l__commalists_var_clist { #1 } } %------Sort list, reverse list \NewDocumentCommand\ctsortasclist{ s m O { \mysortedlist } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \mysortedlist) { \clist_set:Ne \l__commalists_var_clist {#2} \clist_sort:Nn \l__commalists_var_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } \IfBooleanTF { #1 } %if star := just printing // if not, storing { \clist_use:Nn \l__commalists_var_clist { , } } { \tl_gset:Ne \g_tmpa_tl { \l__commalists_var_clist } \tl_gset:NV #3 \g_tmpa_tl } } \NewDocumentCommand\ctsortdeslist{ s m O { \mysortedlist } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \mysortedlist) { \clist_set:Ne \l__commalists_var_clist { #2 } \clist_sort:Nn \l__commalists_var_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } \IfBooleanTF { #1 } %if star := just printing // if not, storing { \clist_use:Nn \l__commalists_var_clist { , } } { \tl_gset:Ne \g_tmpa_tl { \l__commalists_var_clist } \tl_gset:NV #3 \g_tmpa_tl } } \NewDocumentCommand\ctreverselist{ s m O { \reverselist } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \reverselist) { \clist_set:Ne \l__commalists_var_clist { #2 } \IfBooleanTF { #1 } %if star := just printing // if not, storing { \clist_reverse:N \l__commalists_var_clist \clist_use:Nn \l__commalists_var_clist { , } } { \clist_set_eq:NN #3 \l__commalists_var_clist \clist_greverse:N #3 } } %------Test in list \NewDocumentCommand\ctboolvalinlist{ m m O { \resisinlist } } % #1 = element to test % #2 = list % #3 = output macro (non-starred, default \resisinlist) { \clist_set:Ne \l__commalists_var_clist {#2} \tl_gset:Nn #3 { 0 } \clist_map_inline:Nn \l__commalists_var_clist { \tl_if_eq:neT { ##1 } { #1 } { \tl_gset:Nn #3 { 1 } \clist_map_break: } } } \NewDocumentCommand\cttestifvalinlist{ m m m m } % #1 = element to test % #2 = list % #3 = true branch % #4 = false branch { \clist_set:Ne \l__commalists_var_clist { #2 } \bool_set_false:N \l_tmpa_bool \clist_map_inline:Nn \l__commalists_var_clist { \tl_if_eq:neT { ##1 } { #1 } { \bool_set_true:N \l_tmpa_bool \clist_map_break: } } \bool_if:NTF \l_tmpa_bool { #3 } { #4 } } %------Add, remove \NewDocumentCommand\ctaddvalinlist{ s m m } % #1 = star (just printing) % #2 = value to add % #3 = list { \IfBooleanTF { #1 } { #3, #2 } { \tl_gset:Ne #3 { \tl_use:N #3 , #2 } } } \NewDocumentCommand\ctremovevalinlist{ s m m O { \mytmplist } } % #1 = star (just printing) % #2 = value to remove % #3 = list % #4 = output macro (non-starred, default \mytmplist) { \clist_set:Ne \l__commalists_var_clist { #3 } \clist_clear_new:N \l_tmpb_clist \clist_map_inline:Nn \l__commalists_var_clist { \tl_if_eq:neF { ##1 } { #2 } { \clist_put_right:Nn \l_tmpb_clist { ##1 } } } \IfBooleanTF { #1 } { \clist_use:Nn \l_tmpb_clist { , } } { \tl_gset:Ne \g_tmpa_tl { \clist_use:Nn \l_tmpb_clist { , } } \tl_gset:NV #4 \g_tmpa_tl } } %------Count \NewDocumentCommand\ctcountvalinlist{ s m m O { \rescount } } % #1 = star (just printing) % #2 = value to count % #3 = list % #4 = output macro (non-starred, default \rescount) { \clist_set:Ne \l__commalists_var_clist { #3 } \int_zero:N \l_tmpa_int \clist_map_inline:Nn \l__commalists_var_clist { \tl_if_eq:neT { ##1 } { #2 } { \int_incr:N \l_tmpa_int } } \IfBooleanTF { #1 } { \int_use:N \l_tmpa_int } { \tl_gset:NV #4 \l_tmpa_int } } %------Calculus \NewDocumentCommand\ctminoflist{ s m O { \resmin } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \resmin) { \IfBooleanTF { #1 } { \fp_eval:n { min(#2) } } { \tl_gset:Ne #3 { \fp_eval:n { min(#2) } } } } \NewDocumentCommand\ctmaxoflist{ s m O { \resmax } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \resmax) { \IfBooleanTF { #1 } { \fp_eval:n { max(#2) } } { \tl_gset:Ne #3 { \fp_eval:n { max(#2) } } } } \NewDocumentCommand\ctsumoflist{ s m O { \ressum } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \ressum) { \clist_set:Ne \l__commalists_var_clist { #2 } \fp_set:Nn \l__commalists_tmpa_fp { 0 } \clist_map_inline:Nn \l__commalists_var_clist { \fp_set:Nn \l__commalists_tmpa_fp { \fp_eval:n { \l__commalists_tmpa_fp + (##1) } } } \IfBooleanTF { #1 } { \fp_use:N \l__commalists_tmpa_fp } { \tl_gset:Ne #3 { \fp_use:N \l__commalists_tmpa_fp } } } \NewDocumentCommand\ctprodoflist{ s m O { \resprod } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \resprod) { \clist_set:Ne \l__commalists_var_clist { #2 } \fp_set:Nn \l__commalists_tmpa_fp { 1 } \clist_map_inline:Nn \l__commalists_var_clist { \fp_set:Nn \l__commalists_tmpa_fp { \fp_eval:n { \l__commalists_tmpa_fp * (##1) } } } \IfBooleanTF { #1 } { \fp_use:N \l__commalists_tmpa_fp } { \tl_gset:Ne #3 { \fp_use:N \l__commalists_tmpa_fp } } } \NewDocumentCommand\ctmeanoflist{ s m O { \resmean } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \resmean) { \clist_set:Ne \l__commalists_var_clist { #2 } \fp_set:Nn \l__commalists_tmpa_fp { 0 } \clist_map_inline:Nn \l__commalists_var_clist { \fp_set:Nn \l__commalists_tmpa_fp { \fp_eval:n { \l__commalists_tmpa_fp + (##1) } } } \fp_set:Nn \l__commalists_tmpa_fp { \fp_eval:n { ( \l__commalists_tmpa_fp ) / ( \clist_count:N \l__commalists_var_clist ) } } \IfBooleanTF { #1 } { \fp_use:N \l__commalists_tmpa_fp } { \tl_gset:Ne #3 { \fp_use:N \l__commalists_tmpa_fp } } } \NewDocumentCommand\ctlenoflist{ s m O { \resmylen } } % #1 = star (just printing) % #2 = list % #3 = output macro (non-starred, default \myreslen) { \clist_set:Ne \l__commalists_var_clist { #2 } \IfBooleanTF { #1 } { \clist_count:N \l__commalists_var_clist } { \tl_gset:Ne #3 { \clist_count:N \l__commalists_var_clist } } } %------Get, index \NewDocumentCommand\ctgetvaluefromlist{ s m m O { \resmyelt } } % #1 = star (just printing) % #2 = list % #3 = index % #3 = output macro (non-starred, default \resmyelt) { \clist_set:Ne \l__commalists_var_clist { #2 } \IfBooleanTF { #1 } { \clist_item:Nn \l__commalists_var_clist { #3 } } { \tl_gset:Ne #4 { \clist_item:Nn \l__commalists_var_clist { #3 } } } } \NewDocumentCommand\ctgetindexfromlist{ s m m O { \resmyindex } } % #1 = star (just printing) % #2 = list % #3 = element % #3 = output macro (non-starred, default \resmyindex) { \IfBooleanTF { #1 } { \ct_get_index:nn { #3 } { #2 } } { \ct_get_index:nnN { #3 } { #2 } #4 } } \cs_new:Npn \ct_get_index:nn #1 #2 { \int_zero:N \l_tmpa_int \bool_set_false:N \l_tmpa_bool \tl_set:Ne \l_tmpb_tl { #2 } \exp_args:No \clist_map_inline:nn { #1 } { \int_incr:N \l_tmpa_int \str_if_eq:nVT { ##1 } \l_tmpb_tl { \bool_set_true:N \l_tmpa_bool \clist_map_break: } } \bool_if:NTF \l_tmpa_bool { \int_use:N \l_tmpa_int } { 0 } } \cs_new:Npn \ct_get_index:nnN #1 #2 #3 { \int_zero:N \l_tmpa_int \bool_set_false:N \l_tmpa_bool \tl_set:Ne \l_tmpb_tl { #2 } \exp_args:No \clist_map_inline:nn { #1 } { \int_incr:N \l_tmpa_int \str_if_eq:nVT { ##1 } \l_tmpb_tl { \bool_set_true:N \l_tmpa_bool \clist_map_break: } } \bool_if:NTF \l_tmpa_bool { \tl_gset:Ne #3 { \int_use:N \l_tmpa_int } } { \tl_gset:Nn #3 { 0 } } } %------Sub-list \clist_new:N \l__commalists_sub_clist \int_new:N \l__commalists_begin_int \int_new:N \l__commalists_end_int \int_new:N \l__commalists_idx_int \NewDocumentCommand\ctsublist{ s m m m O { \mysublist } } % #1 = star (display only) % #2 = list % #3 = begin index (* = start from first) % #4 = end index (* = go to last) % #5 = output macro (non-starred only, default \mysublist) { \clist_set:Ne \l__commalists_var_clist { #2 } \clist_clear:N \l__commalists_sub_clist % --- resolve begin index \tl_if_eq:nnTF { #3 } { * } { \int_set:Nn \l__commalists_begin_int { 1 } } { \int_set:Nn \l__commalists_begin_int { #3 } } % --- resolve end index \tl_if_eq:nnTF { #4 } { * } { \int_set:Nn \l__commalists_end_int { \clist_count:N \l__commalists_var_clist } } { \int_set:Nn \l__commalists_end_int { #4 } } % --- iterate and collect items in [begin, end] \int_set:Nn \l__commalists_idx_int { 0 } \clist_map_inline:Nn \l__commalists_var_clist { \int_incr:N \l__commalists_idx_int \int_compare:nTF { \l__commalists_idx_int >= \l__commalists_begin_int } { \int_compare:nTF { \l__commalists_idx_int <= \l__commalists_end_int } { \clist_put_right:Nn \l__commalists_sub_clist { ##1 } } { \clist_map_break: } % early exit : idx > end, stop } { } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_sub_clist { , } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_sub_clist { , } } } } %------Slice-list \clist_new:N \l__commalists_slice_clist \int_new:N \l__commalists_slice_x_int \int_new:N \l__commalists_slice_idx_int \int_new:N \l__commalists_slice_len_int \tl_new:N \l__commalists_slice_formula_tl \NewDocumentCommand\ctslicelist{ s m m O { \myslicelist } } % #1 = star (display only) % #2 = list % #3 = formula in x (e.g. 2*x, 3*x+1, x^2) % #4 = output macro (non-starred only, default \myslicelist) { \clist_set:Ne \l__commalists_var_clist { #2 } \clist_clear:N \l__commalists_slice_clist \int_set:Nn \l__commalists_slice_len_int { \clist_count:N \l__commalists_var_clist } \int_set:Nn \l__commalists_slice_x_int { 1 } % --- iterate x = 1..len, evaluate formula, collect item if index in range \clist_map_inline:Nn \l__commalists_var_clist { % substitute x by current value in formula, then evaluate \tl_set:Nn \l__commalists_slice_formula_tl { #3 } \tl_replace_all:Nne \l__commalists_slice_formula_tl { x } { \int_use:N \l__commalists_slice_x_int } \int_set:Nn \l__commalists_slice_idx_int { \fp_eval:n { \l__commalists_slice_formula_tl } } % stop as soon as the computed index goes out of range \int_compare:nTF { \int_use:N \l__commalists_slice_idx_int >= 1 } { \int_compare:nTF { \int_use:N \l__commalists_slice_idx_int <= \int_use:N \l__commalists_slice_len_int } { \clist_put_right:Ne \l__commalists_slice_clist { \clist_item:Nn \l__commalists_var_clist { \l__commalists_slice_idx_int } } \int_incr:N \l__commalists_slice_x_int } { \clist_map_break: } } { \clist_map_break: } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_slice_clist { , } } { \tl_gset:Ne #4 { \clist_use:Nn \l__commalists_slice_clist { , } } } } %------Transform-list \clist_new:N \l__commalists_transform_clist \tl_new:N \l__commalists_transform_formula_tl \NewDocumentCommand\cttransformlist{ s o m m O { \mytransformlist } } % #1 = star (display only) % #2 = round % #3 = list % #4 = formula in x (e.g. 2*x+1, x^2, sqrt(x)) % #5 = output macro (non-starred only, default \mytransformlist) { \clist_set:Ne \l__commalists_var_clist { #3 } \clist_clear:N \l__commalists_transform_clist % --- iterate over each element, apply formula \clist_map_inline:Nn \l__commalists_var_clist { % --- substitute x by current element value in formula \tl_set:Nn \l__commalists_transform_formula_tl { #4 } \tl_replace_all:Nnn \l__commalists_transform_formula_tl { x } { ##1 } % --- evaluate and collect result \IfNoValueTF { #2 } { \clist_put_right:Ne \l__commalists_transform_clist { \fp_eval:n { \l__commalists_transform_formula_tl } } } { \clist_put_right:Ne \l__commalists_transform_clist { \fp_eval:n { round( \l__commalists_transform_formula_tl , #2 ) } } } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_transform_clist { , } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_transform_clist { , } } } } %------Unique list \clist_new:N \l__commalists_uniq_clist \bool_new:N \l__commalists_uniq_asc_bool \bool_new:N \l__commalists_uniq_des_bool \keys_define:nn { commalists / uniq } { asc .bool_set:N = \l__commalists_uniq_asc_bool, asc .default:n = true, des .bool_set:N = \l__commalists_uniq_des_bool, des .default:n = true, } \NewDocumentCommand\ctuniqlist{ s O { } m O { \myuniqlist } } % #1 = star (display only) % #2 = options (asc, des) % #3 = list % #4 = output macro (non-starred only, default \myuniqlist) { % reset booleans \bool_set_false:N \l__commalists_uniq_asc_bool \bool_set_false:N \l__commalists_uniq_des_bool % parse keys \keys_set:nn { commalists / uniq } { #2 } \clist_set:Ne \l__commalists_var_clist { #3 } \clist_clear:N \l__commalists_uniq_clist % --- iterate and collect unique elements \clist_map_inline:Nn \l__commalists_var_clist { \clist_if_in:NnF \l__commalists_uniq_clist { ##1 } { \clist_put_right:Nn \l__commalists_uniq_clist { ##1 } } } % --- sort if requested \bool_if:NT \l__commalists_uniq_asc_bool { \clist_sort:Nn \l__commalists_uniq_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } \bool_if:NT \l__commalists_uniq_des_bool { \clist_sort:Nn \l__commalists_uniq_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_uniq_clist { , } } { \tl_gset:Ne #4 { \clist_use:Nn \l__commalists_uniq_clist { , } } } } %------Intersection \clist_new:N \l__commalists_intersect_clist \bool_new:N \l__commalists_intersect_asc_bool \bool_new:N \l__commalists_intersect_des_bool \keys_define:nn { commalists / intersect } { asc .bool_set:N = \l__commalists_intersect_asc_bool, asc .default:n = true, des .bool_set:N = \l__commalists_intersect_des_bool, des .default:n = true, } \NewDocumentCommand\ctintersectlists{ s O { } m m O { \myintersectlist } } % #1 = star (just printing) % #2 = options (asc, des) % #3 = list 1 % #4 = list 2 % #5 = output macro (non-starred, default \myintersectlist) { % set lists \clist_set:Ne \l__commalists_var_clist { #3 } \clist_set:Ne \l__commalists_varb_clist { #4 } % reset booleans \bool_set_false:N \l__commalists_intersect_asc_bool \bool_set_false:N \l__commalists_intersect_des_bool % parse keys \keys_set:nn { commalists / intersect } { #2 } % clear res list \clist_clear:N \l__commalists_intersect_clist % --- iterate and collect common elements \clist_map_inline:Nn \l__commalists_var_clist { \clist_if_in:NnT \l__commalists_varb_clist { ##1 } { % if ok, add \clist_if_in:NnF \l__commalists_intersect_clist { ##1 } { \clist_put_right:Nn \l__commalists_intersect_clist { ##1 } } } } % --- sort if asked \bool_if:NT \l__commalists_intersect_asc_bool { \clist_sort:Nn \l__commalists_intersect_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } \bool_if:NT \l__commalists_intersect_des_bool { \clist_sort:Nn \l__commalists_intersect_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % --- output \IfBooleanTF { #1 } { \clist_if_empty:NTF \l__commalists_intersect_clist { } { \clist_use:Nn \l__commalists_intersect_clist { , } } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_intersect_clist { , } } } } %------Union \clist_new:N \l__commalists_merge_clist \bool_new:N \l__commalists_merge_asc_bool \bool_new:N \l__commalists_merge_des_bool \keys_define:nn { commalists / merge } { asc .bool_set:N = \l__commalists_merge_asc_bool, asc .default:n = true, des .bool_set:N = \l__commalists_merge_des_bool, des .default:n = true, } \NewDocumentCommand\ctmergelists{ s O { } m m O { \mymergelist } } % #1 = star (just printing) % #2 = options (asc, des) % #3 = list 1 % #4 = list 2 % #5 = output macro (non-starred, default \mymergelist) { % reinit of booleans \bool_set_false:N \l__commalists_merge_asc_bool \bool_set_false:N \l__commalists_merge_des_bool % keys reading \keys_set:nn { commalists / merge } { #2 } % set lists \clist_set:Ne \l__commalists_var_clist { #3 } \clist_set:Ne \l__commalists_varb_clist { #4 } % init merge list \clist_clear:N \l__commalists_merge_clist % add element list1, w/o doublons \clist_map_inline:Nn \l__commalists_var_clist { \clist_if_in:NnF \l__commalists_merge_clist { ##1 } { \clist_put_right:Nn \l__commalists_merge_clist { ##1 } } } % add element list2, w/o doublons \clist_map_inline:Nn \l__commalists_varb_clist { \clist_if_in:NnF \l__commalists_merge_clist { ##1 } { \clist_put_right:Nn \l__commalists_merge_clist { ##1 } } } % sort if asked \bool_if:NT \l__commalists_merge_asc_bool { \clist_sort:Nn \l__commalists_merge_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } \bool_if:NT \l__commalists_merge_des_bool { \clist_sort:Nn \l__commalists_merge_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % --- Output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_merge_clist { , } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_merge_clist { , } } } } %------Difference \clist_new:N \l__commalists_diff_clist \bool_new:N \l__commalists_diff_asc_bool \bool_new:N \l__commalists_diff_des_bool \keys_define:nn { commalists / diff } { asc .bool_set:N = \l__commalists_diff_asc_bool, asc .default:n = true, des .bool_set:N = \l__commalists_diff_des_bool, des .default:n = true, } \NewDocumentCommand\ctdifflist{ s O { } m m O { \mydifflist } } % #1 = star (display only) % #2 = options (asc, des) % #3 = list A % #4 = list B % #5 = output macro (non-starred, default \mydifflist) { % set lists \clist_set:Ne \l__commalists_var_clist { #3 } \clist_set:Ne \l__commalists_varb_clist { #4 } % reset booleans \bool_set_false:N \l__commalists_diff_asc_bool \bool_set_false:N \l__commalists_diff_des_bool % parse keys \keys_set:nn { commalists / diff } { #2 } % clear result list \clist_clear:N \l__commalists_diff_clist % --- iterate A, keep elements absent from B \clist_map_inline:Nn \l__commalists_var_clist { \clist_if_in:NnF \l__commalists_varb_clist { ##1 } { % avoid duplicates in result \clist_if_in:NnF \l__commalists_diff_clist { ##1 } { \clist_put_right:Nn \l__commalists_diff_clist { ##1 } } } } % --- sort if asked \bool_if:NT \l__commalists_diff_asc_bool { \clist_sort:Nn \l__commalists_diff_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } \bool_if:NT \l__commalists_diff_des_bool { \clist_sort:Nn \l__commalists_diff_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_diff_clist { , } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_diff_clist { , } } } } %------Symmetric difference \clist_new:N \l__commalists_symdiff_clist \bool_new:N \l__commalists_symdiff_asc_bool \bool_new:N \l__commalists_symdiff_des_bool \keys_define:nn { commalists / symdiff } { asc .bool_set:N = \l__commalists_symdiff_asc_bool, asc .default:n = true, des .bool_set:N = \l__commalists_symdiff_des_bool, des .default:n = true, } \NewDocumentCommand\ctsymdifflist{ s O { } m m O { \mysymdifflist } } % #1 = star (display only) % #2 = options (asc, des) % #3 = list A % #4 = list B % #5 = output macro (non-starred, default \mysymdifflist) { % set lists \clist_set:Ne \l__commalists_var_clist { #3 } \clist_set:Ne \l__commalists_varb_clist { #4 } % reset booleans \bool_set_false:N \l__commalists_symdiff_asc_bool \bool_set_false:N \l__commalists_symdiff_des_bool % parse keys \keys_set:nn { commalists / symdiff } { #2 } % clear result list \clist_clear:N \l__commalists_symdiff_clist % --- elements in A absent from B \clist_map_inline:Nn \l__commalists_var_clist { \clist_if_in:NnF \l__commalists_varb_clist { ##1 } { \clist_if_in:NnF \l__commalists_symdiff_clist { ##1 } { \clist_put_right:Nn \l__commalists_symdiff_clist { ##1 } } } } % --- elements in B absent from A \clist_map_inline:Nn \l__commalists_varb_clist { \clist_if_in:NnF \l__commalists_var_clist { ##1 } { \clist_if_in:NnF \l__commalists_symdiff_clist { ##1 } { \clist_put_right:Nn \l__commalists_symdiff_clist { ##1 } } } } % --- sort if asked \bool_if:NT \l__commalists_symdiff_asc_bool { \clist_sort:Nn \l__commalists_symdiff_clist { \fp_compare:nNnTF { ##1 } > { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } \bool_if:NT \l__commalists_symdiff_des_bool { \clist_sort:Nn \l__commalists_symdiff_clist { \fp_compare:nNnTF { ##1 } < { ##2 } { \sort_return_swapped: } { \sort_return_same: } } } % --- output \IfBooleanTF { #1 } { \clist_use:Nn \l__commalists_symdiff_clist { , } } { \tl_gset:Ne #5 { \clist_use:Nn \l__commalists_symdiff_clist { , } } } } \endinput