सूची <Double> दिए गए मान, प्रारंभ, समाप्ति और चरण का क्रम उत्पन्न करने का सर्वोत्तम तरीका?


14

मुझे वास्तव में बहुत आश्चर्य हुआ कि मैं इसका उत्तर यहाँ नहीं पा रहा था, हालाँकि शायद मैं गलत खोज शब्दों या किसी चीज़ का उपयोग कर रहा हूँ। निकटतम मैं मिल सकता है इस है, लेकिन वे की एक विशिष्ट श्रेणी पैदा करने के बारे में पूछने doubleके लिए एक विशिष्ट कदम आकार के साथ, और जवाब के रूप में मानते हैं। मुझे कुछ ऐसा चाहिए जो मनमानी शुरू, अंत और चरण आकार के साथ संख्या उत्पन्न करेगा।

मुझे लगता है कि पहले से ही कहीं न कहीं लाइब्रेरी में इस तरह की कोई विधि होनी चाहिए, लेकिन अगर मैं इसे आसानी से नहीं ढूंढ पा रहा था (फिर से, शायद मैं गलत खोज शब्दों या कुछ का उपयोग कर रहा हूं)। तो यहाँ है कि मैं यह करने के लिए पिछले कुछ मिनटों में अपने दम पर पकाया है:

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);
    }

}

इन विधियों stepको अनुक्रम सूचकांक द्वारा गुणा करने और startऑफसेट में जोड़ने के लिए एक सरल लूप चलाया जाता है । यह फ़्लोटिंग-पॉइंट त्रुटियों को कम करता है जो निरंतर वृद्धि के साथ होता है (जैसे stepप्रत्येक पुनरावृत्ति पर एक चर को जोड़ना )।

मैंने generateSequenceRoundedउन मामलों के लिए विधि जोड़ी जहां एक आंशिक चरण आकार ध्यान देने योग्य फ़्लोटिंग पॉइंट त्रुटियों का कारण बन सकता है। यह थोड़ा अधिक अंकगणित की आवश्यकता है, इसलिए हमारे जैसे अत्यंत संवेदनशील स्थितियों में, राउंडिंग अनावश्यक होने पर सरल विधि का उपयोग करने का विकल्प रखना अच्छा है। मुझे संदेह है कि ज्यादातर सामान्य उपयोग के मामलों में राउंडिंग ओवरहेड नगण्य होगा।

ध्यान दें कि मैं जानबूझकर से निपटने जैसे "असामान्य" तर्क के लिए तर्क बाहर रखा गया Infinity, NaN, start> end, या एक नकारात्मक stepसादगी के लिए आकार और हाथ में सवाल पर ध्यान केंद्रित करने की इच्छा।

यहां कुछ उदाहरण उपयोग और संबंधित आउटपुट दिए गए हैं:

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]

क्या कोई मौजूदा लाइब्रेरी है जो पहले से ही इस तरह की कार्यक्षमता प्रदान करती है?

यदि नहीं, तो क्या मेरे दृष्टिकोण से कोई समस्या है?

क्या किसी के पास इसके लिए एक बेहतर दृष्टिकोण है?

जवाबों:


17

जावा 11 स्ट्रीम एपीआई का उपयोग करके अनुक्रम आसानी से उत्पन्न किया जा सकता है।

सरल दृष्टिकोण का उपयोग करना है 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());
}

बड़ी संख्या में पुनरावृत्तियों के साथ पर्वतमाला पर, doubleसटीक त्रुटि जमा हो सकती है, जिसके परिणामस्वरूप बड़ी त्रुटि सीमा के अंत के करीब हो सकती है। IntStreamपूर्णांक और सिंगल डबल गुणक पर स्विच करने और उपयोग करके त्रुटि को कम किया जा सकता है:

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());
}

एक doubleसटीक त्रुटि से छुटकारा पाने के लिए , BigDecimalइसका उपयोग किया जा सकता है:

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());
}

उदाहरण:

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]
}

इस हस्ताक्षर के साथ विधि पुनरावृति (3 पैरामीटर) जावा 9 में जोड़ी गई थी। इसलिए, जावा 8 के लिए कोड जैसा दिखता है

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

यह बेहतर दृष्टिकोण है।
विश्व रत्न

मैं कई संकलन त्रुटियाँ (JDK 1.8.0) देख रहा हूँ error: method iterate in interface DoubleStream cannot be applied to given types; return DoubleStream.iterate(start, d -> d <= end, d -> d + step) required: double,DoubleUnaryOperator. found: double,(d)->d <= end,(d)->d + step. reason: actual and formal argument lists differ in length:। के लिए IntStream.iterateऔर इसी तरह की त्रुटियाँ Stream.iterate। इसके अलावा, non-static method doubleValue() cannot be referenced from a static context
नैनो

