JS Algorithms and Data Structures Projects

Palindrome Checker

Could not use \w in the regex as its clear from the tests that the underscore is to be excluded and \w regards underscore as a valid character.

Very simple test as far as I can see as reduceRight() can be used to get the alphanumerics in reverse order.

function palindrome(str) {
  let regex = /[a-zA-Z0-9]/gi;
  let left2right = str.toLowerCase().match(regex).reduce((a,b) => a + b);
  let right2left = str.toLowerCase().match(regex).reduceRight((a,b) => a + b);
  return left2right === right2left;
}
console.log(palindrome("_eye")); //true
console.log(palindrome("A man, a plan, a canal. Panama")); //true
console.log(palindrome("almostomla")); //false
1
2
3
4
5
6
7
8
9

Convert to Roman Numerals

It seemed to me that writing the function the way I did both made it very easy as well as very easy to understand. There are probably really clever ways to do this I did not even think of, but surely this is about as easy to understand as it could get and that's surely a good thing.

function convertToRoman(num) {
  const romanUnits = ["","I","II","III","IV","V","VI","VII","VIII","IX"];       //0-9
  const romanTens = ["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"];        //00-90
  const romanHundreds = ["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"];    //000-900
  const romanThousand = "M";
  let thousands = Math.floor(num / 1000);
  let hundreds = Math.floor((num - thousands * 1000)/100);
  let tens = Math.floor((num - (thousands * 1000) -(hundreds * 100)) /10);
  let units = num - (thousands * 1000) - (hundreds * 100) - (tens * 10);
  let romanThousands = "";
  while(thousands > 0){
    romanThousands += romanThousand;
    thousands--;
  }
  return romanThousands + romanHundreds[hundreds] + romanTens[tens] + romanUnits[units];
}
convertToRoman(36); //XXXVI
convertToRoman(891) //DCCCXCI
convertToRoman(2014) //MMXIV
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Caesar's Cipher

A common modern use is the ROT13 cipher, where the values of the letters are shifted by 13 places. Thus 'A' ↔ 'N', 'B' ↔ 'O' and so on.

Write a function which takes a ROT13 encoded string as input and returns a decoded string.

All letters will be uppercase. Do not transform any non-alphabetic character (i.e. spaces, punctuation), but do pass them on.

function rot13(str) { // LBH QVQ VG!
  let result = "";
  for (let i = 0; i < str.length; i++){
    if (str.charCodeAt(i) >= 65 && str.charCodeAt(i) <= 90){
      if (str.charCodeAt(i) + 13 > 90){
        result += String.fromCharCode(str.charCodeAt(i)-13);
      }
      else {
        result += String.fromCharCode(str.charCodeAt(i)+13);
      }
    }
    else {
      result += str[i];
    }
  }
  return result;
}
// Change the inputs below to test
console.log(rot13("SERR PBQR PNZC")); //FREE CODE CAMP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

US telephone number check

I do not know what are valid area codes and did not want to use an off the shelf regex which obviously exist all over the place, so came up with relatively simple regular expression and then prepended a check to see if there are any brackets and if so that there are the same number of opening and closing brackets. This vastly reduces the complexity of the regular expression as otherwise would need look aheads and look behinds when dealing with brackets. Prefer something simpler.

The regular expression basically looks for the 3-3-4 digits pattern with an optional 1 at the front and either valid separators between the groups or no separator.

