## DEV Community is a community of 753,853 amazing developers

We're a place where coders share, stay up-to-date and grow their careers. elisabethgross for Coderbyte

Posted on • Updated on

Hey everyone! Hope you enjoyed solving last week’s challenge. In case you haven’t seen it, I’ll link last week’s article so you can go check it out.

Here's a popular way to solve last week's challenge:

## Two index approach:

A more optimized solution (one that is possible because the strings of numbers are sorted) involves initializing two indexes at the start of both strings. Check to see if the element at the index in the first string is equal to, less than, or greater than the element at the index in the second string. If they are equal, push the value to the result array. Because the strings are sorted, if the element in the first string is less than the element in the second string, you can be sure the first element doesn’t exist in the second string. Therefore, you can increment the first index to look at the next value. If the element in the first string is greater than the element in the second string, you can be sure that the value in the second string doesn’t exist in the first string and can increment the second index to look at the next value. This might be clearer to see in code!

`````` function intersection (arr) {
const inBothArrays = []
const [arr1, arr2] = arr.map((str) => str.split(', ').map((e) => parseInt(e)))

let index1 = 0
let index2 = 0

while (index1 < arr1.length && index2 < arr2.length) {
const elem1 = arr1[index1]
const elem2 = arr2[index2]

if (elem1 === elem2) {
inBothArrays.push(elem1)
index1++
index2++
} else if (elem1 > elem2) {
index2++
} else if (elem1 < elem2) {
index1++
}
}

return inBothArrays.join(',')
}
``````

So for the example:

Calling `intersection([“3, 4, 7, 11, 15”, “1, 3, 5, 8, 11”]);` your function should return `“3,11”`.

Here is an illustration that might make this a little clearer. Remember, this solution only works because the arrays are sorted. The time complexity of this solution is `O(n+m)`.

## This week's challenge:

For this week, we'll be solving a coding problem that was given in an actual Google phone screen interview. Remember to head over to Coderbyte to submit your code!

Write a function that takes an array containing two strings where each string represents keypresses separated by commas. For this problem, a keypress can be either a printable character or a backspace (represented by `-B`). Your function should determine if the two strings of keypresses are equivalent.

You can produce a printable string from such a string of keypresses by having backspaces erase one preceding character. Consider two strings of keypresses equivalent if they produce the same printable string. For example:

``````checkEquivalentKeypresses(["a,b,c,d", "a,b,c,c,-B,d"]) // true
checkEquivalentKeypresses(["-B,-B,-B,c,c", "c,c"]) // true
checkEquivalentKeypresses(["", "a,-B,-B,a,-B,a,b,c,c,c,d"]) // false

``````

Have fun and you got this!!

We’re going to be sending out a small, feature reveal snippet every time we release something big, so our community is the first to know when we break out something new. Give us your email here and we'll add you to our "first to know" list :)

## Discussion (40) Small code review regarding the solution: I would refrain from using the term "pointers" - both on explanation and implementation - since the solution does not use actual pointers. Suggestion: rename to pivots (or indexes) instead. Renato Byrro

Agree, most appropriate term would be `index`. `Pointer` reminds me of memory allocation. elisabethgross

Good point! :) Not the fastest algorithm but this is what I came up with.

``````"use strict";

const normalize = (xs, x) => x === "-B" ? xs.slice(0, -1) : [...xs, x];

const checkEquivalentKeypresses = ([xs, ys]) =>
xs.split(",").reduce(normalize, []).join(",")
===
ys.split(",").reduce(normalize, []).join(",")

console.log(checkEquivalentKeypresses(["a,b,c,d", "a,b,c,c,-B,d"])); // true
console.log(checkEquivalentKeypresses(["-B,-B,-B,c,c", "c,c"])); // true
console.log(checkEquivalentKeypresses(["", "a,-B,-B,a,-B,a,b,c,c,c,d"])); // false
`````` I like this solution as it is very straight forward. Defining normalize was great going in. elisabethgross

Good work! Firee80

This solution allows n-amount of inputs and uses Set to check all items are the same.

