[SOLVED] Best way to save min, max, average?

I have some values:

        float time = demoUIDisplay.timer;
        int cadence = powermeterDisplay.instantaneousCadence;
        float heartrate = heartrateDisplay.heartRate;
        int power = powermeterDisplay.instantaneousPower;

I want over “time” save the values every second to a (list or array)
When finish i want to save the average, min and max of the values.
I never have create a list or array in unity, and what is the right choice? Because the Player can ride on the game for hours of time. Another question is: can i save the values in 1 list/array or need i for every value a new list/array?
i find no example what explain it a bit.

Either create each list for each value,
Or
Create list wit Vector3
Or cleanest
Create new class/struct with min max average fields, and use this class in list as its type.

1 Like

Is there anything else you plan to do with this list, other than calculate min/max/avg?

Because a list of values will just continuously grow consuming more memory as time progresses. And then you’ll have to loop that list in the end. This is consuming unnecessary memory and pushing all the work to the end.

Instead you can just hold a running tally. Moment to moment you just calculate the information necessary.

float min = float.PositiveInfinity; //start large so first value is always <
float max = float.MinimumInfinity; //start small so first value is always >
float average = 0f;
float tally = 0f; //running tally... this would be the sum of the entire array
int count = 0; //running count... this would be the length of the array

void CalculateNextValue(float value)
{
    tally += value;
    count++;
    average = tally / count;
    if(value < min) min = value;
    if(value > max) max = value;
}

Now you only need 20 bytes of memory to store all information, and it’s already calculated and ready to access at any moment.

2 Likes

This sounds really interesting :slight_smile:

Did this work what i have in mind? And where i need to call the functions? In Update?

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CountAllValues : MonoBehaviour {

    public Game gamescene;

    // Use this for initialization
    void Start () {
       
    }
   
    // Update is called once per frame
    void Update () {

       
       
    }
   
    public float minHr = 0;
    public float maxHr = 0;
    public float avrHr = 0;

    float minHrCount = float.PositiveInfinity; //start large so first value is always <
    float maxHrCount = float.PositiveInfinity -1; //start small so first value is always > //float max = float.MinimumInfinity; <<-- gives me an error
    float hraverage = 0f;
    float hrtally = 0f; //running tally... this would be the sum of the entire array
    int hrcount = 0; //running count... this would be the length of the array

   void CalculateHeartrate(float hr)
    {
        hrtally += hr;
        hrcount++;
        hraverage = hrtally / hrcount;
        if (hr < minHrCount) minHrCount = hr;
        if (hr > maxHrCount) maxHrCount = hr;
    }

    void saveHrValues()
    {
        minHr = minHrCount;
        maxHr = maxHrCount;
        avrHr = hraverage;
    }

    //next counting and calculating

    public int minPwr = 0;
    public int maxPwr = 0;
    public int avrPwr = 0;

    int minPwrCount = int.PositiveInfinity; //start large so first value is always <
    int maxPwrCount = int.PositiveInfinity - 1; //start small so first value is always > //float max = float.MinimumInfinity; <<-- gives me an error
    int pwraverage = 0;
    int tally = 0; //running tally... this would be the sum of the entire array
    int pwrcount = 0; //running count... this would be the length of the array

    void CalculatePower(int pwr)
    {
        tally += pwr;
        pwrcount++;
        pwraverage = tally / pwrcount;
        if (pwr < minPwrCount) minPwrCount = pwr;
        if (pwr > maxPwrCount) maxPwrCount = pwr;
    }

    void SavePwrValues()
    {
        minPwr = minPwrCount;
        maxPwr = maxPwrCount;
        avrPwr = pwraverage;
    }
   
}

:

I don’t know, was this in your mind? Only you know, no one else.

Where you want to add a new value to the calculation.

1 Like

so i dont need to call the functions every second?

Is up to you. You are the designer. If you need every second, or even every frame, then do it. If not, maybe once at initialization is enough. But typically you call method, whenever you require it to be called.

2 Likes

Ok what is with the 2 errors?
//float max = float.MinimumInfinity; <<-- gives me an error no available
and
int minPwrCount = int.PositiveInfinity; is not definied for int.

