Rebol COLLECT: сохранить порядок исходных данных в цикле

Есть некоторые исходные данные, например [1 2 3 4 "a" "b" "c" "d"], четыре элемента в группе. Я хочу извлечь некоторые данные в определенных позициях, таких как первая, третья и четвертая из каждой группы.

Вот мой код:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
foreach [p1 p2 p3 p4] data [ collect/into [keep p1 keep p3 keep p4] output ]
probe output    ;;the output is ["a" "c" "d" 1 3 4]

Но на самом деле я хочу [1 3 4 "a" "c" "d"]. Как мне сохранить заказ?


person Wayne Cui    schedule 16.02.2014    source источник


Ответы (2)


Все функции в Rebol, которые используют параметр /into, используют семантику insert. Мы добавили эту опцию, чтобы разрешить инкрементное строительство без необходимости создавать столько промежуточных серий, а также чтобы вы могли выбирать тип целевой серии, предварительно распределять и всевозможные другие уловки опытных пользователей. Параметр /into использует семантику insert, потому что insert не теряет столько информации, сколько append.

Возьмем ваш пример, но просто используйте collect:

data: [1 2 3 4 "a" "b" "c" "d"]
output: collect [
    foreach [p1 p2 p3 p4] data [ keep p1 keep p3 keep p4 ]
]
probe output

Такой простой код collect должен упростить написание. Но он немного медленный, поэтому давайте попробуем немного его оптимизировать, используя /into:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
foreach [p1 p2 p3 p4] data [
    output: collect/into [keep p1 keep p3 keep p4] output
]
probe head output

Это стандартная модель для /into кода, и она будет выводить данные в том порядке, в котором вы хотите. Но на самом деле у этого нет никаких преимуществ перед использованием обычного collect, поскольку вы не выделяете целевой блок заранее. Это сэкономит на перераспределениях:

data: [1 2 3 4 "a" "b" "c" "d"]
output: make block! 0.75 * length? data
foreach [p1 p2 p3 p4] data [
    output: collect/into [keep p1 keep p3 keep p4] output
]
probe head output

Но сам collect может быть немного медленным, потому что он не является родным; в основном это удобная функция. К счастью, у нас есть более быстрые нативные функции, которые используют /into одинаково:

data: [1 2 3 4 "a" "b" "c" "d"]
output: make block! 0.75 * length? data
foreach [p1 p2 p3 p4] data [ output: reduce/into [p1 p3 p4] output ]
probe head output

Здесь не используются неродные функции, это должно быть довольно быстро.

person BrianH    schedule 16.02.2014
comment
Отличное объяснение! reduce довольно быстро. - person Wayne Cui; 17.02.2014

По неизвестной мне причине collect использует insert внутри себя, поэтому он вставляет данные в начало, а не append в конец. Надеюсь, кто-нибудь сможет объяснить, почему это так. Между тем, вы можете использовать старый добрый repend для выполнения этой работы:

data: [1 2 3 4 "a" "b" "c" "d"]
output: copy []
forskip data 4 [repend output [data/1 data/3 data/4]]
probe output    ;; [1 3 4 "a" "c" "d"]

Кроме того, в этом случае лучше использовать forskip вместо foreach, поскольку вам не нужно определять переменные и осуществлять доступ только по индексу.

person rebolek    schedule 16.02.2014
comment
Да, это хорошее решение. Спасибо! если collect использовать insert способ, я только что получил ответ: просто используйте collect/into [keep p1 keep p3 keep p4] tail output. Интересно, будет ли потеря производительности, если я сделаю это. - person Wayne Cui; 16.02.2014
comment
Отличная идея. Вероятно, это также причина того, почему collect использует insert, поскольку это обеспечивает большую гибкость. Думаю, потеря производительности не будет заметна, поскольку tail просто устанавливает index. Однако для достижения максимальной производительности не определяйте output как copy [], а зарезервируйте некоторое пространство с помощью output: make block expected-size, чтобы не было необходимости автоматически расширять output буфер. - person rebolek; 16.02.2014
comment
Мне действительно нужно перенести документацию по опции /into в более доступное место. Не должно быть причин, по которым поведение collect/into было бы неожиданным, поскольку параметр /into должен вести себя точно так же для каждой функции Rebol, для которой он определен. Его поведение довольно тщательно стандартизировано. - person BrianH; 17.02.2014