Jeffrey
Jeffrey

Reputation: 35

Convert nested For Loop with .map

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

Answers (5)

Hitmands
Hitmands

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.

const 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

Daniel Duong
Daniel Duong

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

user5536315
user5536315

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

de3
de3

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

Thejool
Thejool

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

Related Questions