Reduce method or another method

Hello all,
If I have an array with only one value for each key and I want to add the length of every three values together, how can this be accomplished?

I tried using reduce, but this seems to only be able to add two values at a time. I’ve tried concat so I can then add all of the contaminated values by getting the length of the new single value, but I need to contact every 3 values in a given array regardless of the values length or the array’s length. Since I’m unable to use functions inside of for loops, I can’t use a for loop to loop through each key via a for of loop or a for each loop.

I don’t want the coding answer written out for me, I just want some ideas of what methods I should be looking at. if I write a function, that function will be a sub-function of my current function. If possible, I’d like an idea that could allow for more than every 3 values based on the length of the word given to my current loop that converts the letters to morse code values. Meaning I can’t just say if less than 3 add. I also can’t say if .length is less than 3 … 6… 9… etc. because the value provided may be greater than a 3 letter word.

Here’s an example array:

// every 3 values of this array  have been given extra space for readability only
charArr = [ '--.',  '..',  '-.',      '--..',  '.',  '-.',      '--.',  '..',  '--.',      '--',  '...',  '--.' ]

An example solution:

//every 3 values lengths are added together, sum is added to new array
result = [ 7,  7, 8,  8 ]

Thanks in advance.

Simple answer: Break the array into chunks of 3, then map over the chunks with a function that should be obvious.

You don’t need reduce for this, but it’s not too much harder to do with reduce. Your starting accumulator value should be something like {sums: [], currentSum: 0, currentIndex: 0}. Now imagine how you’d do it in a loop with those as variables, and update them in your reducer function instead.

1 Like

Great, thanks. So let’s say that I have an input array that is 12 words long with 4 letters each instead of 4 words and 3 letters each. Would taking the total length / letter count length (I assume arr.length / arr[i].length ) would suit that need. Does that sound right?

I would urge you to start by solving the problem of chunking the original array into an array of pieces of the size you require. at that point you could map and reduce.

I’m not really following you there. My algorithm would be that you’d add the string’s length to currentSum, then when currentIndex reaches 2 , reset them both to 0, and append the result to the sums array. There’s probably other ways, that just came off the top of my head. Think of the “accumulator” as your algorithm’s entire state, what you’d normally use variables for.

Thing is, reduce can do anything at all, but it’s definitely not the first tool you should reach for. I’d recommend the chunk-and-map idea by far. reduce comes into its own when you start composing reducer functions. Redux works entirely on that idea.

Thanks to both of you. This is what I’ve landed on to group the array into word length. This allows for any word length to work as well. Let me know what you all think about my choice.

Now I’m off to total each group and push those results into another array. I’ll work on doing that in the process of the function below as well.

function group (arr, num) {
    charArr.slice(0);
    while(charArr[0]) {
      sums.push(charArr.splice(0,num));
    }
  }
  
  group(charArr,charArr[0].length);
  return sums;

FYI - here’s my final code:

let words = ["gin", "zen", "gig", "msg"];
let code = [".-","-...","-.-.","-..",".","..-.","--.","....","..",".---","-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-","...-",".--","-..-","-.--","--.."];
let charArr = [];
let sums = [];
let vals = [];
let val = [];
let groups =[];
let result = [];

function longestMorseCodeWords (strArr) {
    
  for (let i=0; i < words.length; i++) {
    for (let j=0; j < words[0].length; j++) {
      var charNum = words[i].charCodeAt(j)-96;
        charArr.push(code[charNum-1]);
    }
  }
  
  vals = Object.values(charArr);
  for (let k = 0; k < vals.length; k++) {
   val = [vals[k].length];
   sums.push(val);
  }
  sums.slice(0);
  while (sums[0]) {
    groups.push(sums.splice(0,vals[0].length));
  }
  
  for (let m = 0; m < groups.length; m++) {
        let sum = Number(groups[m][0]) + Number(groups[m][1]) + Number(groups[m][2]); 
        result.push(sum);
  }
  return result;
}

longestMorseCodeWords(words);

I feel like there must be a faster and easier way to accomplish this. Although I will leave my code as-is for now, I’d really appreciate some feedback on betters ways to produce my result. Thanks in advance.

I wrapped my solution below in spoiler tags. Let me know if you want me to walk you through the algorithm starting with the code (i.e. look at the spoiler) or if you want to work through it from scratch.

const MORSE_CODE = {
    a: ".-", b: "-...", c: "-.-.", d: "-..", e: ".", f: "..-.", g: "--.", h: "....",
    i: "..", j: ".---", k: "-.-", l: ".-..", m: "--", n: "-.", o: "---", p: ".--.", q: "--.-", 
    r: ".-.", s: "...", t: "-", u: "..-", v: "...-", w: ".--", x: "-..-", y: "-.--", z: "--..",
}

const words = ["gin", "zen", "gig", "msg"];

words.map(word => word.split('').map(c => MORSE_CODE[c].length).reduce((a,b) => a + b, 0))

Using @chuckadams solution as inspiration, here is a version with one less map method:

words.map(word => [ ...word ].reduce((sum, char) => sum + MORSE_CODE[char].length, 0))
1 Like

After thinking about it a bit more, if you already know the lengths of the Morse code characters, then you could do:

const MORSE_CODE_LENGTHS = {
  a: 2, b: 4, c: 4, d: 3, e: 1,
  f: 4, g: 3, h: 4, i: 2, j: 4,
  k: 3, l: 4, m: 2, n: 2, o: 3,
  p: 4, q: 4, r: 3, s: 3, t: 1,
  u: 3, v: 4, w: 3, x: 4, y: 4,
  z: 4
};

