Algoritmo para uma interface do usuário mostrando X controles deslizantes de porcentagem cujos valores vinculados sempre totalizam 100%

11

Um sistema que estou construindo inclui um conjunto de controles deslizantes da interface do usuário (o número varia), cada um com uma escala de 0 a 100. Por slider, quero dizer uma interface do usuário onde você pega um elemento e o arrasta para cima e para baixo, como um controle de volume. Eles são conectados por um algoritmo que garante que sempre totalizem 100. Portanto, quando um controle deslizante é movido para cima, todos os outros se movem para baixo, eventualmente para zero. Quando um é movido para baixo, os outros sobem. O total deve sempre ser 100. Portanto, aqui os controles deslizantes têm vários valores, mas totalizam 100%:

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

Se o primeiro controle deslizante for UP de 40 para 70, os outros deverão mover o valor DOWN (à medida que o controle deslizante é arrastado). Observe que três controles deslizantes foram alterados de 20 para 10 e um permaneceu em zero, pois não pode diminuir.

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

É claro que quando qualquer controle deslizante atinge 0 ou 100, ele não pode se mover mais, e é aí que minha cabeça realmente começa a doer. Portanto, se um controle deslizante se move mais alto, os outros se movem mais baixo, mas quando algum deles chega a zero, apenas os demais que ainda não atingiram o zero podem se mover mais baixo.

Estou perguntando isso aqui, pois esta questão é específica para o algoritmo, não para a implementação. FWIW, a plataforma é Android Java, mas isso não é especialmente relevante.

A abordagem adotada com minha primeira punhalada foi calcular a alteração percentual do controle deslizante que se moveu. Dividi essa alteração e a apliquei (na outra direção) aos outros valores do controle deslizante. O problema, porém, é que, usando porcentagens e multiplicando, se algum controle deslizante chegar a zero, ele nunca poderá ser aumentado novamente de zero - o resultado líquido disso é que controles deslizantes individuais ficam presos em zero. Usei controles deslizantes com um intervalo de 0 a 1.000.000 para evitar problemas de arredondamento e isso parece ser útil, mas ainda não criei um algoritmo que lide bem com todos os cenários.

Ollie C
fonte
2
Perguntas semelhantes foram feitas no site UX, embora o IMHO não tenha sido respondido de maneira particularmente satisfatória. Estou curioso para ver se existem boas soluções. Embora, para ser sincero, acho que ter os controles deslizantes ligados um ao outro causa mais problemas do que vale ao usuário - torna-se difícil terminar com a distribuição que você deseja, pois continua alterando os valores já inseridos à medida que vai.
Daniel B
1
Uma sugestão seria permitir que o usuário alternasse entre dois modos, essencialmente um onde os controles deslizantes estão vinculados entre si e outro onde eles não estão (e retornar ao "modo relativo" re-normalizaria a distribuição). Isso permitiria desviar alguns dos problemas mencionados, mas adiciona tanta complexidade à interface do usuário que pode ser mais fácil convencer o usuário a digitar os valores.
Daniel B
Você está definitivamente certo, pois exige muita brincadeira por parte do usuário, mas, neste caso, é uma forma de aplicativo de pesquisa em que os controles deslizantes representam problemas; portanto, a questão é TODOS sobre os pesos relativos dos valores.
Ollie C
1
Entendo ... meu problema é justamente que expressar algo como uma divisão 60/30/10 exigirá mais de 3 ações, porque, ao mexer na parte 30/10, a 60 continua se movendo. Fica progressivamente pior com listas maiores e só é realmente adequado para informações extremamente vagas, pois você nunca a alcançará no número que tem em sua cabeça.
Daniel B
2
De um ponto de vista de UX, sugiro que a pontuação total seja apresentada como um grande número ao lado dos controles deslizantes. À medida que você desliza um controle deslizante para cima, a pontuação total aumenta acima de "100" e o botão "próximo" fica desativado até que o usuário deslize outro controle deslizante para baixo para reduzir a pontuação total. Você nunca saberá qual dos outros 4 controles deslizantes o usuário deseja reduzir quando deslizar um controle deslizante para cima, portanto, nem tente.
Graham

Respostas:

10

Algoritmo Ganancioso

Quando um controle deslizante se move para cima (para baixo), todos os outros precisam se mover para baixo (para cima). Cada um tem algum espaço em que pode se mover (para baixo - sua posição, para cima: posição 100).

Portanto, quando um controle deslizante se move, pegue os outros, classifique-os pelo espaço em que eles podem se mover e simplesmente percorra-os.

Em cada iteração, mova o controle deslizante na direção necessária por (total para mover para a esquerda / controles deslizantes para a esquerda na fila) ou a distância que ele pode se mover, o que for menor.

Isso é linear em complexidade (já que você pode usar a mesma fila ordenada repetidamente, depois que ela tiver sido classificada).

Nesse cenário, nenhum controle deslizante fica preso, todos tentam se mover o máximo que podem, mas apenas até a parte justa.

Movimento ponderado

Uma abordagem diferente seria ponderar a mudança que eles precisam fazer. Eu acho que foi isso que você tentou fazer, a julgar pelo seu sliders "fique preso no zero". IMHO isso é mais natural, você só precisa fazer mais ajustes.

