Functional style

I am practicing writing code in a functional style, but it turns out badly, please help me rewrite this task

Alice and Bob take turns playing a game, with Alice starting first.
There are n stones in a pile. On each player’s turn, they can remove a stone from the pile and receive points based on the stone’s value. Alice and Bob may value the stones differently .
You are given two integer arrays of length n , aliceValues and bobValues . Each aliceValues[i] and bobValues[i] represents how Alice and Bob, respectively, value the ith stone.
The winner is the person with the most points after all the stones are chosen. If both players have the same amount of points, the game results in a draw. Both players will play optimally . Both players know the other’s values.
Determine the result of the game, and:

  • If Alice wins, return 1 .
  • If Bob wins, return -1 .
  • If the game results in a draw, return 0 .

Example :
Input: aliceValues = [1,3], bobValues = [2,1] Output: 1 Explanation: If Alice takes stone 1 (0-indexed) first, Alice will receive 3 points. Bob can only choose stone 0, and will only receive 2 points. Alice wins.

let stoneGameVI = function (aliceValues, bobValues) {
    let winAlice = 0;
    let winBob = 0;
    let arr = [];
    for (let i = 0; i < aliceValues.length; i++) {
        arr.push([aliceValues[i] + bobValues[i], aliceValues[i], bobValues[i]]);

    }
    arr.sort((a, b) => b[0] - a[0]);
    for (let i = 0; i < aliceValues.length; i += 2) {
        winAlice += arr[i][1]
        if (arr[i + 1] !== undefined) {
            winBob += arr[i + 1][2]
        }
    }

    if (winAlice > winBob) return 1
    if (winAlice === winBob) return 0
    if (winAlice < winBob) return -1
};

What do you mean by turning out badly? Not giving correct results? Bad performance?

the code is working correctly, but I need to do this task in functional style

What do you mean by that?


that’s what functional style means

Again, same question. What do you mean by you want to write it in a functional style? What do you understand this to mean? What have you tried?

1 Like

originally, I did in an imperial approach. Now I want to do in a declarative approach (the code consists only of functions)

It’s imperative, not imperial. Anyway, I can clearly see that the code is in a function. Why isn’t that functional? It’s deterministic, has no side effects. I give it two arrays, it returns one of three numbers.

What I am trying to get at is: what do you think a functional approach is? Just saying “oh it’s just using functions”, you’re already doing that :man_shrugging:t3: What do you understand this to mean, and what have you tried?

1 Like

I would also say that “the code consists only of functions” is not possible. Even as hyperbole that is a bit extreme. I can’t imagine a real world app where everything is functions. You can extract a lot of logic into functions so your main code is more readable (a good thing), but at some level you’re going to need something besides functions.

Also, something like this looks odd to me, at least as an example:

const removeIndex = function(nums, ind) {
  nums = nums.slice(0);
  nums.splice(ind, 1);
  return nums;
}

Why not:

const removeIndex = function(nums, ind) {
  return nums.slice(0).splice(ind, 1);
}

or even:

const removeIndex = (nums, ind) => nums.slice(0).splice(ind, 1);
1 Like

These FCC lessons are pretty good IMO:
Functional Programming | freeCodeCamp.org

Yeah, I think you just need to do it for a while. I think that paradigms become more clear as you build more and more things. It’s good to be curious about these things, but you also can’t obsess about them. I think the most important thing is to write good code. Now, understanding paradigms can be a good tool for that, but it is just a tool. I would suggest just to learn how to write good code. If you want to learn about the different paradigms, then that’s great, but your coding knowledge may not be at a place where you can really understand them. Don’t force it - just learn how to write good code and don’t worry too much about labels and categories and paradigms. You can read about those on the side and one day it will just click.

Those are my thoughts anyway.

I’m not sure if I understand the problem, but why don’t you wrap pieces of code that belong together in separate functions? Example for the last three lines:

    if (winAlice > winBob) return 1
    if (winAlice === winBob) return 0
    if (winAlice < winBob) return -1

… would become…

function getResult(scoreA, scoreB){
    return scoreA > scoreB ? 1 : scoreA < scoreB ? -1 : 0
}