function telephoneCheck(str) {
  const openBrackets = /\(/g;
  const closeBrackets = /\)/g;
  let open = str.match(openBrackets);
  let closed = str.match(closeBrackets);
  if (open){
    if (closed){
      if (open.length != closed.length){
        return false;
      }
    }
    else{
      return false;
    }
  }
  else if (closed){
    return false;
  }
  const regex = /^1*[ (]*[2-9]{3}[ )-]*[2-9]{3}[ -]*[2-9]{4}$/;
  return regex.test(str);
}
console.log(telephoneCheck("1 555)-555-5555"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Cash Register

Floating point arithmetic is a problem here and a proper solution would need to deal with it properly either by storing numbers as integers only or more carefully ensuring that decimals are always two places. This was a particular problem with the pennies as subtracting the last but one penny typically left the total amount of change to be returned at something less than one penny, e.g. 0.99999997 or whatever.

function checkCashRegister(price, cash, cid) {
  let change = {status: "CLOSED", change: []};
  let dollarsToReturn = 0;
  let centsToReturn = 0;
  let cashInDrawer = {TOTAL: 0};
  for (let i= 0; i < cid.length; i++){
    cashInDrawer[cid[i][0]] = cid[i][1];
    cashInDrawer.TOTAL += cid[i][1];
  }
  cashInDrawer.TOTAL = Math.round(cashInDrawer.TOTAL*100)/100;
  //at this point have a local object with all of the cash in the drawer and a total
  //the contents of which can be counted out like a human would do it from biggest to
  //smallest
  if (cash > price){
    dollarsToReturn = Math.floor(cash-price);
    centsToReturn = Math.round((cash-price-dollarsToReturn)*100)/100;
  }
  //now have an integer number of dollars to give in change and the remainder
  //in coins, bearing in mind that all change could be given in coins if no notes in the
  //drawer
  if (cashInDrawer.TOTAL > dollarsToReturn+centsToReturn){
    //enough cash in principle, need to check if can give correct change
    if (dollarsToReturn >= 100 && cashInDrawer["ONE HUNDRED"] > 0){
      let hundreds = 0;
      while(cashInDrawer["ONE HUNDRED"] >= 100 && dollarsToReturn >= 100) {
        cashInDrawer["ONE HUNDRED"] -= 100;
        dollarsToReturn -= 100;
        hundreds++;
      }
      change.change.push(["ONE HUNDRED", hundreds*100]);
    }
    if (dollarsToReturn >= 20 && cashInDrawer["TWENTY"] > 0){
      let twenties = 0;
      while(cashInDrawer["TWENTY"] >= 20 && dollarsToReturn >= 20) {
        cashInDrawer["TWENTY"] -= 20;
        dollarsToReturn -= 20;
        twenties++;
      }
      change.change.push(["TWENTY", twenties*20]);
    }
    if (dollarsToReturn >= 10 && cashInDrawer["TEN"] > 0){
      let tens = 0;
      while(cashInDrawer["TEN"] >= 10 && dollarsToReturn >= 10) {
        cashInDrawer["TEN"] -= 10;
        dollarsToReturn -= 10;
        tens++;
      }
      change.change.push(["TEN", tens*10]);
    }
    if (dollarsToReturn >= 5 && cashInDrawer["FIVE"] > 0) {
      let fives = 0;
      while (cashInDrawer["FIVE"] >= 5 && dollarsToReturn >=5) {
        cashInDrawer["FIVE"] -= 5;
        dollarsToReturn -= 5;
        fives++;
      }
      change.change.push(["FIVE", fives*5]);
    }
    if (dollarsToReturn >= 1 && cashInDrawer["ONE"] > 0) {
      let ones = 0;
      while (cashInDrawer["ONE"] >= 1 && dollarsToReturn >=1) {
        cashInDrawer["ONE"]--;
        dollarsToReturn--;
        ones++;
      }
      change.change.push(["ONE", ones]);
    }
    //having exhausted all the notes, add any remaining dollars to return to the 
    //change that needs to be given in coins
    centsToReturn += dollarsToReturn;
    //repeat the process with coins as did it with notes, note that the while statement
    //is not really reliable as it depends on floating point arithmetic, should really
    //do this better
    if (centsToReturn >= 0.25 && cashInDrawer["QUARTER"] > 0) {
      let quarters = 0;
      while (cashInDrawer["QUARTER"] >= 0.25 && centsToReturn >=0.25) {
        cashInDrawer["QUARTER"] -= 0.25;
        centsToReturn -= 0.25;
        quarters++;
      }
      change.change.push(["QUARTER", quarters*0.25]);
    }
    centsToReturn = Math.round(centsToReturn*100)/100;
    if (centsToReturn >= 0.10 && cashInDrawer["DIME"] > 0) {
      let dimes = 0;
      while (cashInDrawer["DIME"] >= 0.1 && centsToReturn >=0.1) {
        cashInDrawer["DIME"] -= 0.1;
        centsToReturn -= 0.1;
        dimes++;
      }
      change.change.push(["DIME", dimes*0.1]);
    }
    centsToReturn = Math.round(centsToReturn*100)/100;
    if (centsToReturn >= 0.05 && cashInDrawer["NICKEL"] > 0) {
      let nickels = 0;
      while (cashInDrawer["NICKEL"] >= 0.05 && centsToReturn >=0.0049) {
        cashInDrawer["NICKEL"] -= 0.05;
        centsToReturn -= 0.05;
        nickels++;
      }
      change.change.push(["NICKEL", nickels*0.05]);
    }
    centsToReturn = Math.round(centsToReturn*100)/100;
    if (centsToReturn >= 0.01 && cashInDrawer["PENNY"] > 0) {
      let pennies = 0;
      while (cashInDrawer["PENNY"] >= 0.01 && centsToReturn >=0.009) {
        cashInDrawer["PENNY"] -= 0.01;
        centsToReturn -= 0.01;
        pennies++;
      }
      change.change.push(["PENNY", pennies*0.01]);
    }
    centsToReturn = Math.round(centsToReturn*100)/100;
    if (centsToReturn) {
      //not all change can be returned
      change.change = [];
      change.status = "INSUFFICIENT_FUNDS";
    }
    else{
      //correct change can be returned
      change.status = "OPEN";      
    }
  }
  else if(cashInDrawer.TOTAL == dollarsToReturn+centsToReturn) {
    //exact change can be provided with whole contents of drawer
      change.status = "CLOSED";
      change.change = cid;
  }
  else {
      //cannot give change as not enough cash in the drawer
      change.status = "INSUFFICIENT_FUNDS";
  }
  return change;
}
checkCashRegister(19.5, 20, [["PENNY", 0.5], ["NICKEL", 0], ["DIME", 0], ["QUARTER", 0], ["ONE", 0], ["FIVE", 0], ["TEN", 0], ["TWENTY", 0], ["ONE HUNDRED", 0]])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135