1
उत्तर में जावा 11 कोड
एव्जेनी खिस

@NanoWizard ने जावा 8 के लिए एक नमूने के साथ उत्तर का विस्तार किया
इवगेनी खिस्ट

जावा 9 में तीन-तर्क पुनरावृत्ति जोड़ा गया था
थोरबजोरन रेव एंडरसन

3

मुझे व्यक्तिगत रूप से, मैं DoubleSequenceGenerator वर्ग को अन्य अच्छाइयों के लिए थोड़ा छोटा कर दूंगा और केवल एक अनुक्रम जनरेटर विधि का उपयोग करूंगा जिसमें वांछित वांछित परिशुद्धता का उपयोग करने का विकल्प होता है या कोई सटीक उपयोग नहीं करता है:

नीचे जनरेटर विधि में, यदि कुछ भी (या 0 से कम किसी भी मूल्य ) को वैकल्पिक सेटप्रेशर पैरामीटर में आपूर्ति की जाती है, तो कोई दशमलव सटीक गोलाई नहीं ली जाती है। यदि 0 को एक सटीक मूल्य के लिए आपूर्ति की जाती है, तो संख्याओं को उनके निकटतम पूर्ण संख्या में गोल किया जाता है (यानी: 89.674 को 90.0 पर गोल किया जाता है)। यदि 0 से अधिक विशिष्ट सटीक मान की आपूर्ति की जाती है, तो मान उस दशमलव परिशुद्धता में बदल जाते हैं।

BigDecimal का उपयोग यहाँ ... अच्छी तरह से किया जाता है .... परिशुद्धता:

import java.util.List;
import java.util.ArrayList;
import java.math.BigDecimal;
import java.math.RoundingMode;

public class DoubleSequenceGenerator {

     public static List<Double> generateSequence(double start, double end, 
                                          double step, int... setPrecision) {
        int precision = -1;
        if (setPrecision.length > 0) {
            precision = setPrecision[0];
        }
        List<Double> sequence = new ArrayList<>();
        for (double val = start; val < end; val+= step) {
            if (precision > -1) {
                sequence.add(BigDecimal.valueOf(val).setScale(precision, RoundingMode.HALF_UP).doubleValue());
            }
            else {
                sequence.add(BigDecimal.valueOf(val).doubleValue());
            }
        }
        if (sequence.get(sequence.size() - 1) < end) { 
            sequence.add(end); 
        }
        return sequence;
    }    

    // Other class goodies here ....
}

और मुख्य () में:

System.out.println(generateSequence(0.0, 2.0, 0.2));
System.out.println(generateSequence(0.0, 2.0, 0.2, 0));
System.out.println(generateSequence(0.0, 2.0, 0.2, 1));
System.out.println();
System.out.println(generateSequence(0.0, 102.0, 10.2, 0));
System.out.println(generateSequence(0.0, 102.0, 10.2, 0));
System.out.println(generateSequence(0.0, 102.0, 10.2, 1));

और कंसोल प्रदर्शित करता है:

[0.0, 0.2, 0.4, 0.6000000000000001, 0.8, 1.0, 1.2, 1.4, 1.5999999999999999, 1.7999999999999998, 1.9999999999999998, 2.0]
[0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 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.2, 71.4, 81.60000000000001, 91.80000000000001, 102.0]
[0.0, 10.0, 20.0, 31.0, 41.0, 51.0, 61.0, 71.0, 82.0, 92.0, 102.0]
[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]

दिलचस्प विचार, हालांकि मैं कुछ मुद्दों को देखता हूं। 1. valप्रत्येक पुनरावृत्ति पर जोड़कर , आपको योज्य परिशुद्धता हानि हो रही है। बहुत बड़े अनुक्रमों के लिए, अंतिम कुछ नंबरों पर त्रुटि महत्वपूर्ण हो सकती है। 2. दोहराया कॉल BigDecimal.valueOf()अपेक्षाकृत महंगे हैं। आप करने के लिए आदानों परिवर्तित करके बेहतर प्रदर्शन (और सटीक) मिल जाएगा BigDecimal, और का उपयोग कर BigDecimalके लिए val। वास्तव में, एक का उपयोग करके doubleके लिए val, आप वास्तव में किसी भी परिशुद्धता लाभ का उपयोग करने से हो रही है नहीं कर रहे हैं BigDecimalशायद राउंडिंग के साथ छोड़कर।
19

2