let stoneGameVI = function (aliceValues, bobValues) {

    /* --other code--  */

    return getResult(winAlice, winBob)
}

Then do something similar for both for loops, and your main function would be quite clean.

you understood me correctly. I don’t know what to do with the loops. what methods should i use?

Then you need to study the prototype methods. FCC has a section on the prototype methods and there are A LOT of materials online discussing them, between blogs, forums, youtube videos.

Let’s see how you can use a map to replace the first for loop. The goal here is create a new array of arrays like

[[5, 3, 2], [4, 2, 2], [6, 2, 4]]

from two arrays

[3, 2, 2]
[2, 2, 4]

When we use a map function, we have one array to deal with. For example, if you have an array [1, 2, 3, 4, 5] and convert it [2, 4, 6, 8, 10], then you can use map like this

const vals = [1, 2, 3, 4, 5];
const doubles = vals.map(x => 2 * x);

If you don’t like an arrow function, then we can write like

const doubles = vals.map(function(x) { return 2 * x});

or

function doubleIt(x) {
  return 2*x;

const doubles = vals.map(doubleIt);

The key is that a map function will take another function (I call it a mapper or converter function) as its argument. The above sample shows three different ways to pass a function that doubles a value.
What a map function will do is take the converter function and applies this function to every element of an array. So, in the sample code, variable x refers to every element of vals. The effect of this vals.map is in effect

[doubleIt(1), doubleIt(2), doubleIt(3), doubleIt(4), doubleIt(5)]

To apply this to your situation, we hit a problem. There are two arrays to deal with, not one array. But an array could be nested. From two arrays

[3, 2, 2]
[2, 2, 4]

suppose we can magically transform them into one (nested) array like

[[3,2], [2,2], [2,4]]

We’ll discuss this ‘magic’ at the end, but for now, let’s assume we have this nested array. Let’s call this pairwise array pairs. Consider this code

const arr = pairs.map(el => [el[0] + el[1], el[0], el[1]]);

What is el referring to? Every element of pairs, which is [3,2], [2,2], and [2,4]. When el is [3, 2], el[0] is 3 and el[1] is 2. The expression [el[0] + el[1], el[0], el[1]] will result in [5, 3, 2].
This array arr is what you want in your code.

How do we get pairs? The map function can actually take a converter function with two arguments. The first refers to the element and the second the index position of that element in the array. pairs can be computed like this

const pairs = aliceValues.map( (e, i) => [e, bobValues[I]);
const arr =  pairs.map(el => [el[0] + el[1], el[0], el[1]]);

I used the pairs array to highlight the idea of map, but you can actually bypass the pairs array.

const arr = aliceValues.map( (e, i) => [e + bobValues[I], e, bobValues[I]);

Your second for loop is a bit more involved. You probably want to get comfortable with map and filter before attempting to convert the second for loop using map/filter/reduce.

thanks for the explanation. could you help with the second cycle?

Here’s one way to do it.

You start with an array of triples

[t0, t1, t2, …, tn-1]

where ti is a triplet like [5, 3, 2]. You use filter two times to get two arrays

[t0, t2, t4, ...]
[t1, t3, t5, ...]

The first array is the values for Alice and the second is for Bob.
Then, you use reduce once to get the total for Alice from [t0, t2, t4, …] and another reduce to get the total for Bob from [t1, t3, t5, …].

Just to bear in mind: if this is for leetcode, it is not particularly common afaics to use functional programming to solve the challenges. With what @twotani is saying, it may be clean but it’s also multiplying the number of times you loop over the code. You aren’t removing loops from your code, you’re adding loops to your code: the methods may be declarative, but they’re powered by loops. It’s not like an FP language like OCaml where the compiler is designed to optimise code written that way. Or an imperative language like Rust which can take stuff that looks functional but that compiles down to extremely efficient code.

It’s important to know, and normally functional-style code in JS often tends to be a bit cleaner and easier to test and reason about. It’s very practical, but it’s not necessarily what you’d use for leetcode stuff.

So for example:

  • you have two arrays of values representing the two “players”
  • you can “zip” those together into pairs, so for [1, 3] and [2, 1] you get [[1, 2], [3, 1]]
  • you then sort those descending by the sum of the pairs, giving [[3, 1], [1, 2]]
  • you then produce a single pair [a, b] of which a is the sum of the first values of every even pair and b is the sum of the second values of every odd pair.
  • you then compare the two values in this single pair: a > b is an a win, a < b is a b win, a === b is a draw.

So then for example:

/**
 * `pipe`, given a start value, then lets me apply a function
 * that takes any number of functions as an argument and
 * applies them in order. However, these functions can only
 * take a single argument. Therefore I am going to need to curry
 * the functions I use in the pipe:
 */
let pipe = (startVal) => (...fns) => fns.reduce((v, f) => f(v), startVal);
/**
 * It is easier to deal with this if there is one array rather
 * that two, so I want to zip the two arrays together. Given
 * two arrays of identical length, I want to produce an array of
 * tuples, like:
 *
 *     a = [1, 3]
 *     b = [2, 1]
 *     zipped = [[1, 2], [3, 1]]
 */
let zipValues = ([playerAValues, playerBValues]) => playerAValues.map((v, i) => [v, playerBValues[i]]);
/**
 * JS' `sort` method mutates the array it is applied to. I
 * want a new array, so that I can pipe the result into
 * the next function.
 */
let sortBy = (sortFn) => (arr) => [...arr.sort(sortFn)];
/**
 * JS' `reduce` method can be wrapped and curried to enable
 * production of a single-arity function that runs the reduction
 * I require:
 */
let reduce = (reducerFn) => (acc) => (arr) => arr.reduce(reducerFn, acc);

/** --------------------------------------------------------- */

/**
 * Zipping the two players' arrays of stone valuations gives
 * an array of pairs. I want to order those based on the sum,
 * descending. So the comparison function:
 */
let compareSummedPairDesc = ([a1,b1], [a2, b2]) => (a2 + b2) - (a1 + b1);
/**
 * `sortBy` is a curried function: if I call it with just the
 * first argument (the sort function), then it returns a new
 * function that accepts an array. So I can give it the
 * comparison function to generate a function that sorts the
 * way I want:
 */
let sortBySumPair = sortBy(compareSummedPairDesc);

/**
 * With `reduce`, I want to produce a tuple which is the total
 * for each player once they have picked their stones. As they
 * alternate turns, I can do this by summing every other value.
 * Player A takes the even values as they go first -- 0, 2, 4, etc.
 * Player B takes the odd values as they go second -- 1, 3, 5, etc.
 */
let sumAlternateTupleValues = (([accA, accB], [currentA, currentB], i) => i % 2 === 0 ? [accA + currentA, accB + 0] : [accA + 0, accB + currentB]);
/**
 * And again, like with `sortBy`, I can generate a function that
 * takes the input array of tuples:
 */
let produceSumOfAlternateTupleVals = reduce(sumAlternateTupleValues)([0, 0]);
/**
 * The winner is calculated thusly:
 * - given a tuple of [playerAMaxScore, playerBMaxScore]
 * - if playerAMaxScore is the greater of the two, return 1
 * - if they are equal, return 0
 * - if playerBMaxScore is the greater of the two, return -1
 */
let pickWinner = ([playerAMaxScore, playerBMaxScore]) => playerAMaxScore > playerBMaxScore ? 1 : playerAMaxScore < playerBMaxScore ? -1 : 0;



/** --------------------------------------------------------- */

let stoneGame = (playerAValues, playerBValues) =>
	pipe(
		[playerAValues, playerBValues]
	)(
    zipValues,
    sortBySumPair,
    produceSumOfAlternateTupleVals,
    pickWinner,
  );

That’s (roughly, was done quite quickly) something that kinda hopefully gets across how you might approach it functionally. There are libraries that help – lodash/fp and Ramda for example.

Just using JS methods directly:

function stoneGame (aVals, bVals) {
  const [a, b] = aVals
    .map((aVal, i) => [aVal, bVals[i]])
    .sort(([a1, b1], [a2, b2]) => (a2 + b2) - (a1 + b1))
    .reduce(([a, b], [aVal, bVal], i) => i % 2 === 0 ? [a + aVal, b + 0] : [a + 0, b + bVal], [0, 0]);

  return  a > b ? 1 : a < b ? -1 : 0;
}
2 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.