Especulando novamente, eu diria que você tenta ponderar os movimentos de diferentes controles deslizantes pela posição deles (isso se traduziria diretamente no problema de zero). No entanto, observe que você pode visualizar a posição do controle deslizante de diferentes direções - desde o início ou até o final. Se você ponderar por posição desde o início ao diminuir e posição do fim ao aumentar, evite seu problema.

Isso é bastante semelhante em terminologia à parte anterior - não pondere o movimento a ser feito pela posição dos controles deslizantes, o peso pelo espaço que eles deixaram para se mover nessa direção.

Ordous
fonte
1
Eu olhei mais fundo e constatamos que os "presos" não estão presos, mas simplesmente têm valores tão baixos que a alteração percentual quase não tem efeito - a quantidade que um controle deslizante move é relativa ao seu valor. Eu suspeito que sua sugestão de usar o valor oposto possa funcionar melhor, só preciso manter o valor total em 100. Obrigado por injetar alguma clareza.
Ollie C
1

A abordagem que eu adotaria é um pouco diferente e envolve o uso de uma representação interna diferente de cada controle deslizante.

  1. cada controle deslizante pode assumir qualquer valor entre 0..100 (X), que é usado como um fator de ponderação para esse controle deslizante (não uma%)

  2. some todos os valores do controle deslizante para obter o valor total (T)

  3. para determinar o valor exibido de cada controle deslizante, use ROUND (X * 100 / T)

  4. quando um controle deslizante é movido para cima ou para baixo, você altera apenas o valor do controle deslizante ; a ponderação para esse controle deslizante aumentará ou diminuirá em relação a todos os outros controles deslizantes, e o cálculo acima garantirá que a alteração em todos os outros controles deslizantes seja espalhada o mais uniformemente possível.

Jeffrey Kemp
fonte
Essa seria uma boa interface se o usuário estiver preocupado principalmente em criar frações mais ou menos precisas. No entanto, não vejo como isso é funcionalmente diferente de fazer os outros slides se moverem proporcionalmente à sua posição.
Ordous
1

Acho que você está complicando demais as coisas tentando ajustar o valor atual em uma porcentagem da alteração do controle deslizante 'movido', que fornece porcentagens de porcentagens, o que está introduzindo erros de arredondamento.

Como você sabe que está apenas lidando com um valor total de 100, eu manteria as coisas como números inteiros e trabalharia inversamente a partir dos 100, evitando qualquer confusão séria com o arredondamento. (No exemplo abaixo, eu ligo para qualquer arredondamento como números inteiros no final)

Minha técnica seria definir os controles deslizantes como 0-100 simples. Subtraia o valor 'novo' de 100 para calcular quanto redistribuir, espalhe-o entre os outros controles deslizantes de acordo com o peso e depois limpe)

Tanto quanto sei, este não é um código Android válido: 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 

Isso deve manipular intrinsecamente qualquer valor de 0 ou 100

Jon Story
fonte
0

E a abordagem de Round Robin? Crie uma transação que garanta que a agregação de valor a um controle deslizante seja reduzida em relação a seus pares. e vice versa.

Toda vez que uma mudança de controle deslizante executa a transação com um controle deslizante de ponto diferente (eu criaria um iterador que retornaria o controle deslizante de ponto). Se o controle deslizante de ponto for zero, prossiga para o próximo.

michaelbn
fonte
0

Apenas para elaborar a excelente resposta do Greedy Algorithm de @Ordous. Aqui está um detalhamento das etapas.

  1. Mover controle deslizante
  2. Salve a diferençaQuantidade movida como newValue - oldValue (Valor positivo significa que subiu e outros controles deslizantes precisam se mover para baixo e vice-versa)
  3. Classifique os outros na lista da menor para a maior parte da distância que eles PODEM mover na direção requerida pela diferença, sendo positivo ou negativo.
  4. Defina amountToMove = differenceAmount / restante OthersCount
  5. Faça um loop e mova cada controle deslizante de acordo com a quantidade que ele pode mover ou o amountToMove . O que for menor
  6. Subtraia a quantidade que o controle deslizante moveu de amountToMove e divida pela nova OthersCount restante
  7. Depois de concluído, você distribuiu com êxito a diferença Quantia entre os outros controles deslizantes.
Sean
fonte
Resposta fantástica aqui. stackoverflow.com/a/44369773/4222929
Sean
-3

Uma técnica simples é calcular porcentagens dos valores do controle deslizante em relação à soma dos valores do controle deslizante e reatribuir os valores do controle deslizante às respectivas porcentagens calculadas. desta forma, os valores dos controles deslizantes serão reajustados, por exemplo,

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

Embora ele introduza um erro de arredondamento, ele pode ser tratado caso seja necessário que os valores dos sliders sejam exatamente e sempre somados 100.

Eu configurei um violino para demonstrar isso usando angularjs. Por favor, visite demo

Ahmad Noor
fonte
2
... qual é a resposta de Jeffrey. Leia as outras respostas primeiro para evitar duplicatas.
Jan Doggen