Алгоритм для пользовательского интерфейса, показывающий ползунки X процентов, чьи связанные значения всегда составляют 100%

11

Система, которую я создаю, включает в себя набор ползунков пользовательского интерфейса (число варьируется), каждый со шкалой от 0 до 100. Под слайдером я подразумеваю пользовательский интерфейс, в котором вы берете элемент и перетаскиваете его вверх и вниз, как регулятор громкости. Они связаны алгоритмом, который гарантирует, что они всегда составляют 100. Таким образом, когда один ползунок перемещается вверх, все остальные перемещаются вниз, в конечном итоге к нулю. Когда один движется вниз, другие движутся вверх. В любом случае общее количество должно быть 100. Таким образом, здесь ползунки имеют различные значения, но они составляют 100%:

----O------ 40
O----------  0
--O-------- 20
--O-------- 20
--O-------- 20

Если первый ползунок затем перемещается вверх с 40 до 70, остальные должны сдвинуть значение ВНИЗ (так как ползунок перетаскивается). Обратите внимание, что три ползунка изменились с 20 на 10, и один остался на нуле, поскольку он не может опуститься ниже.

-------O--- 70
O----------  0
-O--------- 10
-O--------- 10
-O--------- 10

Конечно, когда какой-либо ползунок достигает 0 или 100, он не может двигаться дальше, и моя голова действительно начинает болеть. Таким образом, если ползунок перемещается выше, другие перемещаются ниже, но когда любой из них достигает нуля, только остальные, которые еще не достигли нуля, могут двигаться ниже.

Я спрашиваю это здесь, так как этот вопрос относится к алгоритму, а не реализации. FWIW платформа Android Android, но это не особенно актуально.

Подход, который я применил к своему первому удару, состоял в том, чтобы рассчитать процентное изменение перемещения ползунка. Затем я разделил это изменение и применил его (в другом направлении) к другим значениям ползунка. Проблема, однако, в том, что при использовании процентов и умножения, если какой-либо ползунок достигает нуля, он никогда не может быть снова увеличен с нуля - чистый результат этого заключается в том, что отдельные ползунки застряли в нуле. Я использовал ползунки с диапазоном от 0 до 1 000 000, чтобы избежать проблем с округлением, и это, кажется, полезно, но мне еще предстоит создать алгоритм, который бы хорошо справлялся со всеми сценариями.

Олли С
источник
2
Подобные вопросы были заданы на сайте UX, хотя ИМХО на них не было получено особенно удовлетворительного ответа. Мне любопытно посмотреть, есть ли хорошие решения. Хотя, честно говоря, я обнаружил, что привязка ползунков друг к другу доставляет больше хлопот, чем того стоит даже пользователю - становится сложно получить желаемый дистрибутив, поскольку вы продолжаете изменять уже введенные значения по мере того, как вы идти.
Даниэль Б,
1
Одним из предложений было бы позволить пользователю переключаться между двумя режимами, по существу, одним, где ползунки связаны друг с другом, и другим, где их нет (и переключение обратно в «относительный режим» приведет к повторной нормализации распределения). Это позволило бы обойти некоторые проблемы, о которых вы упомянули, но при этом настолько усложнить интерфейс, что пользователю было бы проще заставить пользователя вводить значения.
Даниэль Б,
Вы определенно правы, это требует от пользователя много хлопот, но в данном случае это форма приложения для опроса, где ползунки представляют проблемы, поэтому вопрос ВСЕ об относительных весах значений.
Олли C
1
Я понимаю ... моя проблема в том, что для выражения чего-то вроде разделения 60/30/10 потребуется более 3-х действий, потому что при игре с 30/10 часть 60 продолжает двигаться. Он становится все хуже с большими списками, и действительно подходит только для крайне расплывчатого ввода, так как вы никогда не получите это число, которое у вас в голове.
Даниэль Б,
2
С точки зрения UX, я бы предложил, чтобы общий счет был представлен в виде большого числа рядом с ползунками. При перемещении одного ползунка общий балл увеличивается более чем на 100, и независимо от того, какая кнопка «следующий» отключена, пользователь не сдвигает другой ползунок вниз, чтобы уменьшить общий балл. Вы никогда не будете знать, какие из других 4 слайдеров пользователь хочет уменьшить, когда они поднимают один слайдер, так что даже не пытайтесь.
Грэм

Ответы:

10

Жадный алгоритм

Когда ползунок перемещается вверх (вниз), все остальные должны двигаться вниз (вверх). У каждого есть пространство, которое он может переместить (вниз - их позиция, вверх - 100 позиций).

Поэтому, когда один ползунок перемещается, возьмите другие ползунки, отсортируйте их по пространству, в котором они могут двигаться, и просто переберите их.

На каждой итерации перемещайте ползунок в нужном направлении (общая сумма для перемещения влево / ползунки в очереди) или на расстояние, которое он может переместить, в зависимости от того, что меньше.

Это линейный по сложности (так как вы можете использовать одну и ту же упорядоченную очередь снова и снова, как только она будет отсортирована).

В этом сценарии ползунки не застревают, все пытаются двигаться как можно больше, но только до своей справедливой доли.

Взвешенное движение

Другой подход - взвешивать движение, которое им нужно сделать. Я думаю, что это то, что вы пытались сделать, судя по вашим заявлениям «ползунки застряли в 0». ИМХО, это более естественно, вам просто нужно сделать больше настроек.

Снова рассуждая, я бы сказал, что вы пытаетесь взвесить ходы различных ползунков по их положению (это напрямую перешло бы к вашей задаче на 0). Однако обратите внимание, что вы можете просматривать положение ползунка с разных сторон - с начала или с конца. Если вы весите по положению от начала при уменьшении и от конца до конца при увеличении, вам следует избегать вашей проблемы.