``````function checkEquivalentKeypresses(...keyPressLines) {
const keyPressArrays = keyPressLines.map(keyPressLine => keyPressLine.split(','))
const keyPressArraysWithoutBackspace = keyPressArrays.map(array =>
array.reduce((result, letter) => letter === '-B' ? [...result].slice(0, -1) : [...result, letter], []))
const keyPressLinesWithoutBackspace = keyPressArraysWithoutBackspace.map(array => array.join(''))
return keyPressLines.length > 1 ? (new Set(keyPressLinesWithoutBackspace)).size === 1 : false
}

console.log(checkEquivalentKeypresses(...['a,b,c,d', 'a,b,c,c,-B,d'])) // true
console.log(checkEquivalentKeypresses(...['-B,-B,-B,c,c', 'c,c'])) // true
console.log(checkEquivalentKeypresses(...['', 'a,-B,-B,a,-B,a,b,c,c,c,d'])) // false
`````` Firee80

made the function to support also other data types of input..
changed naming and simplified return statement (new Set() is not ran if result.length > 1 fails first).

``````function checkEquivalentKeypresses(...lines) {
const linesSplit = lines.some(line => typeof(line) !== 'string') ? [] : lines.map(line => line.split(','))
const linesNoBackspace = linesSplit.map(lineArray =>
lineArray.reduce((result, key) => key === '-B' ? [...result].slice(0, -1) : [...result, key], []))
const result = linesNoBackspace.map(array => array.join(''))
return result.length > 1 && (new Set(result)).size === 1
}

console.log(checkEquivalentKeypresses(...['a,b,c,d', 'a,b,c,c,-B,d'])) // true
console.log(checkEquivalentKeypresses(...['-B,-B,-B,c,c', 'c,c'])) // true
console.log(checkEquivalentKeypresses(...['', 'a,-B,-B,a,-B,a,b,c,c,c,d'])) // false
console.log(checkEquivalentKeypresses(NaN,{})) // false
console.log(checkEquivalentKeypresses('a,b,c','a,b,c,d,-B')) // true
`````` elisabethgross

Nice! Erick Hagstrom • Edited on

