7

I have written a javascript function for analyzing the biggest drop in an array. But one little issue is still there. As the max value, I always get max value from my hole array and not from my drop.

Example: Array: [100,90,80,120]

The biggest drop would be between 100 and 80. So max must be 100, and min 80. My function always returns the highest value from the whole array. in my case 120

function checkData(data) {
  let max = 0
  let min = 0
  let drop = 0

  for (let i = 0; i < data.length; i++) {
      if (max < data[i]) {
          max = data[i] //?
      } else {
          let tempDrop = max - data[i]
          drop = Math.max(tempDrop, drop)
          min = max - drop
      }
  }
  return [max, min, drop]
}

I want to get the chronological correct biggest delta from left to right Demo Image

Pommesloch
  • 492
  • 5
  • 17
  • 1
    When you say drop, do you mean difference? – cmprogram Nov 26 '18 at 12:40
  • @cmprogram, I mean the biggest delta between max an min. – Pommesloch Nov 26 '18 at 12:41
  • 1
    is this array sorted? what do you mean by saying drop? – yossico Nov 26 '18 at 12:41
  • `return [max, min, drop]` What are max, min and drop? – Wais Kamal Nov 26 '18 at 12:42
  • what would be the drop output to [100,110,120,70,60,50,90,300,200]? – yossico Nov 26 '18 at 12:42
  • What's that `Math.max` doing? It's only operating on one number... – Andy Nov 26 '18 at 12:53
  • @yossico the drop would be 100. In this case, my function is correct. But if I have this drop in the middle of my dataset, (like [100,110,120,300,200,70,60,50,90,400]) and as the last data a value higher than 300 than my function would be calculated with last 400 and min 50. But the 400 is after the current drop. Correct would be max = 300 and min = 50, because there is the biggest delta – Pommesloch Nov 26 '18 at 12:54
  • @Andy thanks for this hint, i edit this line – Pommesloch Nov 26 '18 at 12:55
  • A drop is the biggest delta between 2 neighbor values? because if it's the biggest delta between max and min than drop = max-min (there is no biggest) – yossico Nov 26 '18 at 12:57
  • @yossicoI have added an image to my post. I think this would you guys help to understand what need – Pommesloch Nov 26 '18 at 13:08
  • Do you have more examples, especially for edge cases? – Salman A Nov 26 '18 at 13:25
  • @SalmanA This data comes from a shares API. That is why I need this chronological from left to right delta. The shares course is crashed here from 100 to 80. This is a -20% drop. In my attempt, I miss the chronological aspect. The user doesn't want to know how the shares price is dropped from now no past – Pommesloch Nov 26 '18 at 13:28
  • So, You mean, you want the *highest* difference between a higher value and a lower value where index of higher value is lower than that of the lower value ?! – Anand Undavia Nov 26 '18 at 13:30
  • @AnandUndavia Yes! The lower value is correctly returned from my function but the higher not – Pommesloch Nov 26 '18 at 13:34
  • Have you attempted to find a library that does this for you? I'd imagine that a stats library would have a couple tools you could string together to get the result. The best code is code you don't have to write. – jpmc26 Nov 26 '18 at 18:10
  • Also, you function is buggy. If you're going to do `max < data[i]`, `max` should be initialized with `-infinity`. – jpmc26 Nov 26 '18 at 18:12

6 Answers6

6

Your loop should keep track of the current drop and compare it to the previous largest drop. You can do this by tracking indexes:

function checkData(data) {
  let bestDropStart = 0
  let bestDropEnd = 0
  let bestDrop = 0
  let currentDropStart = 0
  let currentDropEnd = 0
  let currentDrop = 0
  for (let i = 1; i < data.length; i++) {
    if (data[i] < data[i - 1]) {
      // we are dropping
      currentDropEnd = i
      currentDrop = data[currentDropStart] - data[i]
    } else {
      // the current drop ended; check if it's better
      if (currentDrop > bestDrop) {
        bestDrop = currentDrop
        bestDropStart = currentDropStart
        bestDropEnd = currentDropEnd
      }
      // start a new drop
      currentDropStart = currentDropEnd = i
      currentDrop = 0
    }
  }
  // check for a best drop at end of data
  if (currentDrop > bestDrop) {
    bestDrop = currentDrop
    bestDropStart = currentDropStart
    bestDropEnd = currentDropEnd
  }
  // return the best drop data
  return [data[bestDropStart], data[bestDropEnd], bestDrop]
}

console.log(checkData([100, 90, 80, 120]))
console.log(checkData([100, 90, 80, 120, 30]))
console.log(checkData([70, 100, 90, 80]))
console.log(checkData([100, 90, 80, 120, 30, 50]))

You can also do it by just keeping the start and end values for the current and best drops, but my preference would be to explicitly track the indexes. It just seems clearer (easier to debug and maintain) to me that way.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
2

You need to iterate the array once (O(n)) and keep a running count of min and max values and:

  1. Reset it every time the value increases w.r.t. previous value
  2. But note them down if the difference is greater than previously noted values

