O
O
Ostap2021-09-21 22:16:37
JavaScript
Ostap, 2021-09-21 22:16:37

How to optimize calculations in a functional component?

Wrote a component that calculates different values. The data arrives constantly through the websocket and is calculated in useEffect.
Here is what the customer sent me:

will fall because the memory will run out and will not be able to work as required in the task, for weeks or months

, and when a lot of data is collected, it will take a very long time to count

and should give an answer within a second after pressing the button

How can the component be optimized?

...
export const Statistics = () => {
    const [data, setData] = useState([])
    const [average, setAverage] = useState('')
    const [calculationTime, setCalculationTime] = useState(0)
    const [standardDeviation, setStandardDeviation] = useState('')
    const [modes, setModes] = useState('')
    const [median, setMedian] = useState('')
    const [statistic, setStatistic] = useState(0)
    const [isConnectionStarted, setIsConnectionStarted] = useState(false)
    const ws = useRef(null);
  
// ----------вычисления ----------

    const getAverageScores = () => {
        const initialValues = {avg: 0, n: 0};
        return data.reduce(function (initialValues, data) {
            const avg = (data.value + initialValues.n * initialValues.avg) / (initialValues.n + 1)
            const n = initialValues.n + 1
            return {
                avg,
                n
            }
        }, initialValues).avg;
    }

    const getStandardDeviation = () => {
        const total = 0;
        const averageScore = getAverageScores()
        const squaredDeviations = data.reduce(function (total, data) {
            const deviation = data && data.value - averageScore;
            const deviationSquared = deviation * deviation;

            return total + deviationSquared;
        }, total);

        return Math.sqrt(squaredDeviations / data.length);
    }


    const getModes = () => {
        let frequency = [];
        let maxFreq = 0;
        let modes = [];

        for (let i in data){
            frequency[data[i].value] = (frequency[data[i].value] || 0) + 1;
            if (frequency[data[i].value] > maxFreq) {
                maxFreq = frequency[data[i].value];
            }
        }
        for (let j in frequency) {
            if (frequency[j] === maxFreq) {
                modes.push(j);
            }
        }
        if ( maxFreq === 1) {
            return ' - '
        }
        if (!modes.length) {
            return ' - '
        }
        return modes.join(', ')
    }

    const getMedian = () => {
        data.sort(function (a, b) {
            return a.value - b.value;
        });
        const half = Math.floor(data.length / 2);

        if (data.length % 2) {
            return data[half].value;
        }
        return (data[half - 1].value + data[half].value) / 2;

    }

    const calculation = () => {

        const startTime = new Date().getTime();
        setAverage(getAverageScores())
        setStandardDeviation(getStandardDeviation())
        setModes(getModes())
        setMedian(getMedian())
        const finishTime = new Date().getTime();
        setCalculationTime(finishTime - startTime)
    }


// ----------Зыпускаю вычисления при обновлении данных ----------

    useEffect(() => {
        if (data.length) {
            calculation()
        }
    }, [data])

// ----------Коннекчусь к серверу----------
    const start = () => {
        ws.current = new WebSocket('wss://trade.trademux.net:8800/?password=1234');
        if (isConnectionStarted) {
            ws.current.close()
            setData([])
            setStatistic(0)

        }
        ws.current.onopen = () => {
            console.log('ws opened');
            setIsConnectionStarted(true)
        }
        ws.current.onclose = () => {
            setIsConnectionStarted(false)
        }
        ws.current.onmessage = e => {
            const message = JSON.parse(e.data);
            setData(oldData => [...oldData, message])
        }

    }
// ----------Расконекчиваюсь с сервером ----------
    useEffect(() => {
        return () => {
            ws.current.close();
        };
    }, []);

    const getStatistic = () => {
        setStatistic(prevState => prevState + 1)
    }

    return (
        <>
            <div className="quote-statistics">
                <Item  value={Boolean(statistic) && average}/>
                <Item  value={Boolean(statistic) && standardDeviation}/>
                <Item value={Boolean(statistic) && modes}/>
                <Item value={Boolean(statistic) && median}/>
                <Item value={Boolean(statistic) && calculationTime}/>
            </div>
            <div className="quote-statistics_ctrl">
                <Btn func={start} title='START'/>
                <Btn func={getStatistic} title='STAT'/>
            </div>
        </>
    )
}

Answer the question

In order to leave comments, you need to log in

2 answer(s)
W
WbICHA, 2021-09-21
@WblCHA

What a wonderful code, the blood from the eyes went already from the first function. I'll sign it, I won't have enough for more.

const getAverageScores = () => {
        const initialValues = {avg: 0, n: 0};
        return data.reduce(function (initialValues, data) {
            const avg = (data.value + initialValues.n * initialValues.avg) / (initialValues.n + 1)
            const n = initialValues.n + 1
            return {
                avg,
                n
            }
        }, initialValues).avg;
    }

Masterpiece.
0. Take an array of data directly from the state, instead of passing in arguments and dragging the function outside the component.
1. Useless declaration of initialValues ​​that worsens readability.
2. function instead of arrow.
3. Trigger if you click on the duplication of the variable name in the lower scope.
4. Simply masterpiece mathematical operations. On each (!) iteration, multiply, divide, count the length of the array and create a new object. And all this instead of one concise line:
return data.reduce((acc, { value }) => acc + value, 0) / data.length;

A
Alexandroppolus, 2021-09-22
@Alexandroppolus

In addition to the analysis of functions made by WbICHA (severe, but fair), I’ll throw in more purely Reakt points:
1) functions with calculations to evict the hell out of the component, pass data to them.
2) A bunch of states (average... median) - nothing more than blasphemous derivative states to be reforged into useMemo, with the useEffect containing calculation() thrown into the trash.
3) isConnectionStarted looks like an extra one. At least it's not a state. Perhaps it should be changed to ref
4) The purpose of statistic is not clear. I suspect that it is not needed, since it duplicates data.length (a sign of data availability).
5) It would be nice to work with a websocket in a separate class with logic. Store an instance of the class, for example, in ref.
6) in the start function there is some heresy inside ifa

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question