How to do Nested For Loops in Functional Style - javascript

I'm in the process of learning functional programming, and completely getting rid of for loops has been a challenge sometimes, because they provide so much control and freedom. Below is an example of checking if a string is an isogram or not (no letters should be repeated). With nested for loops, it became an easy solution. Is there a way to do this the functional way with any high order functions or anything else? Any suggestion would be a huge help.
Code:
function isIsogram(string) {
let array = string.split('');
let condition = true;
for (let i = 0; i < string.length; i++) { //first loop picks character
for (j = i + 1; j < string.length; j++) { //second loop compares it rest
if (array[i].toLowerCase() == array[j].toLowerCase())
condition = false; //if repeat, the condition false
}
return condition;
}
}

You can use every or some together with a suitable string function:
function isIsogram(string) {
string = string.toLowerCase(); // case insensitive
return string.split('').every(function(character, index) {
return !string.includes(character, index+1);
});
}
Instead of includes you might also have utilised indexOf.

You can sort the String first and then apply every on it. It will stop the iteration as soon as two successive letters are the same:
Here is an improved implementation. Credit goes to #Xotic750:
function isIsogram(x) {
return Array.from(x.toLowerCase()).sort().every((y, i, xs) => i === 0
? true
: y !== xs[i - 1]);
}
console.log( isIsogram("consumptively") );
console.log( isIsogram("javascript") );
The implementation uses Array.prototype.every's second parameter, which represents the index of the current element (of the iteration). Please note that isIsogram solely depends on functions and their arguments.

Another example, like #Bergi but using some ES6 features for comparison.
function isIsogram(string) {
string = string.toLowerCase(); // case insensitive
for (let character of Array.from(string).entries()) {
if (string.includes(character[1], character[0] + 1)) {
return false;
}
}
return true;
}
console.log(isIsogram('abc'));
console.log(isIsogram('abca'));
How your ES3 style code could have looked (noting some of the issues pointed out in the comments)
function isIsogram(string) {
string = string.toLowerCase(); // case insensitive
var length = string.length;
for (var i = 0; i < length; i += 1) {
for (var j = i + 1; j < length; j += 1) {
if (string.charAt(i) === string.charAt(j)) {
return false;
}
}
}
return true;
}
console.log(isIsogram('abc'));
console.log(isIsogram('abca'));

Related

The only word that is not passing the test is "almostomla."