function checkData(array) {
  var currentmax = array[0];
  var currentmin = array[0];
  var overallmax = array[0];
  var overallmin = array[0];
  var i;
  for (i = 1; i < array.length; i++) {
    if (array[i] <= array[i - 1]) {
      // value is same or decreased
      currentmin = array[i];
    } else {
      // check if previous iteration was the largest
      if (currentmax - currentmin > overallmax - overallmin) {
        overallmax = currentmax;
        overallmin = currentmin;
      }
      // restart
      currentmax = array[i];
      currentmin = array[i];
    }
  }
  // check if last iteration was the largest
  if (currentmax - currentmin > overallmax - overallmin) {
    overallmax = currentmax;
    overallmin = currentmin;
  }

  return [overallmax, overallmin, overallmax - overallmin];
}
console.log(checkData([100, 90, 80, 120]));
console.log(checkData([100, 90, 80, 120, 200, 100, 90]));
console.log(checkData([10, 100, 50, 50, 10, 100, 50, 50, 1]));
Salman A
  • 262,204
  • 82
  • 430
  • 521
  • This fails for [100, 90, 80, 120, 200, 100 ,90]. The return must be max = 200, min = 90 and drop = 110 – Pommesloch Nov 26 '18 at 13:50
  • 1
    the logic is sound (I think) and i just need to update the max min everytime the values change. See revised answer. – Salman A Nov 26 '18 at 13:55
2

It sounds like you'd like the returned max and min to be the same ones used in calculating the largest drop rather than the overall max and min. In that case just add an additional variable to store those when drop is updated. Replace

drop = Math.max(tempDrop, drop)

with an if statement (pseudocode):

if current_drop is greater than stored_drop:
  stored_drop = current_drop
  stored_max_min = current max and min

then return the additional stored max and min.

גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
0

You need to keep track of two things:

  • The maximum value you have up to an element i.
  • The minimum value you have from an element i to the end of the list.

Then you just need to find the drop for each index and select which one is biggest.

Here is the code:

function maxDrop (data) {
  if (!data || data.length === 0) {
    return;
  }
  const max = [];
  const min = [];
  for (let i = 0; i < data.length; i++) {
    const left = data.slice(0, i);
    const right = data.slice(i, data.length);
    max.push(Math.max.apply(null, left));
    min.push(Math.min.apply(null, right));
  }
  let maxDrop = max[0] - min[0];
  let maxValue = max[0];
  let minValue = min[0];
  for (let j = 0; j < data.length; j++) {
    const drop = max[j] - min[j];
    if (maxDrop < drop) {
      maxDrop = drop;
      maxValue = max[j];
      minValue = min[j];
    }
  }
  return [maxValue, minValue, maxDrop];
}

console.log(maxDrop([100,90,80,120]))
console.log(maxDrop([100,110,120,300,200,70,60,50,90,400]))
console.log(maxDrop([100,90,80,120,30]))
kakamg0
  • 1,096
  • 5
  • 12
  • This fails for `[100, 90, 80, 120, 30]`. The output is `[100, 80, 20]` but it should be `[120, 30, 90]`. – Ted Hopp Nov 26 '18 at 13:37
  • @TedHopp Fixed it, I was excluding the last element when searching for the minimum value. Thanks! – kakamg0 Nov 26 '18 at 13:56
0

To add one more to the mix.. A possible approach is to create all the drop - ranges first, than get the one with the largest drop

A version with reduce:

function checkData(data) {
let dropInd = 1,
   mx = data.reduce((arr,val, ind) => {
 if(ind && val > data[ind-1])dropInd++;         
 arr[dropInd] = arr[dropInd] || {max:val};
 arr[dropInd].min = val;
 arr[dropInd].drop = arr[dropInd].max - val;
 return arr;
}, []) //after reduce, islands are created based on 'dropInd'  
  .sort((v1,v2)=>v2.drop - v1.drop) //sort by drop descending
  [0]; //the first value now contains the maximum drop  

  return [mx.max,mx.min,mx.drop];
}



console.log(checkData([100, 90, 80, 120]));
console.log(checkData([100, 90, 80, 120, 200, 100 ,90]));
console.log(checkData([200,100, 90, 80, 120,  100 ,90]));
console.log(checkData([200,120,190, 90, 80, 120,  100 ,90]));
Me.Name
  • 12,259
  • 3
  • 31
  • 48
-1

This works exactly as intended (as per your expected output), as shown in the following code snippet.

The issue must be in how you are sending your data as an array.

alert(checkData([100, 90, 80, 120]));

function checkData(data) {
  let max = 0
  let min = 0
  let drop = 0

  for (let i = 0; i < data.length; i++) {
    if (max < data[i]) {
      max = data[i] //?
    } else {
      let tempDrop = max - data[i]
      drop = Math.max(tempDrop)
      min = max - drop
    }
  }
  return [max, min, drop]
}
// Output : 120, 80, 20

However, if what you are looking for is a Max, a Min, and then the biggest difference, i.e. between the the max and min, which in your given example would be 40, then you can simply do this.

alert(checkData([100, 90, 80, 120]));

function checkData(data) {

  let max = data.reduce(function(a, b) {
    return Math.max(a, b);
  });
  let min = data.reduce(function(a, b) {
    return Math.min(a, b);
  });

  let drop = max-min;

  return [max, min, drop];
  
}
// Output : 120, 80, 40

I only mention this as I don't know why you've said you expect the biggest difference to be 20, given 100 and 80, when you have 120 and 80 in the same array, meaning the biggest difference is 40.

cmprogram
  • 1,854
  • 2
  • 13
  • 25
  • Thanks, but the correct return must be max = 100, min = 80 and drop = 20. Look at the graphic that I added to my post – Pommesloch Nov 26 '18 at 13:11
  • But 100 isn't the max... If you don't want the 120 to be included, just don't include it in the array... – cmprogram Nov 26 '18 at 13:13
  • This data comes from a shares API. That is why I need this chronological from left to right delta. The shares course is crashed here from 100 to 80. This is a -20% drop. In my attempt, I miss the chronological aspect – Pommesloch Nov 26 '18 at 13:18
  • 1
    OP wants the max and min associated with the largest drop, where a drop is a sequence of descending data values. The largest drop is from 100 to 80 (indexes 0 through 2). – Ted Hopp Nov 26 '18 at 13:34