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
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>
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
I am currently writing a JS rules engine which at one point needs to evaluate boolean expressions using the eval() function.
Firstly I construct an equation as such:
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "relation.value";
relation.relatedTrigger.previousValue is the value I want to compare.
relation.operator is the operator (either "==", "!=", <=, "<", ">", >=").
relation.value is the value I want to compare with.
I then simply pass this string to the eval function and it returns true or false as such:
return eval(equation);
This works absolutely fine (with words and numbers) or all of the operators except for >= and <=. E.g. When evaluating the equation:
relation.relatedTrigger.previousValue <= 100
It returns true when previousValue = 0,1,10,100 & all negative numbers but false for everything in between.
I would greatly appreciate the help of anyone to either answer my question or to help me find an alternative solution.
Regards,
Augier.
P.S. I don't need a speech on the insecurities of the eval() function. Any value given to relation.relatedTrigger.previousValue is predefined.
edit: Here is the full function:
function evaluateRelation(relation)
{
console.log("Evaluating relation")
var currentValue;
//if multiple values
if(relation.value.indexOf(";") != -1)
{
var values = relation.value.split(";");
for (x in values)
{
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "values[x]";
currentValue = eval(equation);
if (currentValue)
return true;
}
return false;
}
//if single value
else
{
//Evaluate the relation and get boolean
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "relation.value";
console.log("relation.relatedTrigger.previousValue " + relation.relatedTrigger.previousValue);
console.log(equation);
return eval(equation);
}
}
Answer: Provided by KennyTM below. A string comparison doesn't work. Converting to a numerical was needed.
You didn't show how relation.relatedTrigger.previousValue is obtained, but I guess the type of this variable is still a string. In this case, the right hand side will be treated as a string instead. A string comparison matches all characteristics you mentioned:
>>> '-213' <= '100'
true
>>> '0' <= '100'
true
>>> '1' <= '100'
true
>>> '2' <= '100'
false
>>> '10' <= '100'
true
>>> '13' <= '100'
false
You need to make sure relation.relatedTrigger.previousValue is a number. One solution is use the unary + operator in the comparison, e.g.
+relation.relatedTrigger.previousValue <= 100
This has nothing to do with eval. The problem is the overly liberal implicit conversion in Javascript.
Edit: By the way, instead of eval, you could use a dictionary of functions instead. This is faster and also safer. See http://jsperf.com/eval-vs-function-map.
var fmap = {
'>=': function(a, b) { return a >= b; },
...
};
fmap[relation.operator](+relation.relatedTrigger.previousValue,
+relation.value);
It is comparing strings not numbers.
Make sure that relation.relatedTrigger.previousValue and relation.value are numbers.
"11" > "100": Because 11 comes after 100 in alphabetical order.
11 < 100 in numeric order.
var relation = {'relatedTrigger':{'previousValue':"7"}, 'operator':'<=', 'value': "100"};
var equation = "parseFloat(relation.relatedTrigger.previousValue)" + " " + relation.operator +
" " + "parseFloat(relation.value)";
alert(equation + ", " + eval(equation));
This is effectively what you end up with and the extra step to ensure numeric value, not strings are passed seems to work.