इसे इस्तेमाल करे।

public static List<Double> generateSequenceRounded(double start, double end, double step) {
    long mult = (long) Math.pow(10, BigDecimal.valueOf(step).scale());
    return DoubleStream.iterate(start, d -> (double) Math.round(mult * (d + step)) / mult)
                .limit((long) (1 + (end - start) / step)).boxed().collect(Collectors.toList());
}

यहाँ,

int java.math.BigDecimal.scale()

इस BigDecimal का पैमाना लौटाता है। यदि शून्य या सकारात्मक है, तो स्केल दशमलव संख्या के दाईं ओर अंकों की संख्या है। यदि ऋणात्मक है, तो पैमाने के नकारने की शक्ति के लिए संख्या का अनसुलझा मूल्य दस से गुणा किया जाता है। उदाहरण के लिए, -3 के पैमाने का अर्थ है कि बिना मूल्य का मूल्य 1000 से गुणा किया जाता है।

मुख्य () में

System.out.println(generateSequenceRounded(0.0, 102.0, 10.2));
System.out.println(generateSequenceRounded(0.0, 102.0, 10.24367));

और आउटपुट:

[0.0, 10.2, 20.4, 30.6, 40.8, 51.0, 61.2, 71.4, 81.6, 91.8, 102.0]
[0.0, 10.24367, 20.48734, 30.73101, 40.97468, 51.21835, 61.46202, 71.70569, 81.94936, 92.19303]

2
  1. क्या कोई मौजूदा लाइब्रेरी है जो पहले से ही इस तरह की कार्यक्षमता प्रदान करती है?

    क्षमा करें, मुझे नहीं पता, लेकिन अन्य उत्तरों और उनकी सापेक्ष सादगी को देखते हुए - नहीं, वहाँ नहीं है। कोई जरुरत नहीं है। हां तकरीबन...

  2. यदि नहीं, तो क्या मेरे दृष्टिकोण से कोई समस्या है?

    हां और ना। आपके पास कम से कम एक बग है, और प्रदर्शन को बढ़ावा देने के लिए कुछ जगह है, लेकिन दृष्टिकोण ही सही है।

    1. आपका बग: गोलाई त्रुटि (बस बदलने while (mult*fraction < 1.0)के लिए while (mult*fraction < 10.0)और है कि यह ठीक करना चाहिए)
    2. अन्य सभी नहीं पहुंचते हैं end... ठीक है, शायद वे आपके कोड में टिप्पणियों को पढ़ने के लिए पर्याप्त रूप से चौकस नहीं थे
    3. बाकी सभी धीमे हैं।
    4. बस मुख्य पाश में हालत को बदलने से int < Doubleकरने के लिए int < intकाफ़ी अपने कोड की गति में वृद्धि होगी
  3. क्या किसी के पास इसके लिए एक बेहतर दृष्टिकोण है?

    हम्म ... किस तरह से?

    1. सादगी? generateSequenceDoubleStream@Evgeniy Khyst काफी सरल दिखता है। और इस्तेमाल किया जाना चाहिए ... लेकिन शायद नहीं, अगले दो बिंदुओं के कारण
    2. सटीक? generateSequenceDoubleStreamनहीं है! लेकिन अभी भी पैटर्न के साथ बचाया जा सकता है start + step*i। और start + step*iपैटर्न सटीक है। केवल BigDoubleऔर निश्चित-बिंदु अंकगणित इसे हरा सकते हैं। लेकिन BigDoubleधीमे हैं, और मैनुअल फिक्स्ड-पॉइंट अंकगणित थकाऊ है और आपके डेटा के लिए अनुपयुक्त हो सकता है। वैसे, सटीकता के मामलों पर, आप इस से अपना मनोरंजन कर सकते हैं: https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
    3. गति ... अच्छी तरह से अब हम अस्थिर आधार पर हैं। इस उत्तर की जांच करें https://repl.it/repls/RespectfulSufficientWorker मेरे पास अभी एक सभ्य परीक्षण स्टैंड नहीं है, इसलिए मैंने repl.it का उपयोग किया ... जो प्रदर्शन परीक्षण के लिए पूरी तरह से अपर्याप्त है, लेकिन यह मुख्य बिंदु नहीं है। बिंदु है - कोई निश्चित उत्तर नहीं है। सिवाय इसके कि शायद आपके मामले में, जो कि आपके प्रश्न से पूरी तरह से स्पष्ट नहीं है, आपको निश्चित रूप से बिगडेसीमल (आगे पढ़ें) का उपयोग नहीं करना चाहिए।

      मैंने बड़े इनपुट्स के लिए खेलने और ऑप्टिमाइज़ करने की कोशिश की है। और आपका मूल कोड, कुछ मामूली बदलावों के साथ - सबसे तेज़। लेकिन शायद आपको बड़ी मात्रा में छोटे Listएस की आवश्यकता है? फिर वह बिल्कुल अलग कहानी हो सकती है।

      यह कोड मेरे स्वाद के लिए काफी सरल है, और काफी तेजी से:

        public static List<Double> genNoRoundDirectToDouble(double start, double end, double step) {
        int len = (int)Math.ceil((end-start)/step) + 1;
        var sequence = new ArrayList<Double>(len);
        sequence.add(start);
        for (int i=1 ; i < len ; ++i) sequence.add(start + step*i);
        return sequence;
        }

    यदि आप अधिक सुंदर तरीका पसंद करते हैं (या हमें इसे मुहावरेदार कहना चाहिए), तो, मैं व्यक्तिगत रूप से सुझाव दूंगा:

    public static List<Double> gen_DoubleStream_presice(double start, double end, double step) {
        return IntStream.range(0, (int)Math.ceil((end-start)/step) + 1)
            .mapToDouble(i -> start + i * step)
            .boxed()
            .collect(Collectors.toList());
    }

    वैसे भी, संभावित प्रदर्शन बूस्ट हैं:

    1. से स्विच Doubleकरने का प्रयास करें double, और यदि आपको वास्तव में उनकी आवश्यकता है, तो आप फिर से स्विच कर सकते हैं, परीक्षणों को देखते हुए, यह अभी भी तेज हो सकता है। (लेकिन मेरा विश्वास मत करो, अपने आप को अपने वातावरण में अपने डेटा के साथ आज़माएं। जैसा कि मैंने कहा - repl.it बेकार है बेंचमार्क)
    2. थोड़ा सा जादू: इसके लिए अलग लूप Math.round()... शायद यह डेटा स्थानीयता के साथ कुछ करना है। मैं इसकी सिफारिश नहीं करता हूं - परिणाम बहुत अस्थिर है। पर इसमे मज़ा है।

      double[] sequence = new double[len];
      for (int i=1; i < len; ++i) sequence[i] = start + step*i;
      List<Double> list = new ArrayList<Double>(len);
      list.add(start);
      for (int i=1; i < len; ++i) list.add(Math.round(sequence[i])/mult);
      return list;
    3. आप निश्चित रूप से अधिक आलसी हो सकता है और में तो भंडारण के बिना मांग पर संख्या उत्पन्न करने के लिए विचार करना चाहिए Listरों

  4. मुझे संदेह है कि ज्यादातर सामान्य उपयोग के मामलों में राउंडिंग ओवरहेड नगण्य होगा।

    यदि आपको कुछ संदेह है - इसका परीक्षण करें :-) मेरा उत्तर "हां" है, लेकिन फिर से ... मेरा विश्वास मत करो। झसे आज़माओ।

