Find two highest values of variables

I want to find the two highest values of a number of variables.
Example: $number1 $number2 $number3 $number4
IF sum of $number1 and $number2 is higher than sum of all the rest, show A
IF sum of $number1 and $number3 is higher than sum of all the rest, show B
IF sum of $number3 and $number4 is higher than sum of all the rest, show Z
… And so on.
Now I guess I could try and put it all in an if structure, but that would be too long and complicated to figure out.
I also tried making a list using the documentation.
<<set $list to […]>>
<<set $sorted to $list.sort().reverse()>>
<<set $firstplace to $list[0]>>

But I wouldn’t know how to get the two highest values out of it after sorting.
(Sugarcube 2.21.0)

Others will have to respond to the Twine question, but I noticed a bug in the math:

Suppose the numbers are 4, 5, 1, and 6 in that order. The two highest values are 5 and 6. However, 4+5 is greater than 1+6, so the if-condition is satisfied.

Sorting, as you also suggest, is a much better approach! Though I’ll leave it to the Twine experts to provide the syntax.

1 Like

hi there!
This code should do what you need and it also fixes the problem mentioned by Linus with the IF logic:

<<set $datamap to [
	{ varName: '$number1', value: 15 },
	{ varName: '$number2', value: 8 },
	{ varName: '$number3', value: 19 },
	{ varName: '$number4', value: 4 }
]>>

<<set _largestIndex to 0>>
<<set _secondIndex to 0>>
<<for _i=1; _i < $datamap.length; _i++>>
 <<if $datamap[_i].value > $datamap[_largestIndex].value>>
  <<set _secondIndex to _largestIndex>>
  <<set _largestIndex to _i>>
 <</if>>
<</for>>

<<set _twoBiggestVars to [$datamap[_largestIndex].varName, $datamap[_secondIndex].varName]>>

<<if _twoBiggestVars.contains('$number1') and _twoBiggestVars.contains('$number2')>> Show A

<<elseif _twoBiggestVars.contains('$number1') and _twoBiggestVars.contains('$number3')>> Show B

<<elseif _twoBiggestVars.contains('$number3') and _twoBiggestVars.contains('$number4')>> Show Z 
<</if>>
1 Like

If you store the values in an array of objects, instead of four variables, like this:

<<set $numbers = [ { number: 1, value: 20 }, { number: 2, value: 10 },
{ number: 3, value: 30 }, { number: 4, value: 50 } ]>>

then you can sort them like this:

<<set $numbers.sort(function(a, b) { return b.value - a.value; })>>

Now the first two objects in the array will be the two highest values. You can determine which numbers they are by checking $numbers[0].number and $numbers[1].number. (For more information, see the .sort() method for arrays.)

Assuming the order doesn’t matter, then to make things a bit simpler for testing each of the combinations, you could do this:

<<set _tops = [$numbers[0].number, $numbers[1].number]>>
<<set _tops.sort(function(a, b) { return a - b; })>>

That sorts the top two numbers into numerical order.

Now, if you only have four numbers, you’d just need to test the _tops variable for the values of [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], and [3, 4] to get all 6 combinations of the two highest values. Like this:

<<if (_tops[0] == 1) && (_tops[1] == 2)>>show A<</if>>
<<if (_tops[0] == 1) && (_tops[1] == 3)>>show B<</if>>
...
<<if (_tops[0] == 3) && (_tops[1] == 4)>>show Z<</if>>

If you want to avoid having a bunch of <<if>>s, you could create an object on the setup object like this in your StoryInit passage:

<<set setup.output = {}>>
<<set setup.output.v12 = "show A">>
<<set setup.output.v13 = "show B">>
...
<<set setup.output.v34 = "show Z">>

Now, instead of all of those <<if>> statements, you could just do this:

<<print setup.output["v" + _tops[0] + _tops[1]]>>

Please let me know if you have any questions on that.

Hope that helps! :slight_smile:

P.S. The current version of SugarCube is v2.29.0, so you might want to download and install that.

1 Like

Thank you, that seems to work, but only with set values. How would you implement changable variables?
So that (in another passage) I can do <<set $number1 += 1>>
Can you, for example, put variables in the brackets or something? Or somehow make it count up?

