0

Why output is different in following:

  • 1st scenario: prints

https://appmagic.io/modern/1

https://appmagic.io/modern/1

let urlHash = {};
const rootURL = 'http://tinyurl.com/';

var encode = function(longUrl) {
  let hash = Date.now().toString(36);
   urlHash[hash] = longUrl;
  return `${rootURL}${hash}`
};
var decode = function(shortUrl) {
    return urlHash[shortUrl.substring(rootURL.length)]
};

let url1 = encode("https://appmagic.io/classic/1");
let url2 = encode("https://appmagic.io/modern/1");
console.log(decode(url1));
console.log(decode(url2));
  • 2nd scenario: prints

https://appmagic.io/classic/1

https://appmagic.io/modern/1

let urlHash = {};
const rootURL = 'http://tinyurl.com/';

var encode = function(longUrl) {
  let hash = Date.now().toString(36);
    console.log({hash}); // difference in code
    console.log({hash}); // difference in code
   urlHash[hash] = longUrl;
  return `${rootURL}${hash}`
};
var decode = function(shortUrl) {
    return urlHash[shortUrl.substring(rootURL.length)]
};

let url1 = encode("https://appmagic.io/classic/1");
let url2 = encode("https://appmagic.io/modern/1");
console.log(decode(url1));
console.log(decode(url2));

My guess is:

Since Date.now() gives values in milisecond, without console (IO operation i.e. time consuming sync operations) they get evaluate in nano-second and hash remains same, So the similar output in 1st scenario

But if we are adding console (IO operation i.e. time consuming sync operations) it delay operation for more than millisecond and different output comes in 2nd scenario.

I'm not sure if my perception is correct. Can any-one provide better/correct explanation.

If my guess is right, how can I create collision free fast hash,

thinking to use window.performance.now() but it is also not available in all browsers

Akhilesh Kumar
  • 9,085
  • 13
  • 57
  • 95
  • 2
    Your guess sounds correct. Note that I don't think `hash` is actually a hash. Rather it is simply an encoding. – Ben Aston Feb 19 '20 at 20:15
  • because Date.now() is the same milisecond in your first code. In your second code Date.now() is changed because of your console.log. – db1975 Feb 19 '20 at 20:16
  • @Akhilesh, this is indeed what is happening. Nothing more to add. – trincot Feb 19 '20 at 20:17
  • @db1975, did you read the whole question? You just repeat what they said :) – trincot Feb 19 '20 at 20:17
  • Is there any way, I can get away from console still get correct output? @trincot – Akhilesh Kumar Feb 19 '20 at 20:18
  • 1
    you could take a function like this https://stackoverflow.com/a/1349426/4173464 to generate your hash. – db1975 Feb 19 '20 at 20:21
  • @db1975 url you gave is using Math.random which can create collision I want collision free way as here Date.now always will be different, thinking to use `window.performance.now()` but it is also not available in all browsers – Akhilesh Kumar Feb 19 '20 at 20:28
  • @AkhileshKumar you can combine your Date.now() and Math.random. – db1975 Feb 19 '20 at 20:33
  • 1
    @AkhileshKumar `performance.now` is also usually rounded to millisecond for security reasons. Use a UUID generator like https://www.npmjs.com/package/uuid or https://stackoverflow.com/a/2117523/128165 – Gabriele Petrioli Feb 19 '20 at 20:35
  • But still collision can be there doesn't matter how many combination I try with `Math.random` and `Date.now` @db1975 – Akhilesh Kumar Feb 19 '20 at 20:35
  • you have right collision can be there. you could use a database to count a number every time you call this function. than you have a unique number and can combine with date.now() or everything else. – db1975 Feb 19 '20 at 20:40
  • Thanks for insight @GabrielePetrioli I think https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript/2117523#2117523 can sutisfy my requirement as I can't use library. I'm thinking to use `Date.now() + solution in given url`. together they can make my collision least – Akhilesh Kumar Feb 19 '20 at 20:43
  • Thanks @db1975 but don't have access to any db or global state in solution, I have to find unique hash only through code – Akhilesh Kumar Feb 19 '20 at 20:44
  • Any chance you can use Symbol? You could return the symbol from encode along with the url, then use that to decode. Not sure what your specific requirements are but Symbols will give you the collision-free reference you're looking for. – chazsolo Feb 19 '20 at 20:48
  • 1
    Thanks @chazsolo very true, but as of browser compatibility Symbol does not have true polyfills, so won't be able to use them – Akhilesh Kumar Feb 19 '20 at 20:52
  • 1
    @AkhileshKumar gave this some more thought - you could just simply check whether or not the hash exists in the cache, and if it does then defer to do the hash generation again. A simple do/while iteration could be one way to do that – chazsolo Feb 20 '20 at 17:20

1 Answers1

1

While the comments address your main concern that it's possible to generate the same hash due to the encode function running more than once in the same millisecond, I'd like to leave this here as an example of addressing that by deferring the hashing until a unique key is generated:

function Coder(rootUrl) {
  const urlHash = {}
  
  return {
    encode(longUrl) {
      let hash
      
      do {
        hash = Date.now().toString(36)
      } while (urlHash[hash])
      
      urlHash[hash] = longUrl
      
      return `${rootUrl}${hash}`
    },
    
    decode(shortUrl) {
      return urlHash[shortUrl.substring(rootUrl.length)]
    }
  }
}

// usage example
const { decode, encode } = Coder('http://tinyurl.com/')

const url1 = encode('https://appmagic.io/classic/1')
const url2 = encode('https://appmagic.io/modern/1')

console.log('url1 encoded', url1)
console.log('url2 encoded', url2)

console.log('url1 decoded', decode(url1))
console.log('url2 decoded', decode(url2))

With this you'd only be able to generate one hash per millisecond, but I suppose that isn't a bad trade-off.

chazsolo
  • 7,873
  • 1
  • 20
  • 44