По терминологии это очень похоже на предыдущую часть - не взвешивайте движение, которое необходимо выполнить, с помощью положения ползунков, взвешивайте его по месту, которое они оставили, чтобы двигаться в этом направлении.

Ordous
источник
1
Я посмотрел глубже, и оказалось, что «застрявшие» не застряли, но просто имеют такие низкие значения, изменение в процентах почти не влияет - величина, которую перемещает ползунок, относительно его значения. Я подозреваю, что ваше предложение использовать противоположное значение может работать лучше, мне просто нужно сохранить общее значение в 100. Спасибо за внесение некоторой ясности.
Ollie C
1

Подход, который я выбрал бы, немного отличается и включает в себя использование различного внутреннего представления каждого слайдера.

  1. каждый слайдер может принимать любое значение от 0..100 (X), которое используется в качестве весового коэффициента для этого слайдера (не%)

  2. сложите все значения ползунка, чтобы получить общую цифру (T)

  3. чтобы определить отображаемое значение каждого ползунка, используйте ROUND (X * 100 / T)

  4. когда ползунок перемещается вверх или вниз, вы изменяете только значение одного ползунка ; весовые коэффициенты для этого ползунка будут увеличиваться или уменьшаться относительно всех других ползунков, и приведенный выше расчет будет гарантировать, что изменение для всех других ползунков будет распределено как можно более равномерно.

Джеффри Кемп
источник
Это был бы хороший интерфейс, если пользователь в основном заботится о создании более или менее точных дробей. Однако я не вижу, как это функционально отличается от того, как другие слайды перемещаются пропорционально их положению.
Ордос
1

Я думаю, что вы слишком усложняете вещи, пытаясь отрегулировать текущее значение в процентах от изменения «перемещенного» ползунка, который дает вам проценты в процентах, что приводит к ошибкам округления.

Поскольку вы знаете, что когда-либо имеете дело только с общим значением 100, я бы оставил целые числа и работал бы в обратном порядке от 100, избегая каких-либо серьезных проблем с округлением. (В приведенном ниже примере я обрабатываю любое округление как целые числа в конце)

Моя техника - установить ползунки на 0-100. Вычтите «новое» значение из 100, чтобы выяснить, сколько нужно перераспределить, распределите это между другими ползунками в соответствии с их весом, а затем очистите)

Насколько я знаю, это не правильный код Android: p

// see how much is "left" to redistribute
amountToRedistribute = 100 - movedSlider.value

//What proportion of the original 100% was contained in the other sliders (for weighting)
allOtherSlidersTotalWeight = sum(other slider existing values)

//Approximately redistribute the amount we have left between the other sliders, adjusting for their existing weight
for each (otherSlider)
    otherSlider.value = floor(otherSlider.value/allOtherSlidersWeight * amountToRedistribute)
    amountRedistributed += otherSlider.value

//then clean up because the floor() above might leave one or two leftover... How much still hasn't been redistributed?
amountLeftToRedistribute -= amountRedistributed

//add it to a slider (you may want to be smarter and add in a check if the slider chosen is zero, or add it to the largest remaining slider, spread it over several sliders etc, I'll leave it fairly simple here)
otherSlider1.value += amountLeftToRedistribute 

Это должно по сути обрабатывать любое значение 0 или 100

Джон Стори
источник
0

Как насчет подхода Round Robin? Создайте транзакцию, гарантирующую, что добавление значения к одному ползунку уменьшится по сравнению с его аналогом. и наоборот.

Затем каждый раз при смене ползунка запускайте транзакцию с другим ползунком равноправия (я бы создал итератор, который по очереди возвращает ползунок равноправия). Если равноправный ползунок равен нулю, переходите к следующему.

michaelbn
источник
0

Просто чтобы развить великолепный ответ Алгоритма Жадного от @Ordous. Вот разбивка шагов.

  1. Переместить ползунок
  2. Сохраните разницу. Установите его как newValue - oldValue (положительное значение означает, что он перемещен вверх, а другие ползунки должны быть перемещены вниз и наоборот).
  3. Сортируйте остальные в списке от наименьшего к большей части расстояния, которое они МОГУТ сдвинуть в направлении, требуемом разницей : положительное или отрицательное.
  4. Установите amountToMove = differenceAmount / оставшееся OthersCount
  5. Прокрутите и переместите каждый ползунок либо на величину, которую он может переместить, либо на величину ToMove . Что меньше
  6. Вычтите сумму, которую ползунок переместил из amountToMove, и разделите на новый оставшийся OthersCount.
  7. Закончив, вы успешно распределили разницу между другими ползунками.
Шон
источник
Фантастический ответ здесь. stackoverflow.com/a/44369773/4222929
Шон
-3

Один простой метод заключается в том, чтобы рассчитать проценты значений ползунков относительно суммы значений ползунков, а затем переназначить значения ползунков соответствующим вычисленным процентам. таким образом, значения ползунка будут перенастроены, например

slider1.value = (slider1.value / sumOfSliderValues) * 100 ;
slider2.value = (slider2.value / sumOfSliderValues) * 100 ;
slider3.value = (slider3.value / sumOfSliderValues) * 100 ;

Хотя это приводит к ошибке округления, но это может быть обработано в случае, если нам нужно, чтобы значения ползунков точно и всегда суммировались до 100.

Я настроил скрипку, чтобы продемонстрировать это с помощью angularjs. Пожалуйста, посетите демо

Ахмад Нур
источник
2
... что ответ Джеффри. Пожалуйста, сначала прочтите другие ответы, чтобы избежать дубликатов.
Ян Догген