It's a bit tricky if you don't have a fixed number of items to process, but doable of course. The implementation of \tnscalc_splittab:nnn below will split the sequence twice, at the first then at the second delimiter, then restructure it as:
\__tnscalc_item:nw {a}{b}{c}\q_recursion_tail
\__tnscalc_item:nw {1}{2}{3}\q_recursion_tail
\__tnscalc_item:nw {\q_nil }\q_stop \q_recursion_stop
Then every \__tnscalc_item:nw will collect the item after it, except for the end marker \q_nil, and pass the collected items to \__tnscalc_do:n. After a few expansion steps you will have:
\__tnscalc_do:n {{a}{1}}
\__tnscalc_item:nw {b}{c}\q_recursion_tail
\__tnscalc_item:nw {2}{3}\q_recursion_tail
\__tnscalc_item:nw {\q_nil }\q_stop \q_recursion_stop
with the first batch of items removed. After a few more, you'll have:
\__tnscalc_do:n {{a}{1}} % already executed
\__tnscalc_do:n {{b}{2}}
\__tnscalc_item:nw {c}\q_recursion_tail
\__tnscalc_item:nw {3}\q_recursion_tail
\__tnscalc_item:nw {\q_nil }\q_stop \q_recursion_stop
then the last batch will be used and the \q_recursion_tail markers will signal the end of the list and you'll have:
\__tnscalc_do:n {{a}{1}} % already executed
\__tnscalc_do:n {{b}{2}} % already executed
\__tnscalc_do:n {{c}{3}} % already executed
Since the number of items is variable, each column is passed to \__tnscalc_do:n with all items braced, so in \__tnscalc_do:n you have to figure out what is the number of items (\tl_count:n {#1} might help) and process them accordingly. You can also map to every item in the column using \tl_map_inline:nn {#1} { <code with ##1> }.
The mapping makes sure that \__tnscalc_do:n receives always the same number of items. Any incomplete column is ignored.
To get the output from your code you can define \__tnscalc_do:n as:
\cs_new_protected:Npn \__tnscalc_do:n #1
{
(\int_use:N \l__tnscalc_numcol_int :: \use_i:nn #1 ; \use_ii:nn #1 )
\int_decr:N \l__tnscalc_numcol_int
}
(note that the usage of \use_i:nn and \use_ii:nn assumes that there are only two \\-separated items; if the number is different you can't use those two anymore!)

Changing the definition of \__tnscalc_do:n to:
\cs_new_protected:Npn \__tnscalc_do:n #1
{
Column~\int_use:N \l__tnscalc_numcol_int :~
\tl_map_inline:nn {#1}
{ (##1) }
\par
\int_decr:N \l__tnscalc_numcol_int
}
produces, for the same input:

You can make the definition of \__tnscalc_do:n an argument to \splittab, but I'll leave that as an exercise.
Here's the code:
\documentclass{article}
\RequirePackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \splittab { m m +m }
{ \tnscalc_splittab:nnn {#1} {#2} {#3} }
% Variables
\int_new:N \l__tnscalc_numcol_int
\seq_new:N \l__tnscalc_tmp_seq
\tl_new:N \l__tnscalc_items_tl
% Main function
\cs_new_protected:Nn \tnscalc_splittab:nnn
{
\group_begin:
\tl_clear:N \l__tnscalc_items_tl
\int_set_eq:NN \l__tnscalc_numcol_int \c_max_int
\seq_set_split:Nnn \l__tnscalc_tmp_seq {#1} {#3}
\seq_map_inline:Nn \l__tnscalc_tmp_seq
{
\seq_set_split:Nnn \l__tnscalc_tmp_seq {#2} {##1}
__tnscalc_seq_set_map:NNn \l__tnscalc_tmp_seq
\l__tnscalc_tmp_seq { {####1} }
\int_set:Nn \l__tnscalc_numcol_int
{
\int_min:nn { \l__tnscalc_numcol_int }
{ \seq_count:N \l__tnscalc_tmp_seq }
}
\tl_put_right:Nx \l__tnscalc_items_tl
{
\exp_not:N __tnscalc_item:nw
\seq_use:Nn \l__tnscalc_tmp_seq { }
\exp_not:N \q_recursion_tail
}
}
\tl_put_right:Nn \l__tnscalc_items_tl
{ __tnscalc_item:nw { \q_nil } \q_stop }
\tl_use:N \l__tnscalc_items_tl \q_recursion_stop
\group_end:
}
\cs_new_protected:Npn __tnscalc_item:nw
{ __tnscalc_iterate_collect:nnw { } }
\cs_new_protected:Npn __tnscalc_iterate_collect:nnw #1 #2
{
\quark_if_recursion_tail_stop:n {#2}
\quark_if_nil:nTF {#2}
{ __tnscalc_iterate_collect_end:nw {#1} }
{ __tnscalc_iterate_collect_more:nw { #1{#2} } }
}
\cs_new_protected:Npn __tnscalc_iterate_collect_more:nw #1 #2
__tnscalc_item:nw #3 \q_stop
{ __tnscalc_iterate_collect:nnw {#1} #3 __tnscalc_item:nw #2 \q_stop }
\cs_new_protected:Npn __tnscalc_iterate_collect_end:nw #1
__tnscalc_item:nw #2 \q_stop
{
__tnscalc_do:n {#1}
__tnscalc_item:nw #2 __tnscalc_item:nw { \q_nil } \q_stop
}
% Compatibility for older expl3
\cs_if_exist:NTF \seq_set_map_x:NNn
{ \cs_new_eq:NN __tnscalc_seq_set_map:NNn \seq_set_map:NNn } % newer expl3
{
\cs_new_protected:Npn __tnscalc_seq_set_map:NNn #1 #2 #3
{ \seq_set_map:NNn #1 #2 { \exp_not:n {#3} } } % older expl3
}
%
% In this macro, #1 will have as many items as
% there are \-separated items in your list.
%
% You can iterate over those items with \tl_map_inline:nn
% or you can have some other macro process them.
\cs_new_protected:Npn __tnscalc_do:n #1
{
(\int_use:N \l__tnscalc_numcol_int :: \use_i:nn #1 ; \use_ii:nn #1 )
\int_decr:N \l__tnscalc_numcol_int
% Column~\int_use:N \l__tnscalc_numcol_int :~
% \tl_map_inline:nn {#1}
% { (##1) }
% \par
% \int_decr:N \l__tnscalc_numcol_int
}
\ExplSyntaxOff
\begin{document}
\splittab{\}{&}{ a & b & c \ 1 & 2 & 3 }
\end{document}