I am learning JS on freecodecamp and am stuck on a problem. I am creating a palindrome checker. My code almost works. However, there is only one word that is not passing the test and that is almostomla. It is not a palindrome yet my code returns true. I have tried several things. Even rewrote the code and used the while loop but nothing seems to help. There is a solution at the freeCodecamp web but I have written the code in a different way and am unable to figure my mistake out.
Here is my code.
let reversedStr = [];
function palindrome(str) {
let d = str.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
for (let i = d.length - 1; i >= 0; i--) {
reversedStr.push(d[i]);
}
for (let j = 0; j < str.length; j++) {
if (reversedStr[j] == d[j]) {
return true;
} else {
return false;
}
}
}
console.log(palindrome("almostomla"));
This line if (reversedStr[j] == d[j]) {return true; returns as soon as the character matches at both the index. It does not check rest of the characters.
In fact you can just return as soon as the character in both index does not match.
Also note the reversedStr has to be inside the function. Else it will contain previous values
function palindrome(str) {
let reversedStr = [];
let d = str.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
for (let i = d.length - 1; i >= 0; i--) {
reversedStr.push(d[i]);
}
for (let j = 0; j < str.length; j++) {
if (reversedStr[j] !== d[j]) {
return false;
}
}
return true
}
console.log(palindrome("almostomla"));
console.log(palindrome("1221"));
Your code returns "true" or "false" after first match - it checks that 'a' is equal to 'a' and returns true.
You can return "false" from inside of cycle only if you found duplicated letter else return true after the cycle ends.

Codewars Javascript Problem- my code with a double for loop times out

I'm trying to solve a problem on Codewars which involves seeing if one string includes all the letters in a second string. I think I've found a decent solution, but my code times out (12000ms) and I can't figure out why. Could anyone shed some light on this issue?
function scramble(str1, str2) {
let i;
let j;
let x = str2.split();
for (i = 0; i < str1.length; i++) {
for (j = 0; j < str2.length; j++) {
if (str1[i] == str2[j]) {
x.splice(j, 1);
j--;
}
}
}
if (x.length == 0) {
return true;
} else {
return false;
}
}
If your strings have sizes N and M then your algorithm is O(N*M). You can get O(NlogN + MlogM) by sorting both strings and then do a simple comparison. But you can do even better and get O(N+M) by counting the letters in one string and then see if they are present in the other. E.g. something like this:
function scramble(str1, str2) {
let count = {}
for (const c of str1) {
if (!count[c])
count[c] = 1
else
count[c]++
}
for (const c of str2) {
if (!(c in count))
return false
count[c]--
}
for (let k in count) {
if (count.hasOwnProperty(k) && count[k] !== 0)
return false
}
return true
}
You created an infinite loop by both incrementing and decrementing j. The value of j gets stuck whenever str1[i] == str2[j]
Reducing your code snippet to the simplest form would look something like this:
for (j = 0; j < 10; j++) {
j--;
console.log(j) // always -1
}
You're adjusting x but then referring to str2 as if it has been changed. Because you never adjust str2, you're always comparing the same two letters, so you get stuck in a loop. That's one problem. Then, your question's wording suggests that we're checking if every letter in str2 is in str1, but you're going through every letter in str1 and checking it against str2. str1 should be the inner loop.
function scramble(str1, str2) {
var x = str2.split("");
for (var i = 0; i < x.length; i++) {
for (var j = 0; j < str1.length; j++) {
if (str1[j] == x[i]) {
x.splice(i--, 1);
}
}
}
return x.length === 0;
}
console.log(scramble("dirty rooms", "dormitory"));
console.log(scramble("cat", "dog"));
Because x.length === 0 is already a boolean value, you can just return that. No need for the if statements there. The triple equals checks the variable's type and value, and double equals only checks value. I tend to always use triple when I'm checking against 0 and 1 because you don't want unintended consequences like this:
console.log(false == 0, true == 1);
console.log(false === 0, true === 1);
Also, because i-- means "i = i - 1 after execution", you can put that directly in your call to splice, and it won't execute until after splice is finished. --i, on the other hand, would be evaluated before execution.
This is all great, but using indexOf is a simpler solution:
function scramble(str1, str2) {
for (let i = 0; i < str2.length; i++) {
if (str1.indexOf(str2[i]) == -1) return false;
}
return true;
}
console.log(scramble("forty five", "over fifty"));
console.log(scramble("cat", "dog"));

If condition vs loop one item

I was just curious, is it worth to have if condition before looping some array, that in 90% will be array of 1 item?
Code example:
const a = [3];
const aLength = a.length;
if(aLength > 1) {
for(let i = 0; i < aLength; i++) {
func(i);
}
} else {
func();
}
function func(position = 0) {
console.log('hi' + position);
}
I agree with Federico's comment, a single for loop is the most readable in this case.
Also, even though you reuse it, there is not much point to extracting a.length into aLength
const a = [3];
for(let i = 0; i < a.length; i++) {
func(i);
}
function func(position) {
console.log('hi' + position);
}
Warning: very personal perspective down there, you could achieve the same level of clarity with comments too.
Well, unless the single element case has a very specific meaning in your domain. In which case, I would separate them with two functions with very specific names as follows:
const a = [3];
if(a.length > 1) {
handleMultiple(a);
} else {
handleSingleAndWhyItIsASpecialCase(a)
}
handleMultiple(array) {
for(let i = 0; i < array.length; i++) {
func(i);
}
}
handleSingleAndWhyItIsASpecialCase(array) {
func();
}
function func(position = 0) {
console.log('hi' + position);
}
As Hamid said below, you can easily turn it into a oneliner:
[45,63,77].forEach((element, index) => console.log(index));
Consider using forEach instead of map to make your intent clear though.
Write clean code and make everyone happy.
you can eliminate if and loop:
const a=[5,6,3]
a.forEach((value,index)=>console.log('hi'+index));

Variable for loop

If reverse == true I want to run one kind of loop, else I want to run another one.
Currently, here is an example of my code:
if (reverse) {
for (var i = 0; i < length; i++) {
...
}
} else {
for (var i = length; i >= 0; i--) {
...
}
}
The code inside is really big, and is quite the same. I could use a function, but this function would have so many params that is not a good choice.
So I've tried to do something like that:
var loopStart1 = startPageIndex;
if (!reverse) {
condition1 = function(i) {
return i < length;
}
increment1 = function(i) {
return ++i;
}
} else {
condition1 = function(i) {
return i >= 0;
}
increment1 = function(i) {
return i--;
}
}
mainLoop: for (var i = loopStart1; condition1(i); increment1(i)) {
But now I have an infinite loop.
Any idea on how to solve this issue?
Why not do it inline?! ;)
var start = startPageIndex;
for (var i = start; (reverse && i >= 0) || (!reverse && i < length); reverse ? --i : ++i) { }
Assuming the specific case is to traverse through an array either backwards or forwards, there are two simple ways to do that.
1) Just conditionally reverse the array before looping, or
2) Use a single loop but conditionally map the loop variable to something else. Something like...
for (var i = 0; i < length; i++) {
var j = i;
if (reverse) {
j = length - (i + 1);
}
// ...then use j instead of i
}
If you want to make it dynamic, I wouldn't use a for loop, but a do..while loop to be easier to customize.
Your main function would just have a simple reverse bool flag and you could just pass that.
Inside that function that you want to depend on the reverse flag, you can use the ternary expression in the condition (x ? y : z)
Makes it clearer to read. In theory you can do it in a for loop directly, using two ternary directions.
do {
//Your actions here, using i as counter
//Here you will do the counter direction change
if(reverse)
i--;
else
i++;
// Use ternary expression if (x ? y : z) in the while condition
// Reads as: If x is true, do y, else do z
// All in one line
} while(reverse ? i>=0 : i<n);
Ideally, in these situations I would consider using iterators.
How about a simple loop function,
Below I've created a simple function called myLoop, were you can pass the length, if it's reversed, and what callback you want for each loop iteration.
eg.
function myLoop(length, reverse, each) {
var index;
if (!reverse) {
for (index = 0; index < length; index ++) each(index);
} else {
for (index = length -1; index >= 0; index --) each(index);
}
}
function show(index) {
console.log("Loop index = " + index);
}
console.log("forward");
myLoop(5, false, show); //normal
console.log("revere");
myLoop(5, true, show); //reversed
I would opt for the same code, just change the array.
var array = ['one', 'two', 'three'];
var reversed = true;
let arrToUse = reversed ? array.reverse() : array;
for (var i = 0; i < arrToUse.length; i++) {
console.log(arrToUse[i]);
}
Check https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
Primitive parameters (such as a number) are passed to functions by value; the value is passed to the function, but if the function changes the value of the parameter, this change is not reflected globally or in the calling function.
This is what happens when you call increment1(i), outer i in for loop is not changed. To fix it just set i from increment1(i)'s return value.
mainLoop: for (var i = loopStart1; condition1(i); i = increment1(i)) {

Removing Duplicate Items in an Array without Regex or filter

I have been stumped on this problem for a few hours now and am making no progress. I feel like this should be simple. I am trying to Remove duplicate characters in a String without using methods such as Filter or a Reg ex.
Here is my current code:
var duplicate = function(string) {
var newString = string.split("");
var finalArrayWithNoDuplicates = []
for (var i = 0; i < newString.length; i++){
for (var=0; j < newString.length; i++){
while(newString[i])
if (newString[i] !== newString[j]){
}
}
}
return finalArrayWithNoDuplicates.join("");
};
I am able to filter one letter at a time but as I progress down the chain in the while statement I am adding letters that were filtered out originally.
All of the algorithm tutorials for this algorithm are in Java that I have been finding. Is there a way to do this with only using a a for and while loops?
There are several things wrong with the proposed code:
It has serious errors (the inner loop is written all wrong)
You don't need to involve arrays at all, strings will do just fine
The "if char !== other char" check will never provide enough information to act on
Here's an alternative version using for loops and the same basic idea:
function deduplicate(str) {
var result = "";
for (var i = 0; i < str.length; ++i) {
var found = false;
for (var j = 0; j < i; ++j) {
if (str[i] == str[j]) {
found = true;
break;
}
}
if (!found) result += str[i];
}
return result;
}
Each character str[i] in the input string is compared to all characters str[j] that precede it (there is no point in comparing to characters that follow it because we are going to process those when their turn comes up anyway). If the character is not equal to any of those that precede it then we know it's the first of its kind to appear and include it in the result.
Note that this algorithm has O(n²) performance, which is very poor compared to other possible approaches. Its main selling point is that it is straightforward and that everything happens "in front of your eyes".
Here is a slightly modified version of your function that uses an object to keep track of which letters have already been encountered:
var duplicate = function(string) {
var finalArrayWithNoDuplicates = [];
var seen = {};
for (var i = 0; i < string.length; i++) {
if (!seen[string[i]]) {
finalArrayWithNoDuplicates.push(string[i]);
seen[string[i]] = 1;
}
}
return finalArrayWithNoDuplicates.join("");
};
No need for two nested for-loops
No need for "while" loop as well
in the following line of code there are two errors: for (var=0; j < newString.length; i++){ first one is var=0 (compilation error) and second is theat you increment i instead of j
It can be done by adding only unique elements (that don't appear twice) to finalArrayWithNoDuplicates
as follows:
var duplicate = function(newString) {
var finalArrayWithNoDuplicates = []
var x = 0;
for (var i = 0; i < newString.length; i++){
// if the char appears in another index
// or if it's already in the result - don't add it
if (newString.lastIndexOf(newString[i]) !== i || finalArrayWithNoDuplicates.indexOf(newString[i]) > -1){
continue;
}
else{
finalArrayWithNoDuplicates[x++] = newString[i];
}
}
return finalArrayWithNoDuplicates.join("");
};
var arr = [1,2,3,4,5,4,5,6,7];
alert(duplicate(arr));
OUTPUT:
1234567

Categories