if you need to do this check multiple time over the course of the story, I would recommend putting either of the solutions above inside a widget so you can call it easily.

In that case, whenever the widget is called you can initialize the datamap with the values stored in your variables, like this:

<<set $number1 to 5>>
<<set $number2 to 8>>
<<set $number3 to 2>>
<<set $number4 to 3>>

<<set $datamap to [
	{ varName: '$number1', value: $number1 },
	{ varName: '$number2', value: $number2 },
	{ varName: '$number3', value: $number3 },
	{ varName: '$number4', value: $number4 }
]>>

This would also work with the solution given by HiEv as well, just put your variables inside the ‘value’ attribute inside his ‘$numbers’ datamap

1 Like

If you’re referring to my example, then if the numbers are still sorted by the number property, then you’d just do this:

<<set $numbers[0].value += 1>>

and that would increase the value property of the first object in the $numbers array (array indexes always start at zero), which would be for the object with the value of 1 in the number property.

If the objects in the array aren’t sorted by the number property, then you’ll need to sort them numerically first:

<<set $numbers.sort(function(a, b) { return a.number - b.number; })>>
<<set $numbers[0].value += 1>>

Basically, as long as the objects are sorted numerically like that, then:
$numbers[0].value is equivalent to $number1 in your old code
$numbers[1].value is equivalent to $number2 in your old code
$numbers[2].value is equivalent to $number3 in your old code
$numbers[3].value is equivalent to $number4 in your old code

Simply replace one with the other in your code, and it should work the same.

I’d just keep the numbers in the default numerical order like that most of the time, and then, when you need the top two values, just combine all of the above code like this:

/* Sorts $numbers by value, with the biggest value being first: */
<<set $numbers.sort(function(a, b) { return b.value - a.value; })>>

/* Uses the sorted array to determine which numbers were the two largest: */
<<set _tops = [$numbers[0].number, $numbers[1].number]>>

/* Sorts those two numbers from smallest to largest: */
<<set _tops.sort(function(a, b) { return a - b; })>>

/* Displays the correct output based on the numbers with the two largest values: */
<<print setup.output["v" + _tops[0] + _tops[1]]>>

/* Restores the $numbers array to the default numerical order: */
<<set $numbers.sort(function(a, b) { return a.number - b.number; })>>

(You can remove the comment lines if you want.)

That sorts $numbers by value, finds the two largest values, displays the appropriate message, and then changes $numbers back to being sorted numerically again.

Hope that helps! :slight_smile:

1 Like

It didn’t really matter since both worked, but I ended up using the code from Hacd latest reply. Thank you for all the help (and pointing to the latest version).

Just as an FYI, but Hacd’s code won’t work properly when $number1 has the largest value. For example:

<<set $datamap to [
	{ varName: '$number1', value: 15 },
	{ varName: '$number2', value: 8 },
	{ varName: '$number3', value: 5 },
	{ varName: '$number4', value: 4 }
]>>

<<set _largestIndex to 0>>
<<set _secondIndex to 0>>
<<for _i=1; _i < $datamap.length; _i++>>
 <<if $datamap[_i].value > $datamap[_largestIndex].value>>
  <<set _secondIndex to _largestIndex>>
  <<set _largestIndex to _i>>
 <</if>>
<</for>>

In that example, $datamap[_i].value will never be larger than $datamap[_largestIndex].value, which means that both _secondIndex and _largestIndex will still be pointing to index 0 after the <<for>> loop is done.

As a fix, you’ll want to do this instead:

<<if $datamap[0].value >= $datamap[1].value>>
	<<set _largestIndex to 0>>
	<<set _secondIndex to 1>>
<<else>>
	<<set _largestIndex to 1>>
	<<set _secondIndex to 0>>
<</if>>
<<for _i=2; _i < $datamap.length; _i++>>
	<<if $datamap[_i].value > $datamap[_largestIndex].value>>
		<<set _secondIndex to _largestIndex>>
		<<set _largestIndex to _i>>
	<<elseif $datamap[_i].value > $datamap[_secondIndex].value>>
		<<set _secondIndex to _i>>
	<</if>>
<</for>>

Now that code should work for all cases.

Enjoy! :slight_smile:

1 Like

You are right, I hadn’t noticed as it didn’t gave an error.

Thank you for all the help!