is it minValue and maxValue?

Can you please post full error?
You can paste it in between (spoiler)(/spoiler) tags. Just replace round brackets with square brackets [,]

Edit
Alwyas check autocomplete, for suggestions.
float takes

float.PositiveInfinity
float.NegativeInfinity
1 Like

Thank you i take look after work today.

the errors are with int:

[Assets/OwnScripts/CountAllValues.cs(60,27): error CS0117: int' does not contain a definition for PositiveInfinity’]

and float error:
[ Assets/OwnScripts/CountAllValues.cs(60,31): error CS0117: float' does not contain a definition for MinimumInfinity’ ]

minValue maxValue is available!?

Right, follow my very previous post. Int does not have this method tho.
What makes you think there PositiveNegative exists? Where you got it from?

1 Like

I think that with ints it not infinity, its max/minValue (-2,147,483,647 to 2,147,483,647)

2 Likes

That would.make sense, since int don’t take E engineering notation as float does.

1 Like

Sometimes i think i am overwhelmed with my problems :slight_smile:
I have tried a list first, because i understand what happen there, but now i have the problem that i have float and int in the code: (Dont get me wrong but i need to understand what i do there) So for learning i started
here:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CountAllValues : MonoBehaviour
{
    public BicyclePowerSim bicycleSim;
    public PowerMeterDisplay powermeterDisplay;
    public HeartRateDisplay heartRateDisplay;

    public List<float> power; // List of power data of user's whole training session
    public List<float> heartrate; // List of heartrate data of user's whole training session
    public List<float> cadence; // List of cadence per mintue data of the user's whole training session
    public List<float> speed; // List of speed data of the user's whole training session

    public float avgPower;  // The avg power calculated at the end of the session from the data that was collected
    public float avgHeartrate; //The avg heartrate calculated at the end of the session from the data that was collected
    public float avgCadence; // The avg cadence per minute calculated at the end of the session from the data that was collected
    public float avgSpeed; // The avg speed calculated at the end of the session from the data that was collected
    public float time; // Total time of user's training session

  
    public double distance; // Total distance the user has travelled
    public bool finished = false; // Used to determine when the user has finished the session
    private float datarate; // Rate that the data is to be collected
    private float deltatime; // Time since the last set of data was collected

     public GameObject Summary; // Used to display the training summary
    //public Data_Logging datalog; // Used to log the summary to file later....

    // These are text objects used for displaying the session data on the training summary
    public Text TimeText;
    public Text AveragePowerText;
    public Text AverageHeartrateText;
    public Text AverageCadenceText;
    public Text AverageSpeedText;

       // Used for initialisation
    void Start()
    {
  
        power = new List<float>();
        heartrate = new List<float>();
        cadence = new List<float>();
        speed = new List<float>();
        time = 0;
        // Used to only get data every 1 second
        datarate = 1f;
        deltatime = 0.0f;
    }

    // Update is called once per frame
    void Update()
    {
        // Only update once the user has started and stop when the user has finished
        if (bicycleSim.speed >= 1 && !finished)
        {
            time = time + Time.deltaTime;
          
            deltatime += Time.deltaTime;
            // Used to update the training data every 1 second
            if (deltatime >= datarate)
            {
                UpdateSummary();
                deltatime = 0.0f;
            }
        }

    }

    // Used to check if the user has completed a split
  
    public void CheckisFinished()
    {
      
        //check if the end has been reached
        if ( bicycleSim.speed <= 5 && !finished)
        {
            finished = true;
          
            // Used to display the training summary
            DisplaySummary();

        }
    }
  
    // Used to update the data needed for the training summary
    public void UpdateSummary()
    {
        // Update the lists wih the appropriate data from the ANT device and bicycleSim Component (speed)
        power.Add(powermeterDisplay.instantaneousPower);  //this is an int
        heartrate.Add(heartRateDisplay.heartRate); //this is a float
        cadence.Add(powermeterDisplay.instantaneousCadence); //this is an int
        speed.Add(bicycleSim.speed); //this is a float
    }

    // Used to get the average from the lists of data collected
    public float GetAverage(List<float> list) //also get a list for the int values
    {
        float avg = 0;
        int count = 0;
        // Iterate through the entire list
        foreach (float j in list)
        {
            avg += j;
            count++;
        }
        avg = avg / count;
        return avg;
    }

    // Used to calculate and display the training summary
    public void DisplaySummary()
    {
        // Get the averages of the lists of data
        avgPower = GetAverage(power);
        avgHeartrate = GetAverage(heartrate);
        avgCadence = GetAverage(speed);
        avgSpeed = GetAverage(cadence);

      
      
    // Convert total time and average split time into timespan objects
        TimeSpan ts = TimeSpan.FromSeconds(time);
      
        // Update the text for all the data needed for the training summary and display in the right format
        TimeText.text = "Time:              " + ts.Minutes + ":" + ts.Seconds + "." + ts.Milliseconds;
        AveragePowerText.text = "AveragePower: " + Math.Round(avgPower, 2).ToString("F");
        AverageHeartrateText.text = "AverageHeartrate: " + Math.Round(avgHeartrate, 2).ToString("F");
        AverageCadenceText.text = "AverageCadence: " + Math.Round(avgCadence, 2).ToString("F");
        AverageSpeedText.text = "AverageSpeed:   " + Math.Round(avgSpeed, 2).ToString("F");
      
        Summary.SetActive(true);
      
         }
  
}
}

I will try to add the same with int in that script, because when i have finished true and speed <= 5 the data is not shown actually. I think its because float / int values. (But also i get no error) :slight_smile:

If i understand this, then i can use your solution, because the list is so f*** big :frowning:

Use Debug.Log for assisting.
Also use VS break points to narrow problem down. Start with smaller list to analyse problem.

1 Like

Here’s an implementation of all that without lists:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class CountAllValues : MonoBehaviour
{
 
    #region Fields
 
    public BicyclePowerSim bicycleSim;
    public PowerMeterDisplay powermeterDisplay;
    public HeartRateDisplay heartRateDisplay;

    public GameObject Summary; // Used to display the training summary
    //public Data_Logging datalog; // Used to log the summary to file later....

    // These are text objects used for displaying the session data on the training summary
    public Text TimeText;
    public Text AveragePowerText;
    public Text AverageHeartrateText;
    public Text AverageCadenceText;
    public Text AverageSpeedText;
 
    public double distance; // Total distance the user has travelled
    public bool finished = false; // Used to determine when the user has finished the session
 
    private const float DATARATE = 1f; // Rate that the data is to be collected
    private float _ticker; // Time since the last set of data was collected

    private Statistics power = new Statistics();
    private Statistics heartRate = new Statistics();
    private Statistics cadence = new Statistics();
    private Statistics speed = new Statistics();
    private float time; // Total time of user's training session

    #endregion

    #region CONSTRUCTOR

       // Used for initialisation
    void Start()
    {
        time = 0f;
        // Used to only get data every 1 second
        _ticker = 0.0f;
    }
 
    #endregion
 
    #region Methods

    // Update is called once per frame
    void Update()
    {
        // Only update once the user has started and stop when the user has finished
        if (bicycleSim.speed >= 1 && !finished)
        {
            time = time + Time.deltaTime;
       
            _ticker += Time.deltaTime;
            // Used to update the training data every 1 second
            if (deltatime >= DATARATE)
            {
                UpdateSummary();
                deltatime = 0.0f;
            }
        }
    }

    // Used to check if the user has completed a split

    public void CheckisFinished()
    {
        //check if the end has been reached
        if ( bicycleSim.speed <= 5 && !finished)
        {
            finished = true;
       
            // Used to display the training summary
            DisplaySummary();
        }
    }

    // Used to update the data needed for the training summary
    public void UpdateSummary()
    {
        // Update the statistics state
       //type of number passed in is fine, since we use double there is enough precision for both int/float
        power.CalculateNextValue(powermeterDisplay.instantaneousPower);
        heartrate.CalculateNextValue(heartRateDisplay.heartRate);
        cadence.CalculateNextValue(powermeterDisplay.instantaneousCadence);
        speed.CalculateNextValue(bicycleSim.speed);
    }

    // Used to calculate and display the training summary
    public void DisplaySummary()
    {
        // Convert total time and average split time into timespan objects
        TimeSpan ts = TimeSpan.FromSeconds(time);
   
        // Update the text for all the data needed for the training summary and display in the right format
        TimeText.text = "Time:              " + ts.Minutes + ":" + ts.Seconds + "." + ts.Milliseconds;
        AveragePowerText.text = "AveragePower: " + Math.Round(power.average, 2).ToString("F");
        AverageHeartrateText.text = "AverageHeartrate: " + Math.Round(heartRate.Average, 2).ToString("F");
        AverageCadenceText.text = "AverageCadence: " + Math.Round(cadence.Average, 2).ToString("F");
        AverageSpeedText.text = "AverageSpeed:   " + Math.Round(speed.Average, 2).ToString("F");
   
        Summary.SetActive(true);
    }
 
    #endregion

    #region Special Types

    public class Statistics
    {
        //changed to double for greater range and precision
        public double min = double.PositiveInfinity; //start large so first value is always <
        public double max = double.MinimumInfinity; //start small so first value is always >
        public double average = 0d;
        public double tally = 0d; //running tally... this would be the sum of the entire array
        public int count = 0; //running count... this would be the length of the array
       
        public void CalculateNextValue(double value)
        {
            tally += value;
            count++;
            average = tally / count;
            if(value < min) min = value;
            if(value > max) max = value;
        }
    }
 
    #endregion
 
}

(note I just wrote this here in the browser… no promises I didn’t make a typo)

I would argue that basing the finish of the game on the speed of the bike is very error prone.

Why has the speed dropped that low? Is it because it crossed a finish line? Why not signal then that the race(?) has finished?

1 Like

Hm when i set finished to true, then Summar.SetActive is not working!?
And i have no averages, thats weird.
I have done the if ( bicycleSim.speed <= 5 && !finished) for checking in editor
also when i only try with if (!finished) it do not set summary to true and no avg values!?
hmmm.

I have fixed the typos: and i can only use minValue maxValue!?
the inifnity stuff give me the same error:

public class CountAllValues : MonoBehaviour
{

    #region Fields

    public BicyclePowerSim bicycleSim;
    public PowerMeterDisplay powermeterDisplay;
    public HeartRateDisplay heartRateDisplay;

    public GameObject Summary; // Used to display the training summary
                               //public Data_Logging datalog; // Used to log the summary to file later....

    // These are text objects used for displaying the session data on the training summary
    public Text TimeText;
    public Text AveragePowerText;
    public Text AverageHeartrateText;
    public Text AverageCadenceText;
    public Text AverageSpeedText;

    public double distance; // Total distance the user has travelled
    public bool finished = false; // Used to determine when the user has finished the session

    private const float DATARATE = 1f; // Rate that the data is to be collected
    private float _ticker; // Time since the last set of data was collected

    private Statistics power = new Statistics();
    private Statistics heartRate = new Statistics();
    private Statistics cadence = new Statistics();
    private Statistics speed = new Statistics();
    private float time; // Total time of user's training session

    #endregion

    #region CONSTRUCTOR

    // Used for initialisation
    void Start()
    {
        time = 0f;
        // Used to only get data every 1 second
        _ticker = 0.0f;
    }

    #endregion

    #region Methods

    // Update is called once per frame
    void Update()
    {
        // Only update once the user has started and stop when the user has finished
        if (bicycleSim.speed >= 1 && !finished)
        {
            time = time + Time.deltaTime;

            _ticker += Time.deltaTime;
            // Used to update the training data every 1 second
            if (time >= DATARATE)
            {
                UpdateSummary();
                time = 0.0f;
            }
        }
    }

    // Used to check if the user has completed a split

    public void CheckisFinished()
    {
        //check if the end has been reached
        if (bicycleSim.speed <= 2 && !finished)
        {
            finished = true;

            // Used to display the training summary
            DisplaySummary();
        }
    }

    // Used to update the data needed for the training summary
    public void UpdateSummary()
    {
        // Update the statistics state
        //type of number passed in is fine, since we use double there is enough precision for both int/float
        power.CalculateNextValue(powermeterDisplay.instantaneousPower);
        heartRate.CalculateNextValue(heartRateDisplay.heartRate);
        cadence.CalculateNextValue(powermeterDisplay.instantaneousCadence);
        speed.CalculateNextValue(bicycleSim.speed);
    }

    // Used to calculate and display the training summary
    public void DisplaySummary()
    {
        // Convert total time and average split time into timespan objects
        TimeSpan ts = TimeSpan.FromSeconds(time);

        // Update the text for all the data needed for the training summary and display in the right format
        TimeText.text = "Time:              " + ts.Minutes + ":" + ts.Seconds + "." + ts.Milliseconds;
        AveragePowerText.text = "AveragePower: " + Math.Round(power.average, 2).ToString("F");
        AverageHeartrateText.text = "AverageHeartrate: " + Math.Round(heartRate.average, 2).ToString("F");
        AverageCadenceText.text = "AverageCadence: " + Math.Round(cadence.average, 2).ToString("F");
        AverageSpeedText.text = "AverageSpeed:   " + Math.Round(speed.average, 2).ToString("F");

        Summary.SetActive(true);
    }

    #endregion

    #region Special Types

    public class Statistics
    {
        //changed to double for greater range and precision
       // public double min = double.PositiveInfinity; //start large so first value is always <
      //  public double max = double.MinimumInfinity; //start small so first value is always >
        public double min = double.MaxValue; //start large so first value is always <
        public double max = double.MinValue; //start small so first value is always >
        public double average = 0d;
        public double tally = 0d; //running tally... this would be the sum of the entire array
        public int count = 0; //running count... this would be the length of the array

        public void CalculateNextValue(double value)
        {
            tally += value;
            count++;
            average = tally / count;
            if (value < min) min = value;
            if (value > max) max = value;
        }
    }

    #endregion

}

but still summary GameObject go not active
WoW Double can handle float and int (Nobody told me) Great to know

What error?

I assure you, double.PositiveInfinity and NegativeInfinity exist:
https://docs.microsoft.com/en-us/dotnet/api/system.double.positiveinfinity?view=netframework-4.7.2

I’m not certain what you mean by “handle float and int”… but I don’t want to go and think I gave you some panacea by introducing you to double in this way.

Really, on the technical level, it’s like this.

All numeric types represent numbers in different ways and will have different limitations/behaviour as a result.

A float is a 32-bit signed floating point number. By float it means that the radix (decimal point) is “floating”… basically it’s binary scientific notation fit into 32-bits.

A double is really just a 64-bit version of a float (float is also known as a ‘single’). I guess you could say a double can “handle” a float, since they follow the same rule sets.

Both floats and doubles have this behaviour in that instead of having a true “range” of number it can represent. float can not actually represent all numbers between MinValue and MaxValue… rather instead it can represent numbers of a specific significant value range within those boundaries. For example…

6877900000 - valid float
6077900001 - NOT a valid float

A float has 24 significant binary bits of precision… which is about 6-9 digits in decimal/base10.
A double has 53 significant binary bits of precision… which is about 15-17 digits in decimal

int on the other hand can only represent whole numbers. No fractions. And it can represent all whole number between its MinValue and MaxValue.

Thing is if you look at the int’s maxvalue of ‘2147483647’ and minvalue of ‘-2147483648’, both of which are 31 bits long. You’ll see that 31 < the 53 sig values available to double. So thusly an int will fit within the behavioural constraints of double.

A double can therefore represent the full range of int values.

But it comes at the limitations of double… such as its arithmetic performance differences (usually hardware dependent), as well as the fact floats/doubles suffer from what is called float error. So just because double can fit an int, it doesn’t mean you should just use a double.

It’s just that if you have to cast an int to a double… it’s no problem. You don’t have to worry about the cast failing.

3 Likes