Reputation: 35
I am trying to convert a nested for-loop with .map() to make it more functional. My goal to create a simple card deck. This is my working code:
generateDeck() {
const card = (suit, value) => {
return suit + value;
}
const suits = ["S", "H", "D", "C"];
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
for (let s = 0; s < suits.length; s++) {
for (let v = 0; v < values.length; v++) {
this.deck.push(card(suits[s], values[v]))
}
}
}
This is how far I came:
generateDeck() {
const card = (suit, value) => {
return suit + value;
}
const suits = ["S", "H", "D", "C"];
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
let deckNew = suits.map(s => {
values.forEach(v => { return s + v });
});
}
I can't get it to work. How would you avoid the nested for-loop in a clean more functional way?
Thanks!
Upvotes: 0
Views: 2332
Reputation: 14179
So, from a functional programming perspective, a good approach in my opinion is to be as declarative as possible. You don't really need to hadncraft this algorithm since outputting "a new list out of the two supplied by creating each possible pair from the lists" is a very common operation, for which a lot of implementations already exist.
xprod
for instanceconst suits = ["♠︎", "♣︎", "♥︎", "♦︎"];
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
const createDeck = R.pipe(
R.xprod,
R.map(R.join('')),
);
console.log(
createDeck(suits, values),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>
Upvotes: 0
Reputation: 1104
I think you should use reduce
const card = (suit) => (value) => suit + value;
const suits = ['S', 'H', 'D', 'C'];
const values = ['7', '8', '9', '10', 'J', 'D', 'K', 'A'];
const deck = suits.reduce((prev, suit) => {
const suitedCard = card(suit);
return [...prev, ...values.map(suitedCard)];
}, []);
console.log(deck);
Upvotes: 1
Reputation:
You cannot do this with map
, because you have to combine two arrays. But you shouldn't use flatmap
either, because there is no dependency of values. What you need is an applicative functor:
const arrAp = tf => xs =>
arrFold(acc => f =>
arrAppend(acc)
(arrMap(x => f(x)) (xs)))
([])
(tf);
const arrMap = f => xs =>
xs.map((x, i) => f(x, i));
const liftA2 = ({map, ap}) => f => tx => ty =>
ap(map(f) (tx)) (ty);
const arrLiftA2 = liftA2({map: arrMap, ap: arrAp});
const arrFold = f => init => xs => {
let acc = init;
for (let i = 0; i < xs.length; i++)
acc = f(acc) (xs[i], i);
return acc;
};
const arrAppend = xs => ys =>
xs.concat(ys);
// library code ^^^
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
const suits = ["S", "H", "D", "C"];
const createDeck = values => suits => [values, suits];
console.log(
arrLiftA2(createDeck) (values) (suits));
Upvotes: 3
Reputation: 2000
You can nest maps, that will result in a 2-dimensional array. Then using flatMap
you can convert it to a simple array.
flatMap flattens multidimensional arrays. e.g:
[[1,2], [3,4]].flatMap(x => x)
will give [ 1, 2, 3, 4 ]
So someting like this (I didn't test it)
generateDeck() {
const card = (suit, value) => {
return suit + value;
}
const suits = ["S", "H", "D", "C"];
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
return suits.flatMap(
suit => values.map(value => card(suit, value))
);
}
Upvotes: 1
Reputation: 562
You are right that you can use map
to make it more "functional". Map returns a new array while forEach only loops through the items. You can also omit the curly brackets to directly return a value.
generateDeck() {
const card = (suit, value) => suit + value;
const suits = ["S", "H", "D", "C"];
const values = ["7", "8", "9", "10", "J", "D", "K", "A"];
let deckNew = suits.map(s => values.map(v => s + v));
return deckNew;
}
Upvotes: 1