Algoritmo para una IU que muestra controles deslizantes de porcentaje X cuyos valores vinculados siempre suman 100%

11

Un sistema que estoy construyendo incluye un conjunto de controles deslizantes de UI (el número varía) cada uno con una escala de 0-100. Por control deslizante me refiero a una interfaz de usuario donde agarras un elemento y lo arrastras hacia arriba y hacia abajo, como un control de volumen. Están conectados por un algoritmo que asegura que siempre suman 100. Entonces, cuando un control deslizante se mueve hacia arriba, los demás se mueven hacia abajo, eventualmente a cero. Cuando uno se mueve hacia abajo, los otros se mueven hacia arriba. En todo momento, el total debe ser 100. Entonces, los controles deslizantes tienen varios valores, pero suman 100%:

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

Si el primer control deslizante luego se mueve hacia ARRIBA de 40 a 70, los otros deben moverse hacia ABAJO en valor (a medida que se arrastra el control deslizante). Tenga en cuenta que tres controles deslizantes cambiaron de 20 a 10, y uno se mantuvo en cero, ya que no puede bajar.

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

Por supuesto, cuando cualquier control deslizante alcanza 0 o 100, no puede moverse más, que es donde realmente me comienza a doler la cabeza. Entonces, si un control deslizante se mueve más alto, los otros se mueven más abajo, pero cuando alguno de ellos llega a cero, solo los restantes que aún no han llegado a cero pueden moverse más abajo.

Estoy haciendo esto aquí ya que esta pregunta es específica del algoritmo, no de la implementación. FWIW la plataforma es Android Java, pero eso no es especialmente relevante.

El enfoque que tomé con mi primera puñalada fue calcular el cambio porcentual del control deslizante que se movió. Luego dividí ese cambio y lo apliqué (en la otra dirección) a los otros valores del control deslizante. Sin embargo, el problema es que al usar porcentajes y multiplicar, si un control deslizante llega a cero, nunca se puede volver a aumentar desde cero; el resultado neto es que los controles deslizantes individuales se atascan en cero. He usado controles deslizantes con un rango de 0 - 1,000,000 para evitar problemas de redondeo y eso parece ser útil, pero todavía tengo que crear un algoritmo que maneje bien todos los escenarios.

Ollie C
fuente
2
Se han hecho preguntas similares en el sitio de UX, aunque en mi humilde opinión, no han sido respondidas de una manera particularmente satisfactoria. Tengo curiosidad por ver si hay alguna buena solución. Aunque, para ser honesto, creo que tener los controles deslizantes vinculados entre sí causa más problemas de lo que vale incluso para el usuario: se hace difícil terminar con la distribución que desea, ya que sigue modificando sus valores ya ingresados ​​a medida que lo hace Vamos.
Daniel B
1
Una sugerencia sería permitir al usuario cambiar entre dos modos, esencialmente uno donde los controles deslizantes están vinculados entre sí, y uno donde no lo están (y volver al "modo relativo" volvería a normalizar la distribución). Esto permitiría esquivar algunos de los problemas que menciona, pero agregaría tanta complejidad a la interfaz de usuario que podría ser más fácil hacer que el usuario escriba los valores.
Daniel B
Definitivamente tiene razón, requiere mucho trabajo por parte del usuario, pero en este caso es una forma de aplicación de encuesta donde los controles deslizantes representan problemas, por lo que la pregunta es TODO sobre los pesos relativos de los valores.
Ollie C
1
Entiendo ... mi problema es solo que expresar algo como una división 60/30/10 requerirá más de 3 acciones, porque cuando se juega con la parte 30/10, el 60 sigue moviéndose. Se empeora progresivamente con listas más grandes, y solo es realmente adecuado para entradas extremadamente vagas, ya que nunca obtendrá el número que tiene en la cabeza.
Daniel B
2
Desde el punto de vista de UX, sugeriría que la puntuación total se presente como un gran número a un lado de los controles deslizantes. A medida que desliza un control deslizante hacia arriba, la puntuación total aumenta más de "100" y cualquiera que sea su botón "siguiente" se desactiva hasta que el usuario deslice otro control deslizante hacia abajo para reducir la puntuación total. Nunca sabrá cuál de los otros 4 controles deslizantes quiere reducir el usuario cuando desliza un control deslizante hacia arriba, así que ni lo intente.
Graham

Respuestas:

10

Algoritmo codicioso

Cuando un control deslizante se mueve hacia arriba (abajo), todos los demás deben moverse hacia abajo (arriba). Cada uno tiene algo de espacio que puede mover (para abajo, su posición, para arriba: 100 posiciones).

Entonces, cuando se mueve un control deslizante, tome los otros controles deslizantes, ordénelos por el espacio que pueden mover y simplemente repítelos.

En cada iteración, mueva el control deslizante en la dirección necesaria (total para mover a la izquierda / controles deslizantes a la izquierda en la cola) o la distancia que puede moverse, lo que sea menor.

Esto es de complejidad lineal (ya que puede usar la misma cola ordenada una y otra vez, una vez que se ha ordenado).

En este escenario, los controles deslizantes no se atascan, todos intentan moverse tanto como pueden, pero solo hasta su parte justa.

Movimiento ponderado

