206 lines
24 KiB
Markdown
206 lines
24 KiB
Markdown
|
---
|
||
|
id: 5900f3cf1000cf542c50fee1
|
||
|
title: 'Problem 98: Anagramic squares'
|
||
|
challengeType: 5
|
||
|
forumTopicId: 302215
|
||
|
dashedName: problem-98-anagramic-squares
|
||
|
---
|
||
|
|
||
|
# --description--
|
||
|
|
||
|
By replacing each of the letters in the word CARE with 1, 2, 9, and 6 respectively, we form a square number: $1296 = 36^2$. What is remarkable is that, by using the same digital substitutions, the anagram, RACE, also forms a square number: $9216 = 96^2$. We shall call CARE (and RACE) a square anagram word pair and specify further that leading zeroes are not permitted, neither may a different letter have the same digital value as another letter.
|
||
|
|
||
|
Using the `words` array, find all the square anagram word pairs (a palindromic word is NOT considered to be an anagram of itself).
|
||
|
|
||
|
What is the largest square number formed by any member of such a pair?
|
||
|
|
||
|
**Note:** All anagrams formed must be contained in the given `words` array.
|
||
|
|
||
|
# --hints--
|
||
|
|
||
|
`anagramicSquares(['CARE', 'RACE'])` should return a number.
|
||
|
|
||
|
```js
|
||
|
assert(typeof anagramicSquares(['CARE', 'RACE']) === 'number');
|
||
|
```
|
||
|
|
||
|
`anagramicSquares(['CARE', 'RACE'])` should return `9216`.
|
||
|
|
||
|
```js
|
||
|
assert.strictEqual(anagramicSquares(['CARE', 'RACE']), 9216);
|
||
|
```
|
||
|
|
||
|
`anagramicSquares(testWords1)` should return `4761`.
|
||
|
|
||
|
```js
|
||
|
assert.strictEqual(anagramicSquares(_testWords1), 4761);
|
||
|
```
|
||
|
|
||
|
`anagramicSquares(testWords2)` should return `18769`.
|
||
|
|
||
|
```js
|
||
|
assert.strictEqual(anagramicSquares(_testWords2), 18769);
|
||
|
```
|
||
|
|
||
|
# --seed--
|
||
|
|
||
|
## --after-user-code--
|
||
|
|
||
|
```js
|
||
|
const _testWords1 = [
|
||
|
"DAMAGE","DANGER","DANGEROUS","DARK","DATA","DATE","DAUGHTER","DAY","DEAD","DEAL","DEATH","DEBATE","DEBT","DECADE","DECIDE","DECISION","DECLARE","DEEP","DEFENCE","DEFENDANT","DEFINE","DEFINITION","DEGREE","DELIVER","DEMAND","DEMOCRATIC","DEMONSTRATE","DENY","DEPARTMENT","DEPEND","DEPUTY","DERIVE","DESCRIBE","DESCRIPTION","DESIGN","DESIRE","DESK","DESPITE","DESTROY","DETAIL","DETAILED","DETERMINE","DEVELOP","DEVELOPMENT","DEVICE","DIE","DIFFERENCE","DIFFERENT","DIFFICULT","DIFFICULTY","DINNER","DIRECT","DIRECTION","DIRECTLY","DIRECTOR","DISAPPEAR","DISCIPLINE","DISCOVER","DISCUSS","DISCUSSION","DISEASE","DISPLAY","DISTANCE","DISTINCTION","DISTRIBUTION","DISTRICT","DIVIDE","DIVISION","DO","DOCTOR","DOCUMENT","DOG","DOMESTIC","DOOR","DOUBLE","DOUBT","DOWN","DRAW","DRAWING","DREAM","DRESS","DRINK","DRIVE","DRIVER","DROP","DRUG","DRY","DUE","DURING","DUTY","LABOUR","LACK","LADY","LAND","LANGUAGE","LARGE","LARGELY","LAST","LATE","LATER","LATTER","LAUGH","LAUNCH","LAW","LAWYER","LAY","LEAD","LEADER","LEADERSHIP","LEADING","LEAF","LEAGUE","LEAN","LEARN","LEAST","LEAVE","LEFT","LEG","LEGAL","LEGISLATION","LENGTH","LESS","LET","LETTER","LEVEL","LIABILITY","LIBERAL","LIBRARY","LIE","LIFE","LIFT","LIGHT","LIKE","LIKELY","LIMIT","LIMITED","LINE","LINK","LIP","LIST","LISTEN","LITERATURE","LITTLE","LIVE","LIVING","LOAN","LOCAL","LOCATION","LONG","LOOK","LORD","LOSE","LOSS","LOT","LOVE","LOVELY","LOW","LUNCH"
|
||
|
];
|
||
|
const _testWords2 = [
|
||
|
"A","ABILITY","ABLE","ABOUT","ABOVE","ABSENCE","ABSOLUTELY","ACADEMIC","ACCEPT","ACCESS","ACCIDENT","ACCOMPANY","ACCORDING","ACCOUNT","ACHIEVE","ACHIEVEMENT","ACID","ACQUIRE","ACROSS","ACT","ACTION","ACTIVE","ACTIVITY","ACTUAL","ACTUALLY","ADD","ADDITION","ADDITIONAL","ADDRESS","ADMINISTRATION","ADMIT","ADOPT","ADULT","ADVANCE","ADVANTAGE","ADVICE","ADVISE","AFFAIR","AFFECT","AFFORD","AFRAID","AFTER","AFTERNOON","AFTERWARDS","AGAIN","AGAINST","AGE","AGENCY","AGENT","AGO","AGREE","AGREEMENT","AHEAD","AID","AIM","AIR","AIRCRAFT","ALL","ALLOW","ALMOST","ALONE","ALONG","ALREADY","ALRIGHT","ALSO","ALTERNATIVE","ALTHOUGH","ALWAYS","AMONG","AMONGST","AMOUNT","AN","ANALYSIS","ANCIENT","AND","ANIMAL","ANNOUNCE","ANNUAL","ANOTHER","ANSWER","ANY","ANYBODY","ANYONE","ANYTHING","ANYWAY","APART","APPARENT","APPARENTLY","APPEAL","APPEAR","APPEARANCE","APPLICATION","APPLY","APPOINT","APPOINTMENT","APPROACH","APPROPRIATE","APPROVE","AREA","ARGUE","ARGUMENT","ARISE","ARM","ARMY","AROUND","ARRANGE","ARRANGEMENT","ARRIVE","ART","ARTICLE","ARTIST","AS","ASK","ASPECT","ASSEMBLY","ASSESS","ASSESSMENT","ASSET","ASSOCIATE","ASSOCIATION","ASSUME","ASSUMPTION","AT","ATMOSPHERE","ATTACH","ATTACK","ATTEMPT","ATTEND","ATTENTION","ATTITUDE","ATTRACT","ATTRACTIVE","AUDIENCE","AUTHOR","AUTHORITY","AVAILABLE","AVERAGE","AVOID","AWARD","AWARE","AWAY","AYE","BABY","BACK","BACKGROUND","BAD","BAG","BALANCE","BALL","BAND","BANK","BAR","BASE","BASIC","BASIS","BATTLE","BE","BEAR","BEAT","BEAUTIFUL","BECAUSE","BECOME","BED","BEDROOM","BEFORE","BEGIN","BEGINNING","BEHAVIOUR","BEHIND","BELIEF","BELIEVE","BELONG","BELOW","BENEATH","BENEFIT","BESIDE","BEST","BETTER","BETWEEN","BEYOND","BIG","BILL","BIND","BIRD","BIRTH","BIT","BLACK","BLOCK","BLOOD","BLOODY","BLOW","BLUE","BOARD","BOAT","BODY","BONE","BOOK","BORDER","BOTH","BOTTLE","BOTTOM","BOX","BOY","BRAIN","BRANCH","BREAK","BREATH","BRIDGE","BRIEF","BRIGHT","BRING","BROAD","BROTHER","BUDGET","BUILD","BUILDING","BURN","BUS","BUSINESS","BUSY","BUT","BUY","BY","CABINET","CALL","CAMPAIGN","CAN","CANDIDATE","CAPABLE","CAPACITY","CAPITAL","CAR","CARD","CARE","CAREER","CAREFUL","CAREFULLY","CARRY","CASE","CASH","CAT","CATCH","CATEGORY","CAUSE","CELL","CENTRAL","CENTRE","CENTURY","CERTAIN","CERTAINLY","CHAIN","CHAIR","CHAIRMAN","CHALLENGE","CHANCE","CHANGE","CHANNEL","CHAPTER","CHARACTER","CHARACTERISTIC","CHARGE","CHEAP","CHECK","CHEMICAL","CHIEF","CHILD","CHOICE","CHOOSE","CHURCH","CIRCLE","CIRCUMSTANCE","CITIZEN","CITY","CIVIL","CLAIM","CLASS","CLEAN","CLEAR","CLEARLY","CLIENT","CLIMB","CLOSE","CLOSELY","CLOTHES","CLUB","COAL","CODE","COFFEE","COLD","COLLEAGUE","COLLECT","COLLECTION","COLLEGE","COLOUR","COMBINATION","COMBINE","COME","COMMENT","COMMERCIAL","COMMISSION","COMMIT","COMMITMENT","COMMITTEE","COMMON","COMMUNICATION","COMMUNITY","COMPANY","COMPARE","COMPARISON","COMPETITION","COMPLETE","COMPLETELY","COMPLEX","COMPONENT","COMPUTER","CONCENTRATE","CONCENTRATION","CONCEPT","CONCERN","CONCERNED","CONCLUDE","CONCLUSION","CONDITION","CONDUCT","CONFERENCE","CONFIDENCE","CONFIRM","CONFLICT","CONGRESS","CONNECT","CONNECTION","CONSEQUENCE","CONSERVATIVE","CONSIDER","CONSIDERABLE","CONSIDERATION","CONSIST","CONSTANT","CONSTRUCTION","CONSUMER","CONTACT","CONTAIN","CONTENT","CONTEXT","CONTINUE","CONTRACT","CONTRAST","CONTRIBUTE","CONTRIBUTION","CONTROL","CONVENTION","CONVERSATION","COPY","CORNER","CORPORATE","CORRECT","COS","COST","COULD","COUNCIL","COUNT","COUNTRY","COUNTY","COUPLE","COURSE","COURT","COVER","CREATE","CREATION","CREDIT","CRIME","CRIMINAL","CRISIS","CRITERION","CRITICAL","CRITICISM","CROSS","CROWD","CRY","CULTURAL","CULTURE","CUP","CURRENT","CURRENTLY","CURRICULUM","CUSTOMER","CUT","DAMAGE","DANGER","DANGEROUS","DARK","DATA","DATE","DAUGHTER","DAY","DEAD","DEAL","DEATH","DEBATE","DEBT","DECADE","DECIDE","DECISION","DECLARE","DEEP","DEFENCE","DEFENDANT","DEFINE","DEFINITION","DEGREE","DELIVER","DEMAND","DEMOCRATIC","DEMONSTRATE","DENY","DEPARTMENT","DEPEND","DEPUTY","DERIVE","DESCRIBE","DESCRIPTION","DESIGN","DESIRE","DESK","DESPITE","DESTROY","DETAIL","DETAILED","DETERMINE","DEVELOP","DEVELOPMENT","DEVI
|
||
|
];
|
||
|
```
|
||
|
|
||
|
## --seed-contents--
|
||
|
|
||
|
```js
|
||
|
function anagramicSquares(words) {
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Only change code above this line
|
||
|
const testWords1 = [
|
||
|
"DAMAGE","DANGER","DANGEROUS","DARK","DATA","DATE","DAUGHTER","DAY","DEAD","DEAL","DEATH","DEBATE","DEBT","DECADE","DECIDE","DECISION","DECLARE","DEEP","DEFENCE","DEFENDANT","DEFINE","DEFINITION","DEGREE","DELIVER","DEMAND","DEMOCRATIC","DEMONSTRATE","DENY","DEPARTMENT","DEPEND","DEPUTY","DERIVE","DESCRIBE","DESCRIPTION","DESIGN","DESIRE","DESK","DESPITE","DESTROY","DETAIL","DETAILED","DETERMINE","DEVELOP","DEVELOPMENT","DEVICE","DIE","DIFFERENCE","DIFFERENT","DIFFICULT","DIFFICULTY","DINNER","DIRECT","DIRECTION","DIRECTLY","DIRECTOR","DISAPPEAR","DISCIPLINE","DISCOVER","DISCUSS","DISCUSSION","DISEASE","DISPLAY","DISTANCE","DISTINCTION","DISTRIBUTION","DISTRICT","DIVIDE","DIVISION","DO","DOCTOR","DOCUMENT","DOG","DOMESTIC","DOOR","DOUBLE","DOUBT","DOWN","DRAW","DRAWING","DREAM","DRESS","DRINK","DRIVE","DRIVER","DROP","DRUG","DRY","DUE","DURING","DUTY","LABOUR","LACK","LADY","LAND","LANGUAGE","LARGE","LARGELY","LAST","LATE","LATER","LATTER","LAUGH","LAUNCH","LAW","LAWYER","LAY","LEAD","LEADER","LEADERSHIP","LEADING","LEAF","LEAGUE","LEAN","LEARN","LEAST","LEAVE","LEFT","LEG","LEGAL","LEGISLATION","LENGTH","LESS","LET","LETTER","LEVEL","LIABILITY","LIBERAL","LIBRARY","LIE","LIFE","LIFT","LIGHT","LIKE","LIKELY","LIMIT","LIMITED","LINE","LINK","LIP","LIST","LISTEN","LITERATURE","LITTLE","LIVE","LIVING","LOAN","LOCAL","LOCATION","LONG","LOOK","LORD","LOSE","LOSS","LOT","LOVE","LOVELY","LOW","LUNCH"
|
||
|
];
|
||
|
|
||
|
anagramicSquares(testWords1);
|
||
|
```
|
||
|
|
||
|
# --solutions--
|
||
|
|
||
|
```js
|
||
|
function anagramicSquares(words) {
|
||
|
// Based on https://www.mathblog.dk/project-euler-98-anagrams-square-numbers/
|
||
|
function findMaximumSquare(squares, word1, word2) {
|
||
|
let maximumSquare = 0;
|
||
|
|
||
|
for (let i = 0; i < squares.length; i++) {
|
||
|
const length = squares[i].toString().length;
|
||
|
|
||
|
if (length < word1.length) {
|
||
|
continue;
|
||
|
}
|
||
|
if (length > word1.length) {
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
const word1Square = squares[i];
|
||
|
const letterToDigit = mapLettersToDigits(word1, word1Square);
|
||
|
|
||
|
const noProperMappingExist = Object.keys(letterToDigit).length === 0;
|
||
|
if (noProperMappingExist) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const word2Square = getNumberFromMapping(word2, letterToDigit);
|
||
|
if (word2Square === 0) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
const doesWord2SquareExist = squares.indexOf(word2Square) !== -1;
|
||
|
if (doesWord2SquareExist) {
|
||
|
const pairMaximum = Math.max(word1Square, word2Square);
|
||
|
maximumSquare = Math.max(maximumSquare, pairMaximum);
|
||
|
}
|
||
|
}
|
||
|
return maximumSquare;
|
||
|
}
|
||
|
|
||
|
function getNumberFromMapping(word, letterToDigit) {
|
||
|
const wouldNumberHaveLeadingZero = letterToDigit[word[0]] === 0;
|
||
|
if (wouldNumberHaveLeadingZero) {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
let number = 0;
|
||
|
for (let i = 0; i < word.length; i++) {
|
||
|
number = number * 10 + letterToDigit[word[i]];
|
||
|
}
|
||
|
return number;
|
||
|
}
|
||
|
|
||
|
function mapLettersToDigits(word, square) {
|
||
|
const letterToDigit = {};
|
||
|
for (let j = word.length - 1; j >= 0; j--) {
|
||
|
const curDigit = square % 10;
|
||
|
square = Math.floor(square / 10);
|
||
|
|
||
|
const curLetter = word[j];
|
||
|
|
||
|
const isLetterRepeated = letterToDigit.hasOwnProperty(curLetter);
|
||
|
if (isLetterRepeated) {
|
||
|
const isLetterUsedForTheSameDigit =
|
||
|
letterToDigit[curLetter] === curDigit;
|
||
|
if (isLetterUsedForTheSameDigit) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
const isDigitUsed = Object.values(letterToDigit).indexOf(curDigit) !== -1;
|
||
|
if (isDigitUsed) {
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
letterToDigit[curLetter] = curDigit;
|
||
|
}
|
||
|
return letterToDigit;
|
||
|
}
|
||
|
|
||
|
function groupWordsWithSameLetters(words) {
|
||
|
const lettersToWords = {};
|
||
|
for (let i = 0; i < words.length; i++) {
|
||
|
const word = words[i];
|
||
|
const sortedLetters = word.split('').sort().join('');
|
||
|
if (!lettersToWords.hasOwnProperty(sortedLetters)) {
|
||
|
lettersToWords[sortedLetters] = [];
|
||
|
}
|
||
|
lettersToWords[sortedLetters].push(word);
|
||
|
}
|
||
|
return lettersToWords;
|
||
|
}
|
||
|
|
||
|
const lettersToWords = groupWordsWithSameLetters(words);
|
||
|
|
||
|
const anagrams = Object.keys(lettersToWords).filter(
|
||
|
letters => lettersToWords[letters].length > 1
|
||
|
);
|
||
|
const lengthOfLongestAnagram = anagrams
|
||
|
.map(anagram => anagram.length)
|
||
|
.sort((a, b) => b - a)[0];
|
||
|
|
||
|
const squares = [];
|
||
|
const numberLimit = (10 ** lengthOfLongestAnagram) ** 0.5;
|
||
|
for (let number = 2; number < numberLimit; number++) {
|
||
|
const square = number ** 2;
|
||
|
squares.push(square);
|
||
|
}
|
||
|
|
||
|
let largestSquare = 0;
|
||
|
for (let i = 0; i < anagrams.length; i++) {
|
||
|
const curWords = lettersToWords[anagrams[i]];
|
||
|
|
||
|
for (let j = 0; j < curWords.length; j++) {
|
||
|
for (let k = j + 1; k < curWords.length; k++) {
|
||
|
const squareValue = findMaximumSquare(
|
||
|
squares,
|
||
|
curWords[j],
|
||
|
curWords[k]
|
||
|
);
|
||
|
if (squareValue > largestSquare) {
|
||
|
largestSquare = squareValue;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return largestSquare;
|
||
|
}
|
||
|
```
|