Related
There are a 2D char-array and a search word. The task is to find out if there's the word in the array.
The word can be placed in the array in any curve with 4 directions: up, down, left, right.
You can't reuse the letters. For example, from an array [a, b, c] you can't get a word "ababc".
See the examples below.
function findWord(puzzle, word) {
}
const puzzle = [
'ANGULAR',
'REDNCAE',
'RFIDTCL',
'AGNEGSA',
'YTIRTSP',
];
console.log(findWord(puzzle, 'ANGULAR')); // true
console.log(findWord(puzzle, 'REACT')); // true
console.log(findWord(puzzle, 'ARRAY')); // true
console.log(findWord(puzzle, 'UNDEFINED')); // true
console.log(findWord(puzzle, 'RED')); // true
console.log(findWord(puzzle, 'STRING')); // true
console.log(findWord(puzzle, 'CLASS')); // true
console.log(findWord(puzzle, 'FUNCTION')); // false
console.log(findWord(puzzle, 'NULL')); // false
My solution:
function findWord(puzzle, word) {
puzzle = puzzle.map(e => e.split(""));
const current = [];
const clear = [];
for (let y = 0; y < puzzle.length; y++) {
for (let x = 0; x < puzzle[y].length; x++) {
if (puzzle[y][x] === word[0]) {
clear.push({x, y});
current.push(...getSurround(x, y, puzzle));
}
}
}
return nextTurn(puzzle, word.slice(1), clear, current);
}
function nextTurn(puzzle, word, clear, current) {
const next = [];
if (word.length === 0) return true;
for (let v of clear) {puzzle[v.y][v.x] = "-"}
clear.length = 0;
for (let v of current) {
if (v === null) continue;
if (v.letter === word[0]) {
clear.push({x: v.x, y: v.y});
next.push(...getSurround(v.x, v.y, puzzle));
}
}
if (next.length === 0) return false;
return nextTurn(puzzle, word.slice(1), clear, next);
}
function getSurround(x, y, puzzle) {
const surround = [];
const u = (y !== 0) ? {x, y: y - 1, letter: puzzle[y - 1][x]} : null;
const r = (x !== puzzle[y].length - 1) ? {x: x + 1, y, letter: puzzle[y][x + 1]} : null;
const d = (y !== puzzle.length - 1) ? {x, y: y + 1, letter: puzzle[y + 1][x]} : null;
const l = (x !== 0) ? {x: x - 1, y, letter: puzzle[y][x - 1]} : null;
surround.push(u, r, d, l);
return surround;
}
It seems that solution works for finding the word, but the problem is in the recursion exit.
I made a guess, that true is when the word is done, and false is when the word is not done and there is no other appropriate letter to end the word.
Your issue seems to be with:
for (let v of clear) {puzzle[v.y][v.x] = "-"}
clear.length = 0;
for the word "angular", this will set all "a"s to "-", which you shouldn't be doing, you should only be setting "a" to blank if/when you use it. Currently you're setting all "a" characters to "-" which means that you wont be able to use "a" again (so the second a in "angular" wont be found).
I suggest removing the idea of your clear array, and instead updating your nextTurn function:
function nextTurn(puzzle, word, current) {
if (word.length === 0) return true;
for (const v of current) {
if (v.letter === word[0]) {
const nextCandidates = getSurround(v.x, v.y, puzzle);
puzzle[v.y][v.x] = '-'; // mark letter as we're using it
const turnResult = nextTurn(puzzle, word.slice(1), nextCandidates);
if(turnResult) // recursive call returned true
return turnResult;
// If using the letter at x,y didn't work, "un-mark" it
puzzle[v.y][v.x] = v.letter;
}
}
return false;
}
The idea is to mark the current letter as "used" ("-") right before we recurse and call nextTurn(). This allows us to use the current letter right before we search its surrounding letters in the next call to nextTurn(). If searching that particular letter doesn't work, we backtrack and set the letter in the puzzle back to "available" by setting the letter back to its original value so that we can search the other possible options (and possible reuse this letter at some further point in the word).
In the below snippet I've also updated your findWord() function so that it converts your 2d puzzle array of letters to an array of vertices ({x, y, letter} objects), which can then be used by nextTurn() to calculate the next possible solution for each letter. Lastly, I've also updated your getSurround function to avoid the unneeded null values. This helps remove iterations over values which you know you won't be processing:
function findWord(puzzle, word) {
puzzle = puzzle.map(str => Array.from(str));
const candidates = puzzle.flatMap((row, y) => row.map((letter, x) => toVertex(x, y, letter)));
return nextTurn(puzzle, word, candidates);
}
function nextTurn(puzzle, word, current) {
if (word.length === 0) return true;
for (const v of current) {
if (v.letter === word[0]) {
const nextCandidates = getSurround(v.x, v.y, puzzle);
//console.log(v.y, v.x, puzzle[v.y]);
puzzle[v.y][v.x] = '-'; // mark letter as we're using it
const turnResult = nextTurn(puzzle, word.slice(1), nextCandidates);
if(turnResult) // recursive call returned true
return turnResult;
// If using the letter at x,y didn't work, "un-mark" it
puzzle[v.y][v.x] = v.letter;
}
}
return false;
}
function getSurround(x, y, puzzle) {
const surround = [];
if(y !== 0)
surround.push(toVertex(x, y-1, puzzle[y - 1][x]));
if(x !== puzzle[y].length - 1)
surround.push(toVertex(x+1, y, puzzle[y][x + 1]));
if(y !== puzzle.length - 1)
surround.push(toVertex(x, y+1, puzzle[y + 1][x]));
if(x !== 0)
surround.push(toVertex(x - 1, y, puzzle[y][x - 1]));
return surround;
}
function toVertex(x, y, letter) {
return {x, y, letter};
}
const puzzle = [
'ANGULAR',
'REDNCAE',
'RFIDTCL',
'AGNEGSA',
'YTIRTSP',
];
console.log('ANGULAR', findWord(puzzle, 'ANGULAR')); // true
console.log('REACT', findWord(puzzle, 'REACT')); // true
console.log('ARRAY', findWord(puzzle, 'ARRAY')); // true
console.log('UNDEFINED', findWord(puzzle, 'UNDEFINED')); // true
console.log('RED', findWord(puzzle, 'RED')); // true
console.log('STRING', findWord(puzzle, 'STRING')); // true
console.log('CLASS', findWord(puzzle, 'CLASS')); // true
console.log('FUNCTION', findWord(puzzle, 'FUNCTION')); // false
console.log('NULL', findWord(puzzle, 'NULL')); // false
console.log('RANER', findWord(puzzle, 'RANER')); // false (additional test to try re-use)
I have a recursive function that checks if a string is a palindrome, but my assignment asks me to count the number of palindromes in a string (for example kayak has 2).
I'm really confused about how I can implement a recursive function that counts the number of palindromes. Here's my current code:
function isPalindrome(string) {
if (string.length <= 1) {
return true;
}
let [ firstLetter ] = string;
let lastLetter = string[string.length - 1];
if (firstLetter === lastLetter) {
let stringWithoutFirstAndLastLetters = string.substring(1, string.length - 1);
return isPalindrome(stringWithoutFirstAndLastLetters);
} else {
return false;
}
}
When the function gets a palindrome it is easy:
Record the input
Try again without the edges
Stop when input is three characters or less
"kayak" -> "aya"
If the input isn't a palindrome try "both ends" recursively e.g. with "kayam" try with both "kaya" and "ayam" and keep going...
We stop the recursion when a string is 3 (or less) characters. A single character is not a palindrome and checking whether a two or three characters string is a palindrome is trivial.
kayam
|
+-------------+
| |
kaya ayam
| |
+-------+ +--------+
| | | |
kay aya aya yam
const reverse =
([...xs]) =>
xs.reverse().join("");
const is_palindrome =
a =>
a.length === 1 ? false
: a.length <= 3 ? a[0] === a[a.length-1]
: a === reverse(a);
const find_palindromes = str => {
const scan =
(x, xs = []) =>
x.length <= 3 ? xs.concat(is_palindrome(x) ? x : [])
: is_palindrome(x) ? xs.concat
( x
, scan(x.slice(1, -1))
)
: xs.concat
( scan(x.slice(0, -1))
, scan(x.slice(1))
);
return [...new Set(scan(str))];
};
console.log(find_palindromes("kayak").join());
console.log(find_palindromes("kayakkayak").join());
console.log(find_palindromes("kayakcanoe").join());
console.log(find_palindromes("kayam").join());
console.log(find_palindromes("appal").join());
console.log(find_palindromes("madamimadam").join());
console.log(find_palindromes("madamimadamkayak").join());
I think the accepted answer does not actually work. It will not count palindromes unless they are centered in the string and will count substrings that are not palindromes if as long as they start and end with the same letter. The answer from CertainPerformance would probably work but I think it would result in checking a lot of strings that don't need to be checked. Here's what I came up with, I think it works for the extra tests I've added.
function countPalindromes(string) {
if (string.length <= 1) {
return 0;
}
count = 0
for ( var i = 0; i < string.length; i++ ) {
count += countPalindromesCenteredAt(string, i)
count += countPalindromesCenteredAfter(string, i)
}
return count
}
function countPalindromesCenteredAt(string, i) {
count = 0
for ( var j = 1; i-j>=0 && i+j < string.length; j++ ) {
if (string.charAt(i-j) === string.charAt(i+j)) {
count += 1
}
else {
return count
}
}
return count
}
function countPalindromesCenteredAfter(string, i) {
count = 0
for ( var j = 1; i-j>=0 && i+j < string.length; j++ ) {
if (string.charAt(i-j+1) === string.charAt(i+j)) {
count += 1
}
else {
return count
}
}
return count
}
console.log(countPalindromes("kayak"));
console.log(countPalindromes("aya"));
console.log(countPalindromes("kayakcanoe"));
console.log(countPalindromes("kcanoek"));
One method would be to first get all substrings, then validate each:
getAllSubstrings('kayak').filter(str => str.length >= 2 && isPalindrome(str))
function getAllSubstrings(str) {
var i, j, result = [];
for (i = 0; i < str.length; i++) {
for (j = i + 1; j < str.length + 1; j++) {
result.push(str.slice(i, j));
}
}
return result;
}
function isPalindrome(string) {
if (string.length <= 1) {
return true;
}
let [ firstLetter ] = string;
let lastLetter = string[string.length - 1];
if (firstLetter === lastLetter) {
let stringWithoutFirstAndLastLetters = string.substring(1, string.length - 1);
return isPalindrome(stringWithoutFirstAndLastLetters);
} else {
return false;
}
}
console.log(
getAllSubstrings('kayak').filter(str => str.length >= 2 && isPalindrome(str))
);
Here's an answer similar to that from CertainPerformance, but using recursion for the helper functions:
const getSubstrings = (str) =>
str .length == 0
? []
: [
... str .split ('') .map ((_, i) => str .slice (0, str .length - i)),
... getSubstrings (str .slice (1))
]
const isPalindrome = (str) =>
str .length < 2
? true
: str [0] === str .slice (-1) [0] && isPalindrome (str .slice (1, -1))
const getPalindromicSubstrings = (str) =>
getSubstrings (str)
.filter (s => s.length > 1)
.filter (isPalindrome)
const countPalindromicSubstrings = (str) =>
getPalindromicSubstrings (str) .length
const countUniquePalindromicSubstrings = (str) =>
new Set(getPalindromicSubstrings (str)) .size
console .log (getPalindromicSubstrings ('madamimadam'))
console .log (countPalindromicSubstrings ('madamimadam'))
console .log (countUniquePalindromicSubstrings ('madamimadam'))
.as-console-wrapper {max-height: 100% !important; top: 0}
getSubstrings does just what you'd expect. getSubstrings('abcd') returns ["abcd", "abc", "ab", "a", "bcd", "bc", "b", "cd", "c", "d"].
isPalindrome says that the empty string and single-character strings are automatically palindromes and that for another string we check that the two end characters match, recurring on the remainder.
getPalindromicSubstrings finds all the substrings that are palindromes, skipping those of length 1.
countPalindromicSubstrings returns a count of those.
countUniquePalindromicSubstrings uses a Set to filter out duplicates and returns that count.
We could also easily write a getUniquePalindromicSubstrings in a similar manner if needed.
getSubstrings is the only function with any complexity. It operates by repeatedly slicing our string from to a value varying from length down to 1, then recurring on the string starting with the second character, stopping when our input is empty.
I want to write a function that inserts dashes (' - ') between each two odd numbers and inserts asterisks (' * ') between each two even numbers. For instance:
Input: 99946
Output: 9-9-94*6
Input: 24877
Output: 2*4*87-7
My try
function dashAst (para) {
let stringArray = para.toString().split('');
let numbArray = stringArray.map(Number);
for (let i = 0; i<numbArray.length; i++) {
if (numbArray[i] %2 === 0 && numbArray[i+1] % 2 === 0) {
numbArray.splice(numbArray.indexOf(numbArray[i]), 0, '*')
}
else if (numbArray[i] %2 !== 0 && numbArray[i+1] %2 !== 0) {
numbArray.splice(numbArray.indexOf(numbArray[i]), 0, '-')
}
}
return numbArray
}
When I try to invoke the function it returns nothing. For instance, I tested the splice-command separately and it seems to be correct which makes it even more confusing to me. Thanks to everyone reading, or even helping a beginner out.
Looping through an Array that changes its length during the loop can be very messy (i needs to be adjusted every time you splice). It's easier to create a new result variable:
function dashAst(para) {
const stringArray = para.toString().split('');
const numbArray = stringArray.map(Number);
let result = "";
for (let i = 0; i < numbArray.length; i++) {
const n = numbArray[i], next = numbArray[i + 1];
result += n;
if (n % 2 == next % 2) {
result += n % 2 ? '-' : '*';
}
}
return result;
}
console.log(dashAst(99946)); // "9-9-94*6"
console.log(dashAst(24877)); // "2*4*87-7"
You could map the values by checking if the item and next item have the same modulo and take a separator which is defined by the modulo.
function dashAst(value) {
return [...value.toString()]
.map((v, i, a) => v % 2 === a[i + 1] % 2 ? v + '*-'[v % 2] : v)
.join('');
}
console.log(dashAst(99946)); // 9-9-94*6
console.log(dashAst(24877)); // 2*4*87-7
I hope this helps
var str = '24877';
function dashAst (para) {
let stringArray = para.toString().split('');
let numbArray = stringArray.map(x => parseInt(x));
console.log(numbArray);
var out=[];
for(let i = 0; i < numbArray.length; i++) {
if(numbArray[i] % 2 == 0){
out.push(numbArray[i]);
numbArray[i + 1] % 2 == 0 ? out.push('*') : 0;
}else if(numbArray[i] % 2 != 0) {
out.push(numbArray[i]);
numbArray[i + 1] != undefined ? out.push('-') : 0;
}
}
console.log(out.join(''));
return out;
}
dashAst(str);
I have written this piece of code that returns an array of odd numbers or even numbers depending on which of the arguments are greater than each other.
const number_game = (x, y) => {
// Code here
let numbersArray = [];
if (typeof x === 'number' && typeof y === 'number') {
if (x > y) {
for (let i = y + 1; i < x; i++) {
if (i % 2 === 0) {
numbersArray.push(i);
}
}
}
if (y > x) {
for (let i = x + 1; i < y; i++) {
if (i % 2 === 1) {
numbersArray.push(i);
}
}
}
if (y === x) {
return numbersArray;
}
return numbersArray;
}
else {
return `${x} and ${y} should be numbers`
}
}
console.log(number_game(3,13));
I have tested it with possible cases and it works but it keeps failing a hidden test online saying "expected [ Array(9) ] to deeply equal [ Array(11) ] or expected [ Array(10) ] to deeply equal [ Array(11) ]". I have tried to tweak my solution in different ways but it still didn't work. I want to know what I am doing wrong and how I can correct it.
P.S: A search on deepEquality reveals that "The assert.deepEqual() method tests if two objects, and their child objects, are equal, using the == operator".
I just can't seem to point where the error is specifically.
Only thing I could do was simplify it a bit.
const number_game = (x, y) => {
var isOdd = x > y;
let numbersArray = [];
if (typeof x != "number" || typeof y != "number") {
return "${x} and ${y} should be numbers";
}
for (let i = (isOdd ? y : x) + 1; i < (isOdd ? x : y); i++) {
if (i % 2 === (isOdd ? 0 : 1)) {
numbersArray.push(i);
}
}
return numbersArray;
};
console.log(number_game(13, 3));
So after much inquisitiveness and research, I discovered that the problem specs and the test cases are in conflict. An input of (12, 0) should yield => [2,4,6,8,10] according to the problem specs but that result will return this error as a failed test "expected [ Array(9) ] to deeply equal [ Array(11) ] or expected [ Array(10) ] to deeply equal [ Array(11) ]". However, I observed that when I remove the + 1 from let i = x + 1 and y = x + 1 and I add the equal to sign to i <=x and i <=y termination conditions, the test passes. The adjustment, however, returns this result [ 0, 2, 4, 6, 8, 10 ] which is not correct with the problem specs. This is the code that passed the test:
const number_game = (x, y) => {
// Code here
let numbersArray = [];
if (typeof x === 'number' && typeof y === 'number') {
if (x > y) {
for (let i = y; i <= x; i++) {
if (i % 2 === 0) {
numbersArray.push(i);
}
}
}
else {
for (let i = x; i <= y; i++) {
if (i % 2 === 1) {
numbersArray.push(i);
}
}
}
return numbersArray;
}
else {
return `${x} and ${y} should be numbers`
}
}
I would like to sort an array of strings (in JavaScript) such that groups of digits within the strings are compared as integers not strings. I am not worried about signed or floating point numbers.
For example, the result should be ["a1b3","a9b2","a10b2","a10b11"] not ["a1b3","a10b11","a10b2","a9b2"]
The easiest way to do this seems to be splitting each string on boundaries around groups of digits. Is there a pattern I can pass to String.split to split on character boundaries without removing any characters?
"abc11def22ghi".split(/?/) = ["abc","11","def","22","ghi"];
Or is there another way to compare strings that does not involve splitting them up, perhaps by padding all groups of digits with leading zeros so they are the same length?
"aa1bb" => "aa00000001bb", "aa10bb" => "aa00000010bb"
I am working with arbitrary strings, not strings that have a specific arrangement of digit groups.
I like the /(\d+)/ one liner from Gaby to split the array. How backwards compatible is that?
The solutions that parse the strings once in a way that can be used to rebuild the originals are much more efficient that this compare function. None of the answers handle some strings starting with digits and others not, but that would be easy enough to remedy and was not explicit in the original question.
["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"].sort(function (inA, inB) {
var result = 0;
var a, b, pattern = /(\d+)/;
var as = inA.split(pattern);
var bs = inB.split(pattern);
var index, count = as.length;
if (('' === as[0]) === ('' === bs[0])) {
if (count > bs.length)
count = bs.length;
for (index = 0; index < count && 0 === result; ++index) {
a = as[index]; b = bs[index];
if (index & 1) {
result = a - b;
} else {
result = !(a < b) ? (a > b) ? 1 : 0 : -1;
}
}
if (0 === result)
result = as.length - bs.length;
} else {
result = !(inA < inB) ? (inA > inB) ? 1 : 0 : -1;
}
return result;
}).toString();
Result: "!!,9,9.5,10,a3,a3b,a3b3,a3b20,a3b100,a20,a100,~~"
Another variant is to use an instance of Intl.Collator with the numeric option:
var array = ["a100", "a20", "a3", "a3b", "a3b100", "a3b20", "a3b3", "!!", "~~", "9", "10", "9.5"];
var collator = new Intl.Collator([], {numeric: true});
array.sort((a, b) => collator.compare(a, b));
console.log(array);
I think this does what you want
function sortArray(arr) {
var tempArr = [], n;
for (var i in arr) {
tempArr[i] = arr[i].match(/([^0-9]+)|([0-9]+)/g);
for (var j in tempArr[i]) {
if( ! isNaN(n = parseInt(tempArr[i][j])) ){
tempArr[i][j] = n;
}
}
}
tempArr.sort(function (x, y) {
for (var i in x) {
if (y.length < i || x[i] < y[i]) {
return -1; // x is longer
}
if (x[i] > y[i]) {
return 1;
}
}
return 0;
});
for (var i in tempArr) {
arr[i] = tempArr[i].join('');
}
return arr;
}
alert(
sortArray(["a1b3", "a10b11", "a10b2", "a9b2"]).join(",")
);
Assuming you want to just do a numeric sort by the digits in each array entry (ignoring the non-digits), you can use this:
function sortByDigits(array) {
var re = /\D/g;
array.sort(function(a, b) {
return(parseInt(a.replace(re, ""), 10) - parseInt(b.replace(re, ""), 10));
});
return(array);
}
It uses a custom sort function that removes the digits and converts to a number each time it's asked to do a comparison. You can see it work here: http://jsfiddle.net/jfriend00/t87m2/.
Use this compare function for sorting...
function compareLists(a, b) {
var alist = a.split(/(\d+)/), // Split text on change from anything
// to digit and digit to anything
blist = b.split(/(\d+)/); // Split text on change from anything
// to digit and digit to anything
alist.slice(-1) == '' ? alist.pop() : null; // Remove the last element if empty
blist.slice(-1) == '' ? blist.pop() : null; // Remove the last element if empty
for (var i = 0, len = alist.length; i < len; i++) {
if (alist[i] != blist[i]){ // Find the first non-equal part
if (alist[i].match(/\d/)) // If numeric
{
return +alist[i] - +blist[i]; // Compare as number
} else {
return alist[i].localeCompare(blist[i]); // Compare as string
}
}
}
return true;
}
Syntax
var data = ["a1b3", "a10b11", "b10b2", "a9b2", "a1b20", "a1c4"];
data.sort(compareLists);
alert(data);
There is a demo at http://jsfiddle.net/h9Rqr/7/.
Here's a more complete solution that sorts according to both letters and numbers in the strings
function sort(list) {
var i, l, mi, ml, x;
// copy the original array
list = list.slice(0);
// split the strings, converting numeric (integer) parts to integers
// and leaving letters as strings
for( i = 0, l = list.length; i < l; i++ ) {
list[i] = list[i].match(/(\d+|[a-z]+)/g);
for( mi = 0, ml = list[i].length; mi < ml ; mi++ ) {
x = parseInt(list[i][mi], 10);
list[i][mi] = !!x || x === 0 ? x : list[i][mi];
}
}
// sort deeply, without comparing integers as strings
list = list.sort(function(a, b) {
var i = 0, l = a.length, res = 0;
while( res === 0 && i < l) {
if( a[i] !== b[i] ) {
res = a[i] < b[i] ? -1 : 1;
break;
}
// If you want to ignore the letters, and only sort by numbers
// use this instead:
//
// if( typeof a[i] === "number" && a[i] !== b[i] ) {
// res = a[i] < b[i] ? -1 : 1;
// break;
// }
i++;
}
return res;
});
// glue it together again
for( i = 0, l = list.length; i < l; i++ ) {
list[i] = list[i].join("");
}
return list;
}
I needed a way to take a mixed string and create a string that could be sorted elsewhere, so that numbers sorted numerically and letters alphabetically. Based on answers above I created the following, which pads out all numbers in a way I can understand, wherever they appear in the string.
function padAllNumbers(strIn) {
// Used to create mixed strings that sort numerically as well as non-numerically
var patternDigits = /(\d+)/g; // This recognises digit/non-digit boundaries
var astrIn = strIn.split( patternDigits ); // we create an array of alternating digit/non-digit groups
var result = "";
for (var i=0;i<astrIn.length; i++) {
if (astrIn[i] != "") { // first and last elements can be "" and we don't want these padded out
if (isNaN(astrIn[i])) {
result += astrIn[i];
} else {
result += padOneNumberString("000000000",astrIn[i]);
}
}
}
return result;
}
function padOneNumberString(pad,strNum,left) {
// Pad out a string at left (or right)
if (typeof strNum === "undefined") return pad;
if (typeof left === "undefined") left = true;
var padLen = pad.length - (""+ strNum).length;
var padding = pad.substr(0,padLen);
return left? padding + strNum : strNum + padding;
}
Sorting occurs from left to right unless you create a custom algorithm. Letters or digits are compared digits first and then letters.
However, what you want to accomplish as per your own example (a1, a9, a10) won’t ever happen. That would require you knowing the data beforehand and splitting the string in every possible way before applying the sorting.
One final alternative would be:
a) break each and every string from left to right whenever there is a change from letter to digit and vice versa; &
b) then start the sorting on those groups from right-to-left. That will be a very demanding algorithm. Can be done!
Finally, if you are the generator of the original "text", you should consider NORMALIZING the output where a1 a9 a10 could be outputted as a01 a09 a10. This way you could have full control of the final version of the algorithm.