Двухуровневые брызги TCL

Если у меня есть процедура или команда в TCL с переменным числом аргументов, можно использовать, если элементы списка являются входными данными, оператор "splatter", например:

set a [list "ko" ]
set m [ list "ok" "bang" ]
lappend a {*}$m

Но что, если я захочу "дважды разбрызгать"? Т.е. сгладить 2 уровня? Использование его дважды по порядку не работает:

set a [list "ko" ]
set m [ list [ list "ok" ] [ list "bang" ] ]
lappend a {*}{*}$m

Будет ошибка на дополнительный символ.

2 ответа

Решение

Вы заметили, что {*} (намеренно) не идет на два шага вглубь.

Ваш конкретный пример не очень свидетельствует о проблеме, поэтому я предлагаю следующее:

set a [list "ko" ]
set m [list [list "a b" "c d"] [list "e f" "g h"]]
lappend a {*}$m

Здесь мы получаем a установлен в ko {{a b} {c d}} {{e f} {g h}}, Что не то, что вы хотели. Но вместо этого мы можем сделать это:

lappend a {*}[concat {*}$m]

Что дает это: ko {a b} {c d} {e f} {g h}, Это выглядит правильно.


Но мы действительно делаем правильные вещи здесь? Давайте заглянем внутрь с нашим суперсекретным интроспектором, representation команда:

% tcl::unsupported::representation $m
value is a list with a refcount of 4, object pointer at 0x10085ec50, internal representation 0x103016790:0x0, string representation "{{a b} {c d}}..."
% tcl::unsupported::representation [concat {*}$m]
value is a string with a refcount of 1, object pointer at 0x10085de10, internal representation 0x1030052d0:0x10085f190, string representation "{a b} {c d} {..."

Ой! Мы потеряли список. Это не катастрофа, но это не то, что мы хотели. Вместо этого мы должны сделать:

foreach sublist $m {
    lappend a {*}$sublist
}

Что ж, это больше кода, но он сохраняет списки (что было бы хорошо, если бы вы имели драгоценные типы на листьях; ядро ​​Tcl не имеет таких ценных типов, но некоторые расширения имеют).

Мы можем сравнить сроки:

% time {
    set a [list "ko" ]
    set m [list [list "a b" "c d"] [list "e f" "g h"]]
    lappend a {*}[concat {*}$m]
} 10000
2.852789 microseconds per iteration
% time {
    set a [list "ko" ]
    set m [list [list "a b" "c d"] [list "e f" "g h"]]
    foreach sublist $m {
        lappend a {*}$sublist
    }
} 10000
4.022959 microseconds per iteration

Ой…

% time {apply {{} {
    set a [list "ko" ]
    set m [list [list "a b" "c d"] [list "e f" "g h"]]
    lappend a {*}[concat {*}$m]
}}} 10000
2.4486125 microseconds per iteration
% time {apply {{} {
    set a [list "ko" ]
    set m [list [list "a b" "c d"] [list "e f" "g h"]]
    foreach sublist $m {
        lappend a {*}$sublist
    }
}}} 10000
1.6870501 microseconds per iteration

Хах! Корректный тип лучше в процедуре (как в контексте).

{*} синтаксис на самом деле не сводит список как таковой. Уплощение является действием уровня выполнения, в то время как расширение аргумента является действием уровня синтаксического анализа.

% set a a
% lappend a {*}{{a b} {c d}}

В этом примере элементы в списке {{a b} {c d}} вставляются в командную строку как два отдельных аргумента:

% lappend a {a b} {c d}
a {a b} {c d}

Если вам нужно сплющить еще один уровень, вы должны сделать это с помощью команды "между" расширениями:

% lappend a {*}[concat {*}{{a b} {c d}}]
a a b c d

Документация: concat, lappend, {*} (синтаксис), сводка синтаксиса языка Tcl

Другие вопросы по тегам