Перетасуйте массив так, чтобы рядом не было двух одинаковых элементов

у меня есть массив, который содержит строки. Некоторые из этих строк могут совпадать, и это нормально. Они могут быть в любом порядке, для начала, но скорее всего они расположены в алфавитном порядке. У меня есть следующее 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"