I'm learning clojure, which isn't supported by Coderbyte :-(

Here's my clojure solution:

``````(ns challenge20191202.core
(:gen-class)
(:require [clojure.string :as str]))

(defn check-equivalent-keypresses [v]
(defn parse-string [s]
(vec (str/split s #",")))

(defn apply-keypresses [v s]
(if (= s "-B")
(vec (drop-last v))
(conj v s)))

(let [[s1 s2] v
v1 (str/split s1 #",")
v2 (str/split s2 #",")]
(= (reduce apply-keypresses '[] v1)
(reduce apply-keypresses '[] v2))))
`````` elisabethgross • Edited on

One of the benefits of having a close relationship with our community is hearing what you all want and building it! Like this comment if you'd be interested in Coderbyte supporting clojure! Stack solution using JavaScript array methods push and pop. O(n).

``````const checkEquivalentKeypresses = array => {
const array1 = array.split(",");
const array2 = array.split(",");

const firstString = [];
const secondString = [];

array1.forEach(char => {
if (char === "-B") {
firstString.pop();
} else {
firstString.push(char);
}
});

array2.forEach(char => {
if (char === "-B") {
secondString.pop();
} else {
secondString.push(char);
}
});

return firstString.join() === secondString.join();
};
`````` Olexiy

You can try to go in backward direction whule comparing char by char without strings creation. In best case you will determine that strings are different at first iteration. At worst - iterate for max(n, m) times. And in any case you will only need constant extra memory allocation Tails128 • Edited on

I think a stack-based solution is the most efficient and readable method, to be fair...

If we think about the complexity it should be around O(n)
(n (to compute first) + n (to compute second) + n(to go once over both))
Which is ofc not as good as the O(log(n)), but I am not sure there's a way to cut times.

Also we can do a sneaky trick such as

``````if(stack1.length !== stack2.length) {
return false;
}
``````

But... to be 100% correct, in order to determine if this is helpful or just added time for our average case we would need to know the context a bit more in depth. elisabethgross

Love the stack based approach! rascanoo • Edited on
```Using Regex:

const check = arr => {
let regex = /[a-z]-B/g

let [x, y] = arr.map(elm => {
elm = elm.replace(/,/g, '');

while (regex.test(elm)) {
elm = elm.replace(regex, '');
}

return elm.replace(/-B/g, '');
});

return x === y;
}

console.log(check(["a,b,c,d", "a,b,c,c,-B,d"])); // true
console.log(check(["-B,-B,-B,c,c", "c,c"])); // true
console.log(check(["", "a,-B,-B,a,-B,a,b,c,c,c,d"])); // false
console.log(check(["a,a,a,-B,-B,c", "a,c"])); //true
``` Simon Landry

I'm not convinced this works with every case; You're missing some edge cases such as

``````['a,a,a,-B,-B,c', 'a,c']
`````` O(s+b) is good. s for small( to search for) and b for big (to search in). But I guess, using O(s log b) is a better approach, esp if b>>s. Instead of the linear search, going with a modified binary search that indicates whether the element being searched is outside the other array's bounds would be much faster. In that case, you can get rid of the condition of the smaller array being sorted. Scott Yeatts • Edited on

Not the "code golf" solution, but I think is more readable and avoids nested looping (plus I think the ternaries are more readable once they get a little long using multiple lines).

"Clever" solutions are fun... maintainable ones save time :D

If you wanted to split this out to handle multiple cases or non-compliant arrays (say with spaces or non-string content) I would add additional functions to handle those cases, but not as the spec exists now.

Localization for non-English languages could be handled by adding a language code param to the function or a constant.

``````function EquivalentKeypresses(strArr) {

// code goes here
const comparatorString = strArr.split(',')
.reduce((agg, current) => {
return current === '-B'
? agg.substring(0, agg.length - 1)
: agg + current;
}, '');

return strArr.localeCompare(comparatorString, 'en', {ignorePunctuation: true}) === 0;
}

// keep this function call here
`````` Scott Yeatts • Edited on

Changed, only cause when I submitted on coderbyte, the test cases showed that "-B" cases were possible in the first string (IE: comparing vs an edited string as opposed to a base-string, so... egg on my face haha)

(And I edited again... because code review matters, and there's code to be removed...)

Here's the modified solution:

``````function compare(str) {
return str.split(',')
.reduce((agg, current) => {
return current === '-B'
? agg.substring(0, agg.length - 1)
: agg + current;
}, '');
}

function EquivalentKeypresses(strArr) {

// code goes here
return compare(strArr).localeCompare(compare(strArr),'en',{ignorePunctuation: true}) === 0;
}

// keep this function call here
`````` Just compare starting from the end of the strings. When you see "-B" (possibly consecutively), you know you can skip stuff. O(n) time and O(1) space.

The fact that backspace is two characters and the keypresses are separated by commas is gross. It would be so much nicer (and realistic) for the input be an ASCII string with literal backspace characters. Important thing when just skipping:
lets say we have "a,c,-B,-B" and we start from the back: we see a -B, so we skip the next character, now there is a c and then an a so we get ca (reversed), which is wrong. You need to count the -B s that are back to back and then remove as many "chars". also agree, real chars would be far better, also returning literal "true" and "false" hurts a lot karpour

Got this recommended via Google and gave it a try :)

``````"use strict"

function checkEquivalentKeypresses(input) {
// Reverse arrays for easier processing
let a = input.split(",").reverse();
let b = input.split(",").reverse();
// Function to return index of the next not-deleted character
let nextChar = (arr, idx) => {
let bc = 0; // Backspace counter
let i = idx; // Start index
// Loop until a regular character is found with bc=0
// If the character at idx is a regular character, the loop is skipped
for (; i < arr.length && !(bc == 0 && arr[i].length == 1); i++) {
bc += (arr[i].length > 1 ? 1 : -1);
}
// If bc is 0 and the character at i is defined and not a backspace, return that i
// Otherwise it means the end of the array is reached, return undefined
return (bc == 0 && arr[i] != undefined && arr[i].length == 1) ? i : undefined;
}
let nextcharA = nextChar(a, 0); // Get first character
let nextcharB = nextChar(b, 0); // Get first character
// Loop as long as the characters are the same
for (let i = 0; !(nextcharA == undefined || nextcharB == undefined) && b[nextcharB] == a[nextcharA]; i++) {
nextcharA = nextChar(a, nextcharA + 1);
nextcharB = nextChar(b, nextcharB + 1);
//console.log("a: " + a[nextcharA]);
//console.log("b: " + b[nextcharB]);
}
// If both strings are equivalent, they will get set to undefined at the same point

return nextcharA == undefined && nextcharB == undefined;
}
`````` elisabethgross

Nice! Glad you found us :) elisabethgross

Enjoy!!