Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
I am trying to solve a challenge on jshero.net. The challenge is :
Write a function list that takes an array of words and returns a string by concatenating the words in the array, separated by commas and - the last word - by an 'and'. An empty array should return an empty string.
Example: list(['Huey', 'Dewey', 'Louie']) should return 'Huey, Dewey and Louie'
Best solution I could come up with was this :
function list (a){
let myList = a.join(' and ' )
return myList
}
The problem is that I cannot figure out how to make the function to return just the "and" word just after the second name and not before it. Does anyone know how to solve this?
............................................................
Update : I found an alternative :
function list(arr){
let myL= arr.slice(0,2);
let myLi= myL.join(' , ')
let myL2 = arr.slice(2,3);
let myLi2= myL2.join(' and ');
let myList = myLi + myLi2
if (arr.length <=2){
return arr.join(' and ')} else {
return myList}
}
I am close, the outcome is "Huey , DeweyLouie" but somehow is still not adding the "add" word into it. Anyone got an ideea?
From my comment:
list.reduce((acc, cur, i) => acc + cur + (i < list.length - 2 ? ', ' : i < list.length -1 ? ', and ' : ''), '');
This is a more advanced example, since it uses some JavaScript specific functions that are less "general programming."
Here's an explanation:
list
.reduce(
(acc, cur, i) => // start of an arrow function (implicit 'return')
acc + cur + // + as in string concatenation
(
i < list.length - 2 ? // what to concatenate? comma or 'and'?
', ' : // if it's before the 2nd last index, use a comma.
i < list.length - 1 ? // else, double check if it's the very last one.
', and ' : // if it is, use ', and '.
'' // else-else (the space AFTER the last index), put nothing.
),
''); // starter argument for 'reduce'
The Array.reduce() function takes another function as its argument. That function will be run on each element in the list in order. On each subsequent element, the first argument (acc) will be whatever the function returned on the last item (in the case of the first item, it'll be the supplied starting arugment). This let's you gradually tack the each element (cur) onto the value that follows through the array.
Using ternaries lets you make it a one liner. If you wanted it to be more verbose:
const list = ['Huey', 'Dewey', 'Louie']
const output = list.reduce(function(acc, cur, i) {
let str = acc;
str += cur;
if (i < list.length - 2) {
str += ', ';
} else if (i < list.length - 1) {
str += ', and ';
}
return str;
}, '');
console.log(output);
If you're new to programming in general, this may seem a bit overwhelming (I know reduce did for me, too). But, if you're familiar with programming practices, this is a good example of a JS-specific way to do things.
Cheers, and good luck :-)
Interesting use case. In fact I thought it would be a nice addition to my tagged templates library!
So we have two separators:
delim: ', '
delimlast: ' and '
And a few cases:
[] -> ''
['Huey'] -> 'Huey'
['Huey', 'Dewey'] -> 'Huey and Dewey'
['Huey', 'Dewey', 'Louie'] -> 'Huey, Dewey and Louie'
Here's one way to approach this:
const list =
(xs, delim = ', ', delimlast = ' and ') =>
!Array.isArray(xs) ? xs
: xs.length === 0 ? ''
: xs.length === 1 ? xs[0]
: xs.length === 2 ? xs[0] + delimlast + xs[1]
: xs.slice(0, -1).join(delim) + delimlast + xs[xs.length-1];
list('Huey'); // 'Huey'
list([]); // ''
list(['Huey']); // 'Huey'
list(['Huey', 'Dewey']); // 'Huey and Dewey'
list(['Huey', 'Dewey', 'Louie']); // 'Huey, Dewey and Louie'
I am the author of a small library of tagged templates (yet another one!) and your question gave me the idea for another one. Thanks!
import {list} from '<DETAILS CAN BE FOUND IN MY PROFILE IF YOU ARE INTERESTED>';
const guys = ['Huey', 'Dewey', 'Louie'];
list`Hey ${guys}!`;
//=> "Hey Huey, Dewey and Louie!"
One approach will be to iterate over till the last -1 array values and add "," to them.
When reached the last-1 of the list add the "and" outside the loop
Related
So I've been working on this coding challenge for about a day now and I still feel like I haven't scratched the surface even though it's suppose to be Easy. The problem asks us to take a string parameter and if there are exactly 3 characters (not including spaces) in between the letters 'a' and 'b', it should be true.
Example: Input: "maple bread"; Output: false // Because there are > 3 places
Input: "age bad"; Output: true // Exactly three places in between 'a' and 'b'
Here is what I've written, although it is unfinished and most likely in the wrong direction:
function challengeOne(str) {
let places = 0;
for (let i=0; i < str.length; i++) {
if (str[i] != 'a') {
places++
} else if (str[i] === 'b'){
}
}
console.log(places)
}
So my idea was to start counting places after the letter 'a' until it gets to 'b', then it would return the amount of places. I would then start another flow where if 'places' > 3, return false or if 'places' === 3, then return true.
However, attempting the first flow only returns the total count for places that aren't 'a'. I'm using console.log instead of return to test if it works or not.
I'm only looking for a push in the right direction and if there is a method I might be missing or if there are other examples similar to this. I feel like the solution is pretty simple yet I can't seem to grasp it.
Edit:
I took a break from this challenge just so I could look at it from fresh eyes and I was able to solve it quickly! I looked through everyone's suggestions and applied it until I found the solution. Here is the new code that worked:
function challengeOne(str) {
// code goes here
str = str.replace(/ /g, '')
let count = Math.abs(str.lastIndexOf('a')-str.lastIndexOf('b'));
if (count === 3) {
return true
} else return false
}
Thank you for all your input!
Here's a more efficient approach - simply find the indexes of the letter a and b and check whether the absolute value of subtracting the two is 4 (since indexes are 0 indexed):
function challengeOne(str) {
return Math.abs(str.indexOf("a") - str.indexOf("b")) == 4;
}
console.log(challengeOne("age bad"));
console.log(challengeOne("maple bread"));
if there are exactly 3 characters (not including spaces)
Simply remove all spaces via String#replace, then perform the check:
function challengeOne(str) {
return str = str.replace(/ /g, ''), Math.abs(str.indexOf("a") - str.indexOf("b")) == 4;
}
console.log(challengeOne("age bad"));
console.log(challengeOne("maple bread"));
References:
Math#abs
String#indexOf
Here is another approach: This one excludes spaces as in the OP, so the output reflects that. If it is to include spaces, that line could be removed.
function challengeOne(str) {
//strip spaces
str = str.replace(/\s/g, '');
//take just the in between chars
let extract = str.match(/a(.*)b/).pop();
return extract.length == 3
}
console.log(challengeOne('maple bread'));
console.log(challengeOne('age bad'));
You can go recursive:
Check if the string starts with 'a' and ends with 'b' and check the length
Continue by cutting the string either left or right (or both) until there are 3 characters in between or the string is empty.
Examples:
maple bread
aple brea
aple bre
aple br
aple b
ple
le
FALSE
age bad
age ba
age b
TRUE
const check = (x, y, z) => str => {
const exec = s => {
const xb = s.startsWith(x);
const yb = s.endsWith(y);
return ( !s ? false
: xb && yb && s.length === z + 2 ? true
: xb && yb ? exec(s.slice(1, -1))
: xb ? exec(s.slice(0, -1))
: exec(s.slice(1)));
};
return exec(str);
}
const challenge = check('a', 'b', 3);
console.log(`
challenge("maple bread"): ${challenge("maple bread")}
challenge("age bad"): ${challenge("age bad")}
challenge("aabab"): ${challenge("aabab")}
`)
I assume spaces are counted and your examples seem to indicate this, although your question says otherwise. If so, here's a push that should be helpful. You're right, there are JavaScript methods for strings, including one that should help you find the index (location) of the a and b within the given string.
Try here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#instance_methods
I am trying to understand this recursion. I know how recursion works in factorial function but when it gets to this complex recursion like this I am confused. The most confusing part to me is this code
str.split('').map( (char, i) =>
permutations( str.substr(0, i) + str.substr(i + 1) )map( p => char + p))
First, with "abc", say, it will split into ["a","b","c"] and go through the map function, then go through the second map function to wrap each return with a, b, c, respectively. However, I am very confused at the recursion part.
I thought the first recursion in "a" with value of str as "abc" will return "bc", and second recursion with str value of "bc" will return "c", and so on.
But when I just ran this code to see a clear recursion, it returns
[ [ [ 'c' ], [ 'b' ] ], [ [ 'c' ], [ 'a' ] ], [ [ 'b' ], [ 'a' ] ] ]
This is most confusing to me. I just can't see how this recursion returns these values. Can anyone go more in detail through how this work, like illustrating your thought process step by step?
I am a visual learner. Thank you for your help.
function permutations(str) {
return (str.length <= 1) ? [str] :
// Array.from(new Set(
str.split('')
.map( (char, i) =>
permutations( str.substr(0, i) + str.substr(i + 1))
.map( p => char + p))
// .reduce( (r, x) => r.concat(x), [])
// ));
}
permutations('abc')
One way I prefer to analyze and create recursive solutions is to work as though it's mathematical induction1.
The trick is to show that the function returns the right value for our base case(s), and then show that if it returns the right value for our simpler cases, it will also return the right value for our current case. Then we know that it will work for all values, as long as each recursive call is to some simpler case that eventually leads to a base case.
So look at your function. I've reformatted it to make discussion easier, and I've restored the reduce call you've commented out. That turns out to be necessary to do this right (although we'll discuss a more modern alternative below.) You also commented out the Array .from (new Set( ... )) wrapper, which is used to remove duplicates in the case your string has repeated characters. Without this, "aba" returns ["aba", "aab", "baa", "baa", "aab", "aba"]. With it, we get ["aba", "aab", "baa"], which makes more sense. But that is separate from our recursion question.
The cleaned-up function looks like this:
function permutations (str) {
return (str .length <= 1)
? [str]
: str
.split ('')
.map ((char, i) =>
permutations (str .substr (0, i) + str.substr (i + 1))
.map (p => char + p)
)
.reduce ((r, x) => r .concat (x), [])
}
permutations('abc')
Our base cases are pretty simple, str.length <= 1. In that case we yield [str]. This only has two possibilities: the string is empty, and we return [''], or the string has a single character, say 'x', and we return ['x']. These are pretty clearly correct, so we move on to the recursive call.
Let's say we pass 'abc'. The split and map calls turn that into the equivalent of this:
[
permutations ('bc') .map (p => 'a' + p),
permutations ('ac') .map (p => 'b' + p),
permutations ('ab') .map (p => 'c' + p),
]
But we have made the assumption that our recursion works on the smaller strings of 'bc', 'ac', and 'ab'. That means that permutations('bc') will yield ['bc', 'cb'], and similarly for the others, so this is equivalent to
[
['bc', 'cb'] .map (p => 'a' + p),
['ac', 'ca'] .map (p => 'b' + p),
['ab', 'ba'] .map (p => 'c' + p),
]
which is
[
['abc', 'acb']
['bac', 'bca']
['cab', 'cba']
]
Now we do the reduce call, which successively concatenates each array onto the previous result, starting with [], to get
['abc', 'acb', 'bac', 'bca', 'cab', 'cba']
There is a cleaner way of doing this. We can replace the map call followed by this reduce call with a single flatMap call, like this:
function permutations (str) {
return (str .length <= 1)
? [str]
: str
.split ('')
.flatMap ((char, i) =>
permutations (str .substr (0, i) + str.substr (i + 1))
.map (p => char + p)
)
}
In any case, we've demonstrated our inductive trick. By assuming this works for the simpler cases, we show that it works for out current case. (And no, we haven't done this rigorously, only by example, but it wouldn't be terribly difficult to prove this with some sort of mathematical rigor.) When we combine that with the demonstration that it works for the base case, we show that it works for all cases. This depends on our recursive calls being simpler in some way that eventually leads to a base case. Here, the strings being passed to the recursive call are one character shorter than those we were supplied, so we know that eventually we will hit our str .length <= 1 condition. And thus we know that it works.
If you add the Array .from (new Set ( ... )) wrapper back on, this will also work for those cases with repeating characters.
1
You may or may not have run across induction, and you may or may not remember it if you did, but in essence, it's very simple. Here's a very simple mathematical induction argument:
We will prove that 1 + 2 + 3 + ... + n == n * (n + 1) / 2, for all positive integers, n.
First, we can easily see that it's true when n is 1: 1 = 1 * (1 + 1) / 2
Next we assume that the statement is true for all integers below n.
We show that it's true for n like this:
1 + 2 + 3 + ... + n is the same as 1 + 2 + 3 + ... + (n - 1) + n, which is (1 + 2 + 3 + ... (n - 1)) + n. But we know that the statement is true for n - 1 (since we assumed it's true for all integers below n), so 1 + 2 + 3 + ... + (n - 1) is, by substituting in n - 1 for n in the expression above, equal to (n - 1) * ((n - 1) + 1) / 2, which simplifies to (n - 1) * n / 2. So now our larger expression ((1 + 2 + 3 + ... (n - 1)) + n is the same as ((n - 1) * n / 2) + n, which we can simplify to (n^2 - n) / 2 + n and then to (n^2 - n + (2 * n)) / 2 and to (n^2 + n) / 2. which factors into n * (n + 1) / 2.
So, by assuming it's true for everything less than n we show that it's true for n as well. Together with the fact that it's true when n is 1, the principle of induction says that it's true for all positive integers n.
You may have seen induction stated slightly differently: If (a) it's true for 1 and (b) being true for n - 1 implies that it's true for n, then (c) it's true for all positive integers n. (The difference here is that we don't need the assumption that it's true for all integers below n, only for n - 1.) It's easy to prove the equivalence of these two models. And the everything below formulation usually makes for a more convenient analogy in recursive problems.
Let's examine permutations('abc').
'abc' is converted to ['a','b','c'] for mapping
Map
a
First, char='a',i=0.
Note that permutations(str.substr(0, i) + str.substr(i + 1)) means "get the permutations of all the characters EXCEPT the one I'm looking at. In this case, this means permutations('bc'). Let's assume this gives the correct outputs ['bc','cb'], as the inductive hypothesis.
.map(p => char + p) then tells us to prepend the character we are looking at ('a') to each of the smaller permutations. This yields ['abc',acb'].
b
Following the same logic, char='b',i=1'. permutations('ac') == ['ac','ca']. Final outputs are ['bac','bca']
c
Following the same logic, char='c',i=2'. permutations('ab') == ['ab','ba']. Final outputs are ['cab','cba'].
Thus the overall output of the function would be [['abc','acb'],['bac','bca'],['cab','cba']]...
This is actually a pretty unusual definition of permutations, one that I happen to never have seen before.
(well, as it turns out, I actually wrote an answer once with its near exact equivalent, and only a few years prior... oh my).
In pseudocode, the simply-recursive definition one usually sees is
perms [x, ...xs] = [ [...as, x, ...bs] | p <- perms xs, (as, bs) <- splits p]
but this one is
perms2 xs = [ [x, ...p] | (as, [x, ...bs]) <- splits xs, p <- perms2 [...as, ...bs]]
(with list comprehensions and patterns; sans the empty list cases; with the "natural" definition of splits which builds a list of all possibilities of breaking a list up into two parts).
There's a certain duality here... Interesting. And not "simply"-recursive. :)
Or, with some more named functions to be implemented in obvious ways,
perms [x, ...rest] = [ i | p <- perms rest, i <- inserts x p]
= flatMap (inserts x) (perms rest)
--- and this version,
perms2 xs = [ [x, ...p] | (x, rest) <- picks xs, p <- perms2 rest]
See also:
Understanding Peter Norvig's permutation solution in PAIP
I know questions like this are here, but I'm specifically addressing processing speed. What I'm doing already works.
I have an Array containing titles to sort on. Some of the titles start with a double quote, such as "You're in Charge" Leadership in Difficult Times and some start with A, An, or The such as A Collective Impact on Literacy. I want to ignore those in sorting, and this works fine:
theArray.sort(function(a, b) {
var titleA = a.title.toLowerCase();
titleA = removeCertainLeadingCharacters(titleA);
var titleB = b.title.toLowerCase();
titleB = removeCertainLeadingCharacters(titleB);
return (titleA < titleB) ? -1 : (titleA > titleB) ? 1 : 0;
});
where the function removeCertainLeadingCharacters() is defined this way:
function removeCertainLeadingCharacters(title) {
// remove leading quote from a lowercase title, and also leading articles (a, the, an)
if (title.indexOf('"') == 0) title = title.substr(1);
words = title.split(" ");
if(words[0] == 'a' || words[0] == 'the' || words[0] == 'an' ) {
return words.splice(1).join(" ");
}
else {
return title;
}
}
My only question is whether there is a faster way of doing this. If I'm sorting an Array of about 800 titles and leave out the call to removeCertainLeadingCharacter() the sort is blazingly fast. If I add the function call then it's slower. Not a huge drag on the speed, but noticeably slower. Without the call it's basically instantaneous.
So I was just wondering if there is a faster of accomplishing this than what I'm doing.
Thanks.
this may save you some time (50% of the time) because you do not need to split when you only check the first word in the statement.
function removeCertainLeadingCharacters(title) {
// remove leading quote from a lowercase title, and also leading articles (a, the, an)
if (title.indexOf('"') === 0) {
title = title.substr(1);
}
if (title.startsWith('a ')) {
title = title.substring(2);
} else if (title.startsWith('an ')) {
title = title.substring(3);
} else if (title.startsWith('the ')) {
title = title.substring(4);
}
return title;
}
You can simplify the final line in your sort algorithm like this:
return titleA - titleB;
Request:
Using JavaScript, write a function that takes an integer. The integer represents the number of times a coin is flipped. Using recursive strategies only, return an array containing all possible combinations of coin flips. Use "H" to represent heads and "T" to represent tails. The order of combinations does not matter.
For example, passing in "2" would return:
["HH", "HT", "TH", "TT"]
Context:
I am relatively new to JavaScript as well as the concept of recursion. This is purely for practice and understanding, so the solution does not necessarily need to match the direction of my code below; any useful methods or other ways of thinking through this are helpful, as long as it's purely recursive (no loops).
Attempt:
My attempt at this started out simple however the "action" progressively got more convoluted as I increased the input. I believe this works for inputs of 2, 3, and 4. However, inputs of 5 or higher are missing combinations in the output. Many thanks in advance!
function coinFlips(num){
const arr = [];
let str = "";
// adds base str ("H" * num)
function loadStr(n) {
if (n === 0) {
arr.push(str);
return traverseArr();
}
str += "H";
loadStr(n - 1);
}
// declares start point, end point, and index to update within each str
let start = 0;
let end = 1;
let i = 0;
function traverseArr() {
// base case
if(i === str.length) {
console.log(arr);
return arr;
}
// updates i in base str to "T"
// increments i
// resets start and end
if(end === str.length) {
str = str.split('');
str[i] = "T";
str = str.join('');
i++;
start = i;
end = i + 1;
return traverseArr();
}
// action
let tempStr = str.split('');
tempStr[start] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = str.split('');
tempStr[start] = "T";
tempStr[end] = "T";
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
tempStr = tempStr.split('');
tempStr.reverse();
tempStr = tempStr.join('');
if(!arr.includes(tempStr)){
arr.push(tempStr);
};
// recursive case
start++;
end++;
return traverseArr();
}
loadStr(num);
}
coinFlips(5);
Below is a long description about how to create such recursive functions. I think the steps described help solve a great number of problems. They are not a panacea, but they can be quite useful. But first, here's what we'll work toward:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
Determining our algorithm
To solve a problem like this recursively, we need to answer several questions:
What value are we recurring on?
For simple recursions, it's often a single numeric parameter. In all cases there must be a way to demonstrate that we are making progress toward some final state.
This is a simple case, and it should be pretty obvious that we want to recur on the number of flips; let's call it n.
When does our recursion end?
We need to stop recurring eventually. Here we might consider stopping when n is 0 or possibly when n is 1. Either choice could work. Let's hold off on this decision for a moment to see which might be simpler.
How do we convert the answer from one step into the answer for the next?
For recursion to do anything useful, the important part is calculating the result of our next step based on the current one.
(Again, there are possible complexities here for more involved recursions. We might for instance have to use all the lower results to calculate the next value. For an example look up the Catalan Numbers. Here we can ignore that; our recursion is simple.)
So how do we convert, say ['HH', 'HT', 'TH', 'TT'] into the next step, ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']? Well if we look at the next result closely, we can see that the in first half all elements begin with 'H' and in the second one they begin with 'T'. If we ignore the first letters, each half is a copy of our input, ['HH', 'HT', 'TH', 'TT']. That looks very promising! So our recursive step can be to make two copies of the previous result, the first one with each value preceded by 'H', the second one by 'T'.
What is the value for our base case?
This is tied to the question we skipped. We can't say what it ends on without also knowing when it ends. But a good way to make the determination for both is to work backward.
To go backward from ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT'] to ['HH', 'HT', 'TH', 'TT'], we can take the first half and remove the initial 'H' from each result. Let's do it again. From ['HH', 'HT', 'TH', 'TT'], we take the first half and remove the initial 'H' from each to get ['H', 'T']. While that might be our stopping point, what happens if we take it one step further? Taking the first half and removing the initial H from the one remaining element leaves us just ['']. Does this answer make sense? I'd argue that it does: How many ways are there to flip the coin zero times? Just one. How would we record it as a string of Hs and Ts? As the empty string. So an array containing just the empty string is a great answer for the case of 0. That also answers our second question, about when the recursion ends. It ends when n is zero.
Writing code for that algorithm
Of course now we have to turn that algorithm into code. We can do this in a few steps as well.
Declaring our function
We write this by starting with a function definition. Our parameter is called n. I'm going to call the function getFlips. So we start with
const getFlips = (n) =>
<something here>
Adding our base case.
We've already said that we're going to end when n is zero. I usually prefer to make that a little more resilient by checking for any n that is less than or equal to zero. This will stop an infinite recursion if someone passes a negative number. We could instead choose to throw an exception in this case, but our explanation of [''] for the case of zero seems to hold as well for the negative values. (Besides, I absolutely hate throwing exceptions!)
That gives us the following:
const getFlips = (n) =>
n <= 0
? ['']
: <something here>
I choose here to use the conditional (ternary) expression instead of if-else statements because I prefer working with expressions over statements as much as possible. This same technique can easily be written with if-else instead if that feels more natural to you.
Handling the recursive case
Our description was to "make two copies of the previous result, the first one with each value preceded by 'H', the second one by 'T'." Our previous result is of course getFlips (n - 1). If we want to precede each value in that array with 'H', we're best using .map. We can to id like this: getFlips (n - 1) .map (r => 'H' + r). And of course the second half is just getFlips (n - 1) .map (r => 'T' + r). If we want to combine two arrays into one, there are many techniques, including .push and .concat. But the modern solution would probably be to use spread parameters and just return [...first, ...second].
Putting that all together, we get this snippet:
const getFlips = (n) =>
n <= 0
? ['']
: [...getFlips (n - 1) .map (r => 'H' + r), ...getFlips (n - 1) .map (r => 'T' + r)]
console .log (getFlips (3))
Examining the results
We can test this on a few cases. But we should be fairly convinced by the code. It seems to work, it's relatively simple, there are no obvious edge cases missing. But I still see a problem. We're calculating getFlips (n - 1) twice, for no good reason. In a recursive situation that it usually quite problematic.
There are several obvious fixes for this. First would be to give up my fascination with expression-based programming and simply use if-else logic with a local variable:
Replace conditional operator with if-else statements
const getFlips = (n) => {
if (n <= 0) {
return ['']
} else {
const prev = getFlips (n - 1)
return [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
}
}
(Technically, the else isn't necessary, and some linters would complain about it. I think the code reads better with it included.)
Calculate a default parameter to use as a local variable
Another would be to use a parameter default value in the earlier definition.
const getFlips = (n, prev = n > 0 && getFlips (n - 1)) =>
n <= 0
? ['']
: [...prev .map (r => 'H' + r), ...prev .map (r => 'T' + r)]
This might rightly be viewed as over-tricky, and it can cause problems when your function is used in unexpected circumstances. Don't pass this to an array's map call, for instance.
Rethink the recursive step
Either of the above would work. But there is a better solution.
We can also write much the same code with a different approach to the recursive step if we can see another way of turning ['HH', 'HT', 'TH', 'TT'] into ['HHH', 'HHT', 'HTH', 'HTT', 'THH', 'THT', 'TTH', 'TTT']. Our technique was to split the array down the middle and remove the first letters. But there are other copies of that base version in the version of the array without one of their letters. If we remove the last letters from each, we get ['HH', 'HH', 'HT', 'HT', 'TH', 'TH', 'TT', 'TT'], which is just our original version with each string appearing twice.
The first code that comes to mind to implement this is simply getFlips (n - 1) .map (r => [r + 'H', r + 'T']). But this would be subtly off, as it would convert ['HH', 'HT', 'TH', ' TT'] into [["HHH", "HHT"], ["HTH", "HTT"], ["THH", "THT"], [" TTH", " TTT"]], with an extra level of nesting, and applied recursively would just yield nonsense. But there is an alternative to .map that removes that extra level of nesting, .flatMap.
And that leads us to a solution I'm very happy with:
const getFlips = (n) =>
n <= 0
? ['']
: getFlips (n - 1) .flatMap (r => [r + 'H', r + 'T'])
console .log (getFlips (3))
function getFlips(n) {
// Helper recursive function
function addFlips(n, result, current) {
if (n === 1) {
// This is the last flip, so add the result to the array
result.push(current + 'H');
result.push(current + 'T');
} else {
// Let's say current is TTH (next combos are TTHH and TTHT)
// Then for each of the 2 combos call add Flips again to get the next flips.
addFlips(n - 1, result, current + 'H');
addFlips(n - 1, result, current + 'T');
}
}
// Begin with empty results
let result = [];
// Current starts with empty string
addFlips(n, result, '');
return result;
}
In case this is of any interest, here's a solution that doesn't use recursion as such but make use of the Applicative type.
Except when n is 1, the list of all possible combinations is obtained by combining all possible outcomes of each coin flip:
22 → [H, T] × [H, T] → [HH, HT, TH, TT]
23 → [H, T] × [H, T] × [H, T] → [HHH, HHT, HTH, HTT, THH, THT, TTH, TTT]
...
A function that can take n characters and concat them can be written as such:
const concat = (...n) => n.join('');
concat('H', 'H'); //=> 'HH'
concat('H', 'H', 'T'); //=> 'HHT'
concat('H', 'H', 'T', 'H'); //=> 'HHTH'
//...
A function that produces a list of outcomes for n coin flips can be written as such:
const outcomes = n => Array(n).fill(['H', 'T']);
outcomes(2); //=> [['H', 'T'], ['H', 'T']]
outcomes(3); //=> [['H', 'T'], ['H', 'T'], ['H', 'T']]
// ...
We can now sort of see a solution here: to get the list of all possible combinations, we need to apply concat across all lists.
However we don't want to do that. Instead we want to make concat work with containers of values instead of individual values.
So that:
concat(['H', 'T'], ['H', 'T'], ['H', 'T']);
Produces the same result as:
[ concat('H', 'H', 'H')
, concat('H', 'H', 'T')
, concat('H', 'T', 'H')
, concat('H', 'T', 'T')
, concat('T', 'H', 'H')
, concat('T', 'H', 'T')
, concat('T', 'T', 'H')
, concat('T', 'T', 'T')
]
In functional programming we say that we want to lift concat. In this example I'll be using Ramda's liftN function.
const flip = n => {
const concat = liftN(n, (...x) => x.join(''));
return concat(...Array(n).fill(['H', 'T']));
};
console.log(flip(1));
console.log(flip(2));
console.log(flip(3));
console.log(flip(4));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
<script>const {liftN} = R;</script>
I have a assignment where I write a number in Expanded Form and display it as a string.
For example:
expandedForm(7020); // should return '7000 + 20';
expandedForm(7402); // '7000 + 400 + 2';
I haven't figured out all of it yet, but my current code looks like:
function expandedForm(num) {
let numArr = Array.from(num.toString()).map(Number),
counter = numArr.length,
answer = "";
for(let i = 0; i < numArr.length; i++) {
(numArr[i] === 0) ? counter-- : --counter;
console.log(numArr[i].toString(), counter);
}
}
expandedForm(702); // returns '7' 2 and '2' 0
I skip 0 since it's supposed to be ignored, go through each integer and check the amount of units it has with the counter. This is helpful but now I'm trying to somehow add '00' to the '7' based on counter value. So if counter is 2 I need to add two 0 as a string '00'.
There is a good chance I'm not doing this with best practices so if something should be done differently with what I already have please suggest. Thank you.
"0".repeat(counter)
thats it... then just concatenate all those to strings and you are done :)
Also note that:
(numArr[i] === 0) ? counter-- : --counter;
basically equals counter--;.
"best practice" is quite opinion based, heres how I'd do that:
const chars = [...("" + input)];
return chars
.map((char, i) => char + "0".repeat(chars.length - i - 1))
.filter(it => it[0] !== "0")
.join(" + ");
If you are interested in code golfing (it's fun, but not always a "best practice"), you can use:
[...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
Here it is in action:
> input = 2
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'2'
> input = 200
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'200'
> input = 209777800001
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'200000000000 + 9000000000 + 700000000 + 70000000 + 7000000 + 800000 + 1'
Here is how it works:
[...`${input}`]
makes an array out of the digits in your number. For example, 7503 becomes ['7', '5', '0', '3']
.map((c,i,a)=>c.padEnd(a.length-i,'0'))
maps each digit to its padding with the correct number of zeros. The callback to map takes the digit, the array index, and then the array itself. This gives us, for the running example, ['7000', '500', '00', '3']
.filter(c=>+c)
removes any string of zeros such as "0", "0000", etc. from the array. Now we have ['7000', '500', '3']
.join(' + ')
does what it looks like it does.
Here's what this solution does not do:
Handle the value 0 (because you get an empty string)
Handle negative numbers
Handle non integers
The first one can be fixed by doing
[...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ') || '0'
Since this is an assignment, I'll leave the other two cases to you.
Also have fun explaining this to the person that assigned you the problem. ;-)
As mentioned in another answer, best practice is subjective. You can be more explicit by introducing intermediate variables:
const chars = Array.from(`${input}`);
const places = chars.map((c, i, a) => c.padEnd(a.length - i, '0'));
const nonZeros = places.filter(c => Number(c) !== 0);
const result = nonZeros.join(' + ') || '0';
Again, best practices would also require you do some error handling. What if your input is NaN or a string, array, or object? What if it is infinity? Floating point?