तो, मुख्य प्रश्न पर वापस जाएं: क्या कोई बेहतर तरीका है?
हां बिल्कुल!
लेकिन यह निर्भर करता है।

  1. यदि आपको बहुत बड़ी संख्या और बहुत कम संख्या की आवश्यकता है, तो बड़ा दशमलव चुनें । लेकिन अगर आप उन्हें वापस करने के लिए , और उस से अधिक, यह "बंद" परिमाण की संख्या के साथ उपयोग करते हैं - उनके लिए कोई ज़रूरत नहीं है! उसी उत्तर को चेकआउट करें: https://repl.it/repls/RespectfulSufficientWorker - अंतिम परीक्षण से पता चलता है कि परिणामों में कोई अंतर नहीं होगा , लेकिन गति में एक खो नुकसान।Double
  2. अपने डेटा गुणों, अपने कार्य और अपने वातावरण के आधार पर कुछ माइक्रो-ऑप्टिमाइज़ेशन करें।
  3. 5-10% के प्रदर्शन को बढ़ावा देने के लिए बहुत कुछ नहीं है, तो लघु और सरल कोड को प्राथमिकता दें। अपने समय कमर मत करो
  4. यदि आप कर सकते हैं और यदि यह इसके लायक है तो शायद निर्धारित बिंदु अंकगणित का उपयोग करें।

इसके अलावा, आप ठीक हैं।

पुनश्च । उत्तर में एक कहन सारांश फॉर्मूला कार्यान्वयन भी है ... सिर्फ मनोरंजन के लिए। https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html#1346 और यह काम करता है - आप योग त्रुटियों को कम कर सकते हैं

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.