Перетасуйте массив так, чтобы рядом не было двух одинаковых элементов
у меня есть массив, который содержит строки. Некоторые из этих строк могут совпадать, и это нормально. Они могут быть в любом порядке, для начала, но скорее всего они расположены в алфавитном порядке. У меня есть следующее shuffle
функция, которая перетасует все элементы. Однако, я хочу добавить условие, что никакие две одинаковые строки могут быть смежными в массиве.
например, это прекрасно: ook eek ook monkey ook
но это не так: ook ook eek ook monkey
два ook
являются смежными. Предполагается, что входные данные были проверены так, что любые дубликаты составляют менее половины общего числа элементов, поэтому существует набор несмежных решений. Например, ook ook ook eek
будет отказано. Строки могут содержать пробелы и символы UTF-8, но не новые строки - строки фактически являются именем файла изображений.
как я могу изменить
1 ответов
учитывая предварительное условие отклонения, можно разделить список слов на несколько "классов эквивалентности" (ECs) - специальных групп слов, в каждой из которых слова одинаковы по любому критерию. Отказ подразумевает, что существует не более одного ЕС, который частично находится в одной половине списка, а частично в другой.
мы откладываем часть этого EC в сторону, так что (1) оставшаяся часть содержится не более чем в одной из оставшихся половин списка, и (2) половинки строго одинакового размера. Затем перетасовываем половинки, каждую по отдельности. После этого мы их объединяем, первая половина занимает нечетные элементы,а вторая-четные. Затем мы случайным образом вставляем оставшиеся ранее отложенные элементы. Это довольно просто, так как все они принадлежат к одной ЕС и поэтому легко отметить места, где они могут быть и где они не могут.
по конструкции не может быть двух смежных элементов, принадлежащих одному EC.
[редактирование:] Наконец, реализация того, что выше.
shuffle() {
local sort="$(sort <<<"" | sed "s/^/+/g")"
local size="$(grep -c ^ <<<"$sort")"
local take cntr head tail
if [ "$sort" == "${sort%%$'\n'*}" ]; then
# single line: nothing to shuffle
echo "${sort#+}"
return
fi
if [ $[size & 1] == 1 ]; then
# enforcing equal halves, beginning to extract the middle
take="$(head -$[size / 2 + 1] <<<"$sort" | tail -1)"
fi
cntr="$take"
size=$[size / 2]
head="$(head -$size <<<"$sort")"
tail="$(tail -$size <<<"$sort")"
while [ "$(head -1 <<<"$tail")" == "$(tail -1 <<<"$head")" ]; do
# continue extracting the middle, if still left
if [ -n "$cntr" -a "$cntr" != "$(tail -1 <<<"$head")" ]; then
break
else
cntr="$(tail -1 <<<"$head")"
fi
((--size))
head="$(head -$size <<<"$head")"
tail="$(tail -$size <<<"$tail")"
take="${take:+$take$'\n'}$cntr"$'\n'"$cntr"
done
sort=()
for cntr in $(seq $size); do
# transforming two line sets into a single interlaced array
sort[$[cntr * 4 - 3]]="$(head -$cntr <<<"$head" | tail -1)"
sort[$[cntr * 4 - 1]]="$(head -$cntr <<<"$tail" | tail -1)"
done
for cntr in $(seq $[size - 1]); do
# shuffling each of the interlaced halves by Fisher-Yates
head="${sort[$[cntr * 4 - 3]]}"
tail=$[RANDOM % (size - cntr + 1) + cntr]
sort[$[cntr * 4 - 3]]="${sort[$[tail * 4 - 3]]}"
sort[$[tail * 4 - 3]]="$head"
head="${sort[$[cntr * 4 - 1]]}"
tail=$[RANDOM % (size - cntr + 1) + cntr]
sort[$[cntr * 4 - 1]]="${sort[$[tail * 4 - 1]]}"
sort[$[tail * 4 - 1]]="$head"
done
if [ -n "$take" ]; then
# got a remainder; inserting
tail=($(seq 0 $[size * 2]))
for cntr in $(seq $[size * 2]); do
# discarding potential places with remainder in proximity
if [ "${sort[$[cntr * 2 - 1]]}" \
== "${take%%$'\n'*}" ]; then
tail[$[cntr - 1]]=""
tail[$[cntr]]=""
fi
done
tail=(${tail[@]})
for cntr in $(seq 0 $[${#tail[@]} - 2]); do
# shuffling the remaining places, also by Fisher-Yates
head="${tail[$cntr]}"
size=$[RANDOM % (${#tail[@]} - cntr) + cntr]
tail[$cntr]="${tail[$size]}"
tail[$size]="$head"
done
size="$(grep -c ^ <<<"$take")"
while ((size--)); do
# finally inserting remainders
sort[$[${tail[$size]} * 2]]="${take%%$'\n'*}"
done
fi
head=0
size="${#sort[@]}"
while ((size)); do
# printing the overall result
if [ -n "${sort[$head]}" ]; then
echo "${sort[$head]#+}"
((size--))
fi
((head++))
done
}
# a very simple test from the original post
shuffle \
"ook
ook
eek
ook
monkey"