const words = ["gin", "zen", "gig", "msg"];

words.map(word => [ ...word ].reduce((sum, char) => sum + MORSE_CODE_LENGTHS[char], 0))

I prefer keeping each HOF step a single operation myself, makes it easier to test incrementally – that’s composability for you. I’d forgotten about using spread to split a string though. Well-golfed :clap:

@chuckadams and @RandellDawson
That’s really sweet guys. I’ll keep my really long code for now because I spent so much time on it and I’m pretty new to JS (studied it 10 years ago and have lost most) : )
However, it’s amazing how something so complex can be summarized so much.

I’d love the walkthrough for understanding, but after I completed all of what I shared above, I tested my program and failed! I reread my instructions and discover that I need to return the words, but sorted by the length of the Morse code and then alphabetically. So, I’m working on doing that now.

Here’s what I have so far:

let codeSort = result.sort((a,b) => {
    if (a > b) { return -1 }
    else if (a < b) { return 1 }
    else if (a === b) {
      for (let n = 0; n < result.length; n ++) {
       var sliced = result[0].slice(result[n][1], result[n][1]);
       sliced.sort((a,b) => a - b); 
      }
    }
  });
  return result;
}

It sort of works. It first sorts by the numbers, then seems to sort alphabetically, except the last two (7 values) are out of order. alphabetically. Then I need to return only the words. Which I’ll work on next.

Just an FYI update. I’ve broken down my code for this problem to one for loop. I not familiar enough with map to do what you all suggested, although I’m excited to learn it : ).

After the for loop, I’ll do a sort, and push final results and I should be done. Any thoughts on my new approach? Any good free tutorials out there for undersanding how to properly use map?

  for (let i=0; i < words.length; i++) {
    for (let j=0; j < words[0].length; j++) {
      var charNum = words[i].charCodeAt(j)-96;
        charArr.push(code[charNum-1]);
    }
    groups = {
      word: words[i],
      code: charArr[i] + charArr[i+1] + charArr[i+2]
    };
    result.push(groups);
    codeLength = Object.values(result[i].code).length;
    result[i].cLength = codeLength;
  }

Any time you loop over an array and add items to another array in a loop, and you don’t have any conditions that skip items or add additional ones, you can probably use map() instead.

But let’s take a higher-level perspective (functional programming does that a lot) and we’ll forget about what map does and concentrate on what it is instead.

For our example, let’s say you want to get the lengths of all the words in a string:

const str = "My hovercraft is full of eels"
const words = str.split(' ')
const len = x => x.length

const result = []
for (word of words) 
  result.push(len(word))

I’m assuming all the above is familiar to you. The code is very imperative: “start result as an empty array; then for each word, add it to result”. Let’s express this as map instead:

result = words.map(len)

Other than being shorter, it says what result is, not how to go about it. Result is a mapping from words to some new array, using the len function. It’s always a 1-1 mapping, which is to say that each word in words is mapped from a string to a number via len, and there will always be the same number of elements. Notice that it doesn’t go through the mechanics of building the array or calling the function or anything, it just says “this is the mapping of words to an array with the len function applied”

If you remember high school maths, you might remember that “mapping” terminology to describe mathematical functions – same thing here, a function maps its input its output. The map method on arrays maps an array to another array, using the mapping function.

So anytime you have an array and want to get another out of it where the elements all correspond to each other, you’re looking for Array.map

Thanks for that explanation. So to clarify, I still need to perform split and create the function I want to apply before using map right? The only difference is that instead of a fo of loop or any for loop, I’m using a one-line map method, right?

Also, you said,

It’s always a 1-1 mapping, which is to say that each word in words is mapped from a string to a number via len , and there will always be the same number of elements

So if I didn’t want to return a number and instead I wanted to return one element of a multi-dimensional (lets say a 4x4 array), how do I map something like that? Do I first need to create a forEach function or something of the sort?

Right about having to do the split and defining the function, though you can do the whole thing in one expression with no variables. I’ll leave it to you to figure that out .

As for why, let’s break it down into types. map is a method on Array (Array.prototype really, but meh) so you have to call it on an Array instance, like so: Again, use something like repl.it to follow along.

console.log(  [1,2,3,4,5].map(x => x + 1) )

That means you have to turn a string into an array somehow. The default way strings are treated like arrays is by character. You want by words. No prob, it’s the .split() method on strings.

"my hovercraft is full of eels".split().map(...)

Aside from being a neat compact one-liner… Look ma, no variables! (parameters don’t count). This means you can take any part of a chain of pure functional:

foo
    .filter(baz)
    .map(bar)
    .flatMap(mumble)
    .reduce(frotz)

Or whatever combination of pure higher order functions (HOFs) you like, and “cut” it at any point to do more with:

filtered =  foo.filter(baz).map(bar)

console.debug(filtered) // haha i lied

processed = filtered.flatMap(mumble).reduce(frotz)

This is to say functional programs are compositional. Functional expressions are the ultimate legos: you take take expressions, pull them apart and glue them together wherever you want.

That’s what makes FP better than clever one-liners.

To get a single value from an Array, you can use Array.reduce(). For example, the infamous sum example:

[1, 3, 66, -8, 2].reduce((a, b) => a+b, 0)

Notice how that whole array is reduced to a single value. For the matrix thing, it depends on your algorithm, but you’d probably need two reduce calls. It’s a bit twisty, so in real life I’d personally just use a loop to do it.

To access one element, you’d just do mat[row][col]. No FP gadgetry needed.

Oh, and I just can’t recommend this guy enough. He explains stuff well, and he’s a character and a half.