Un enfoque diferente sería sopesar el movimiento que necesitan hacer. Creo que esto es lo que trató de hacer, a juzgar por su declaración "los controles deslizantes se atascan en 0". En mi humilde opinión, esto es más natural, solo necesitas hacer más ajustes.

Especulando nuevamente, diría que intentas sopesar los movimientos de los diferentes controles deslizantes por su posición (Esto se traduciría directamente en tu problema atascado en 0). Sin embargo, tenga en cuenta que puede ver la posición del control deslizante desde diferentes direcciones, desde el principio o desde el final. Si pesa por posición desde el principio al disminuir y posición desde el final al aumentar, debe evitar su problema.

Esto es bastante similar en terminología a la parte anterior: no evalúe el movimiento a realizar por la posición de los controles deslizantes, evalúe el espacio que les queda para moverse en esa dirección.

Ordous
fuente
1
Miré más profundo y resultó que los "atascados" no están atascados, sino que simplemente tienen valores tan bajos que el cambio porcentual casi no tiene efecto: la cantidad que mueve un deslizador es relativa a su valor. Sospecho que su sugerencia de usar el valor opuesto puede funcionar bastante mejor, solo necesito mantener el valor total en 100. Gracias por inyectar algo de claridad.
Ollie C
1

El enfoque que tomaría es ligeramente diferente e implica el uso de una representación interna diferente de cada control deslizante.

  1. cada control deslizante puede tomar cualquier valor de 0..100 (X) que se utiliza como factor de ponderación para ese control deslizante (no un%)

  2. sume todos los valores del control deslizante para obtener la cifra total (T)

  3. para determinar el valor mostrado de cada control deslizante, use REDONDO (X * 100 / T)

  4. cuando un control deslizante se mueve hacia arriba o hacia abajo, solo cambia el valor de un control deslizante ; la ponderación de ese control deslizante aumentará o disminuirá en relación con todos los demás controles deslizantes, y el cálculo anterior garantizará que el cambio a todos los otros controles deslizantes se distribuya de la manera más uniforme posible.

Jeffrey Kemp
fuente
Esta sería una buena interfaz si el usuario se preocupa principalmente por hacer fracciones más o menos precisas. Sin embargo, no puedo ver cómo esto es funcionalmente diferente de hacer que las otras diapositivas se muevan proporcionalmente a su posición.
Ordous
1

Creo que está complicando demasiado las cosas al intentar ajustar el valor actual en un porcentaje del cambio del control deslizante 'movido', que le da porcentajes de porcentajes, lo que está introduciendo errores de redondeo.

Como sabes que solo estás tratando con un valor total de 100, mantendría las cosas como números enteros y trabajaría hacia atrás desde el 100, evitando cualquier problema serio con el redondeo. (En el siguiente ejemplo, manejo cualquier redondeo como enteros enteros al final)

Mi técnica sería establecer los controles deslizantes como simples 0-100. Reste el valor 'nuevo' de 100 para calcular cuánto redistribuir, distribuya eso entre los otros controles deslizantes de acuerdo con su peso, luego limpie)

Este no es, hasta donde yo sé, un código de 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 

Esto debería manejar intrínsecamente cualquier valor 0 o 100

Jon Story
fuente
0

¿Qué pasa con el enfoque Round Robin? Cree una transacción que asegure que agregar valor a un control deslizante se reducirá de su par. y viceversa.

Luego, cada vez que cambie un control deslizante, ejecute la transacción con un control deslizante de pares diferente (crearía un iterador que devolvería el control deslizante de pares por turnos). Si el control deslizante de pares es cero, continúe con el siguiente.

michaelbn
fuente
0

Solo para elaborar sobre la gran respuesta del algoritmo codicioso de @Ordous. Aquí hay un desglose de los pasos.

  1. Mover control deslizante
  2. Ahorre la diferencia Montar que se movió como newValue - oldValue (El valor positivo significa que se movió hacia arriba y otros controles deslizantes deben moverse hacia abajo y viceversa)
  3. Ordenar los otros en la lista de menos a más de distancia que pueden moverse en la dirección requerida por el differenceAmount ser positivo o negativo.
  4. Establezca la cantidad ToMove = differenceAmount / resto OthersCount
  5. Loop a través y mover cada deslizador por ya sea la cantidad que se puede mover o la amountToMove . El que sea más pequeño
  6. Reste la cantidad que el control deslizante se movió de la cantidad a Mover y divida por el nuevo OthersCount restante
  7. Una vez terminado, ha distribuido con éxito la diferencia de Cantidad entre los otros controles deslizantes.
Sean
fuente
Fantástica respuesta aquí. stackoverflow.com/a/44369773/4222929
Sean
-3

Una técnica simple es calcular los porcentajes de los valores del control deslizante en relación con la suma de los valores del control deslizante y luego reasignar los valores del control deslizante a los porcentajes calculados respectivos. de esta manera los valores del control deslizante se reajustarán, por ejemplo

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

Aunque introduce un error de redondeo, puede manejarse en caso de que necesitemos los valores de los controles deslizantes para sumar exactamente y siempre hasta 100.

He configurado un violín para demostrar esto usando angularjs. Por favor visite demo

Ahmad Noor
fuente
2
... que es la respuesta de Jeffrey. Lea las otras respuestas primero para evitar duplicados.
Jan Doggen