#foreach #tuples #d #variadic
Вопрос:
Допустим, я хочу обработать переменную функцию, которая поочередно получает начальные и конечные значения 1 или более интервалов, и она должна возвращать диапазон случайных значений в этих интервалах. Вы можете представить, что входные данные представляют собой сплющенную последовательность кортежей, все элементы которых распределены по одному диапазону.
import std.meta; //variadic template predicates
import std.traits : isFloatingPoint;
import std.range;
auto randomIntervals(T = U[0], U...)(U intervals)
if (U.length/2 > 0 amp;amp; isFloatingPoint!T amp;amp; NoDuplicates!U.length == 1) {
import std.random : uniform01;
T[U.length/2] randomValues;
// split and iterate over subranges of size 2
foreach(i, T start, T end; intervals.chunks(2)) { //= intervals.slide(2,2)
randomValues[i] = uniform01 * (end - start) start,
}
return randomValues.dup;
}
Пример не важен, я использую его только для объяснения. Размер фрагмента может быть любым конечным положительным size_t
, а не только 2
, и изменение размера фрагмента должно требовать только изменения количества переменных цикла в цикле foreach.
В приведенной выше форме он не будет компилироваться, так как будет ожидать только один аргумент (диапазон) для цикла foreach. То, что я хотел бы,-это то, что скорее автоматически использует или выводит скользящее окно в виде кортежа, полученного из числа заданных переменных цикла, и заполняет дополнительные переменные следующими элементами диапазона/массива при необходимости позволяет дополнительный индекс. Согласно документации, ряд кортежей позволяет уничтожатьпреобразование элементов кортежа в переменные цикла foreach, поэтому первое, о чем я подумал,-это превращение диапазона в последовательность кортежей, но не нашел для этого удобной функции.
Есть ли простой способ перебирать деструктурированные поддиапазоны (с такой простотой, как показано в моем примере кода) вместе с индексом? Или существует функция (стандартная библиотека), которая выполняет эту работу по разбиению диапазона на перечислимые кортежи одинакового размера? Как легко превратить диапазон поддиапазонов в диапазон кортежей?
Возможно ли std.algorithm.iteration.map
в этом случае (ПРАВКА: с простым аргументом функции для сопоставления и без доступа к элементам кортежа)?
ПРАВКА: Я хочу проигнорировать последний фрагмент, который не вписывается во весь кортеж. Это просто не повторяется снова и снова.
РЕДАКТИРОВАТЬ: Дело не в том, что я не мог запрограммировать это сам, я надеюсь только на простую нотацию, потому что этот вариант использования циклического перебора нескольких элементов весьма полезен. Если в D есть что-то вроде оператора «распространение» или «отдых», как в JavaScript, пожалуйста, дайте мне знать!
Спасибо.
Ответ №1:
(Добавлен в качестве отдельного ответа, потому что он значительно отличается от моего предыдущего ответа и не поместился бы в комментарии)
После прочтения ваших комментариев и обсуждения ответов до сих пор мне кажется, что вы ищете что-то вроде приведенной ниже staticChunks
функции:
unittest {
import std.range : enumerate;
size_t index = 0;
foreach (i, a, b, c; [1,2,3,1,2,3].staticChunks!3.enumerate) {
assert(a == 1);
assert(b == 2);
assert(c == 3);
assert(i == index);
index;
}
}
import std.range : isInputRange;
auto staticChunks(size_t n, R)(R r) if (isInputRange!R) {
import std.range : chunks;
import std.algorithm : map, filter;
return r.chunks(n).filter!(a => a.length == n).map!(a => a.tuplify!n);
}
auto tuplify(size_t n, R)(R r) if (isInputRange!R) {
import std.meta : Repeat;
import std.range : ElementType;
import std.typecons : Tuple;
import std.array : front, popFront, empty;
Tuple!(Repeat!(n, ElementType!R)) resu<
static foreach (i; 0..n) {
result[i] = r.front;
r.popFront();
}
assert(r.empty);
return resu<
}
Обратите внимание, что это также связано с тем, что последний кусок имеет другой размер, хотя бы потому, что он молча выбрасывается. Если такое поведение нежелательно, удалите filter
его и разберитесь с ним внутри tuplify
(или не делайте этого и наблюдайте за появлением исключений).
Комментарии:
1. Приятно, я как раз собирался опубликовать свое собственное решение этого вопроса, но сначала я принимаю ваш ответ 🙂 . Два часа назад я придумал, по сути, очень похожий подход.
Ответ №2:
chunks
и slide
возвращайте Range
s, а не кортежи. Их последний элемент может содержать меньше указанного размера, в то время как кортежи имеют фиксированный размер во время компиляции.
Если вам нужно деструктурирование, вы должны реализовать свои собственные фрагменты/слайды, которые возвращают кортежи. Чтобы явно добавить индекс в кортеж, используйте enumerate
. Вот пример:
import std.typecons, std.stdio, std.range;
Tuple!(int, int)[] pairs(){
return [
tuple(1, 3),
tuple(2, 4),
tuple(3, 5)
];
}
void main(){
foreach(size_t i, int start, int end; pairs.enumerate){
writeln(i, ' ', start, ' ', end);
}
}
Редактировать:
Как сказал БиоТроник, использование map
также возможно:
foreach(i, start, end; intervals
.chunks(2)
.map!(a => tuple(a[0], a[1]))
.enumerate){
Комментарии:
1. 1 из-за упоминания уменьшенного размера последнего фрагмента. Это ближе к ответу, который я хотел получить. Я надеялся на одну стандартную библиотечную функцию или умный метод (не обязательно в смысле ООП), который выполняет эту схему «chunks().map().enumerate()» за один вызов функции, точно так же, как вы можете добавить индекс при циклическом переборе простых итераций с помощью foreach. Я добавлю более подробную информацию к своему вопросу.
2. И я полагаю, что ваш пример «карты» потерпит неудачу, если
intervals
диапазон содержит нечетное количество элементов.3. Для вашей проблемы не существует «простого» решения с одной функцией, так как комбинация операций (особенно с «отбросьте последнюю, если она не подходит») очень специфична и, следовательно, не подходит для стандартной библиотеки. Если вы ожидаете список интервалов нечетной длины и хотите удалить оставшуюся часть, вы можете нарезать перед группировкой:
intervals[0 .. size_t(intervals.length/2)*2]
4. Спасибо, идея нарезки хороша! Однако, с моей точки зрения (по крайней мере, при преобразовании диапазона в последовательность групп статического размера), я действительно ожидал бы, что реализация будет отсекать слишком маленькие группы. Это только до тех пор, пока группы имеют статический размер , но преобразование диапазона в группы динамического размера-это совсем другая история, в которой я бы согласился с вами.
Ответ №3:
Ваш вопрос меня немного смутил, так что извините, если я неправильно понял. По сути, вы спрашиваете foreach(a, b; [1,2,3,4].chunks(2))
, может ли это сработать, верно?
Простое решение здесь состоит в том, чтобы, как вы говорите, map
от куска к tuple
:
import std.typecons : tuple;
import std.algorithm : map;
import std.range : chunks;
import std.stdio : writeln;
unittest {
pragma(msg, typeof([1,2].chunks(2).front));
foreach(a, b; [1,2,3,4].chunks(2).map!(a => tuple(a[0], a[1]))) {
writeln(a, ", ", b);
}
}
Комментарии:
1. Ну, это «простое» решение, но для него потребуются изменения лямбды для «карты» при изменении размера фрагмента. Он не предоставляет индекс для цикла foreach и требует, чтобы вы вручную индексировали каждый элемент кортежа. Избегание явного индексированного доступа-главная цель моего вопроса. В вашем примере я лично считаю, что лучше разместить[0] и[i] в теле цикла, а не в вызове «карта».
Ответ №4:
В то же время с BioTronic я попытался закодировать какое-то собственное решение этой проблемы (протестировано на DMD). Мое решение работает для срезов (НО НЕ массивов фиксированного размера) и позволяет избежать вызова filter
:
import std.range : chunks, isInputRange, enumerate;
import std.range : isRandomAccessRange; //changed from "hasSlicing" to "isRandomAccessRange" thanks to BioTronics
import std.traits : isIterable;
/** turns chunks into tuples */
template byTuples(size_t N, M)
if (isRandomAccessRange!M) { //EDITED
import std.meta : Repeat;
import std.typecons : Tuple;
import std.traits : ForeachType;
alias VariableGroup = Tuple!(Repeat!(N, ForeachType!M)); //Tuple of N repititions of M's Foreach-iterated Type
/** turns N consecutive array elements into a Variable Group */
auto toTuple (Chunk)(Chunk subArray) @nogc @safe pure nothrow
if (isInputRange!Chunk) { //Chunk must be indexable
VariableGroup nextLoopVariables; //fill the tuple with static foreach loop
static foreach(index; 0 .. N) {
static if ( isRandomAccessRange!Chunk ) { // add cases for other ranges here
nextLoopVariables[index] = subArray[index];
} else {
nextLoopVariables[index] = subArray.popFront();
}
}
return nextLoopVariables;
}
/** returns a range of VariableGroups */
auto byTuples(M array) @safe pure nothrow {
import std.algorithm.iteration : map;
static if(!isInputRange!M) {
static assert(0, "Cannot call map() on fixed-size array.");
// auto varGroups = array[].chunks(N); //fixed-size arrays aren't slices by default and cannot be treated like ranges
//WARNING! invoking "map" on a chunk range from fixed-size array will fail and access wrong memory with no warning or exception despite @safe!
} else {
auto varGroups = array.chunks(N);
}
//remove last group if incomplete
if (varGroups.back.length < N) varGroups.popBack();
//NOTE! I don't know why but `map!toTuple` DOES NOT COMPILE! And will cause a template compilation mess.
return varGroups.map!(chunk => toTuple(chunk)); //don't know if it uses GC
}
}
void main() {
testArrayToTuples([1, 3, 2, 4, 5, 7, 9]);
}
// Order of template parameters is relevant.
// You must define parameters implicitly at first to be associated with a template specialization
void testArrayToTuples(U : V[], V)(U arr) {
double[] randomNumbers = new double[arr.length / 2];
// generate random numbers
foreach(i, double x, double y; byTuples!2(arr).enumerate ) { //cannot use UFCS with "byTuples"
import std.random : uniform01;
randomNumbers[i] = (uniform01 * (y - x) x);
}
foreach(n; randomNumbers) { //'n' apparently works despite shadowing a template parameter
import std.stdio : writeln;
writeln(n);
}
}
Использование поэлементных операций с оператором среза здесь не сработало бы, поскольку uniform01
in uniform01 * (ends[] - starts[]) starts[]
вызывался бы только один раз, а не несколько раз.
РЕДАКТИРОВАТЬ: Я также протестировал некоторые онлайн-компиляторы для D для этого кода, и странно, что они ведут себя по-разному для одного и того же кода. Для составления D я могу порекомендовать
- https://run.dlang.io/ (Я был бы очень удивлен, если бы это не сработало)
- https://www.mycompiler.io/new/d (но немного медленно)
- https://ideone.com (это работает, но делает ваш код общедоступным! Не используйте с защищенным кодом.)
но это не сработало для меня:
- https://tio.run/#d2 (в одном случае компиляция не была завершена, в противном случае неправильные результаты при выполнении даже при использовании динамического массива для теста)
- https://www.tutorialspoint.com/compile_d_online.php (не компилирует статический foreach)
Комментарии:
1. С этим решением есть некоторые проблемы, которые я собираюсь отнести скорее к языку, чем к вам. В
byTuples
функцииvarGroups
переменная сохраняется в стеке, и когда функция возвращает ссылку, она становится недействительной. Это может привести к повреждению памяти в какой-то момент позже. Скомпилируйте с-dip1000
(который, я полагаю, скоро будет по умолчанию), чтобы получить там ошибку.2. Вы также используете эту
hasSlicing
черту, чтобы проверить, можно лиChunk
ее индексировать. Как следует из названия,hasSlicing
проверьте, является ли тип срезаемым (foo[1..3]
), одновременноhasIndexing
проверяя индексируемость (foo[2]
). Я не знаю ни о каких типах, которые имеют нарезку, но не индексирование, но это теоретически возможно. Это не большая проблема.3. @BioTronic О, хорошо. Я этого не знал. Я тоже еще немного новичок, и мне требуется много времени, чтобы найти библиотечные символы, которые делают то, что и как я хотел бы иметь.
4. Кстати, я не смог найти
hasIndexing
на Фобосе, но вместоisRandomAccessRange
этого используется для проверки индексируемого диапазона.5. Вы абсолютно правы,
isRandomAccessRange
это то, о чем я думал. У меня нет оправдания. :p