La mejor manera de generar una lista <Double> secuencia de valores dado inicio, final y paso?

NanoWizard:

En realidad estoy muy sorprendido de que yo era incapaz de encontrar la respuesta a esta aquí, aunque tal vez sólo estoy usando mal los términos de búsqueda o algo así. Más cercano que pude encontrar es esta , sino que se preguntan sobre la generación de un rango específico de doubles con un tamaño de paso específica, y las respuestas tratan como tal. Necesito algo que generará los números con inicio arbitrario, extremo y tamaño de paso.

Calculo que hay tiene que haber algún método como este en una biblioteca en algún lugar ya, pero si es así yo no era capaz de encontrar fácilmente (de nuevo, tal vez sólo estoy usando los términos de búsqueda mal o algo). Así que esto es lo que he cocinado en mi propia en los últimos minutos para hacer esto:

import java.lang.Math;
import java.util.List;
import java.util.ArrayList;

public class DoubleSequenceGenerator {


     /**
     * Generates a List of Double values beginning with `start` and ending with
     * the last step from `start` which includes the provided `end` value.
     **/
    public static List<Double> generateSequence(double start, double end, double step) {
        Double numValues = (end-start)/step + 1.0;
        List<Double> sequence = new ArrayList<Double>(numValues.intValue());

        sequence.add(start);
        for (int i=1; i < numValues; i++) {
          sequence.add(start + step*i);
        }

        return sequence;
    }

    /**
     * Generates a List of Double values beginning with `start` and ending with
     * the last step from `start` which includes the provided `end` value.
     * 
     * Each number in the sequence is rounded to the precision of the `step`
     * value. For instance, if step=0.025, values will round to the nearest
     * thousandth value (0.001).
     **/
    public static List<Double> generateSequenceRounded(double start, double end, double step) {

        if (step != Math.floor(step)) {
            Double numValues = (end-start)/step + 1.0;
            List<Double> sequence = new ArrayList<Double>(numValues.intValue());

            double fraction = step - Math.floor(step);
            double mult = 10;
            while (mult*fraction < 1.0) {
                mult *= 10;
            }

            sequence.add(start);
            for (int i=1; i < numValues; i++) {
              sequence.add(Math.round(mult*(start + step*i))/mult);
            }

            return sequence;
        }

        return generateSequence(start, end, step);
    }

}

Estos métodos se ejecutan un bucle sencillo multiplicando el steppor el índice de secuencia y añadiendo a la startoffset. Este capitalización errores de punto flotante mitiga que ocurrirían con incrementación continua (tales como la adición de la stepa una variable en cada iteración).

He añadido el generateSequenceRoundedmétodo para aquellos casos en que un tamaño de paso fraccional puede causar errores de punto flotante notables. Se requiere un poco más de la aritmética, por lo que en situaciones extremadamente sensibles rendimiento como el nuestro, es bueno tener la opción de utilizar el método más sencillo cuando el redondeo es innecesaria. Sospecho que en la mayoría de los casos de uso general de la sobrecarga redondeo sería insignificante.

Tenga en cuenta que Excluí intencionadamente lógica para el manejo de argumentos "anormales", tales como Infinity, NaN, start> end, o un negativo steptamaño de la simplicidad y el deseo de centrarse en la cuestión que nos ocupa.

He aquí algunos ejemplo de uso y la salida correspondiente:

System.out.println(DoubleSequenceGenerator.generateSequence(0.0, 2.0, 0.2))
System.out.println(DoubleSequenceGenerator.generateSequenceRounded(0.0, 2.0, 0.2));
System.out.println(DoubleSequenceGenerator.generateSequence(0.0, 102.0, 10.2));
System.out.println(DoubleSequenceGenerator.generateSequenceRounded(0.0, 102.0, 10.2));
[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2000000000000002, 1.4000000000000001, 1.6, 1.8, 2.0]
[0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
[0.0, 10.2, 20.4, 30.599999999999998, 40.8, 51.0, 61.199999999999996, 71.39999999999999, 81.6, 91.8, 102.0]
[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]

¿Hay una biblioteca existente que ofrece este tipo de funcionalidad ya?

Si no es así, ¿hay algún problema con mi enfoque?

¿Alguien tiene una mejor aproximación a esto?

Evgeniy Khyst :

Las secuencias se pueden generar fácilmente utilizando Java API 11 Stream.

El enfoque sencillo es utilizar DoubleStream:

public static List<Double> generateSequenceDoubleStream(double start, double end, double step) {
  return DoubleStream.iterate(start, d -> d <= end, d -> d + step)
      .boxed()
      .collect(toList());
}

En rangos con un gran número de iteraciones, doubleerror de precisión podría acumular resultante en el error más grande más próximo al extremo de la gama. El error puede ser minimizado por el cambio a IntStreamy el uso de números enteros y solo doble multiplicador:

public static List<Double> generateSequenceIntStream(int start, int end, int step, double multiplier) {
  return IntStream.iterate(start, i -> i <= end, i -> i + step)
      .mapToDouble(i -> i * multiplier)
      .boxed()
      .collect(toList());
}

Para deshacerse de un doubleerror de precisión en absoluto, BigDecimalse puede utilizar:

public static List<Double> generateSequenceBigDecimal(BigDecimal start, BigDecimal end, BigDecimal step) {
  return Stream.iterate(start, d -> d.compareTo(end) <= 0, d -> d.add(step))
      .mapToDouble(BigDecimal::doubleValue)
      .boxed()
      .collect(toList());
}

Ejemplos:

public static void main(String[] args) {
  System.out.println(generateSequenceDoubleStream(0.0, 2.0, 0.2));
  //[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2, 1.4, 1.5999999999999999, 1.7999999999999998, 1.9999999999999998]

  System.out.println(generateSequenceIntStream(0, 20, 2, 0.1));
  //[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2000000000000002, 1.4000000000000001, 1.6, 1.8, 2.0]

  System.out.println(generateSequenceBigDecimal(new BigDecimal("0"), new BigDecimal("2"), new BigDecimal("0.2")));
  //[0.0, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.6, 1.8, 2.0]
}

Método iterate con esta firma (3 parámetros) se añadió en Java 9. Por lo tanto, para Java 8 las miradas de código como

DoubleStream.iterate(start, d -> d + step)
    .limit((int) (1 + (end - start) / step))

Supongo que te gusta

Origin http://10.200.1.11:23101/article/api/json?id=435997&siteId=1
Recomendado
Clasificación