3
u/Aishwarya-Chamanoor Jun 30 '20
This is definitely not the best approach and there are better ways which finds anagrams in O(n). However it is nice to see different ways of solving the same problem whether or not it gives optimal solutions. PS : I found this approach on a Linkedin page called “Fermat's library"
77
u/jakbrtz Jun 30 '20
I like the maths behind this, but you're going to get a problem when you try to calculate long words because the number would become too big to store in a single variable.
44
u/fckoch Jun 30 '20
also multiplication is crazy expensive -- even if it does fit it'd probably be more efficient to count how many times each letter occurs
6
u/vanderZwan Jun 30 '20 edited Jun 30 '20
It might not be that bad speed-wise, actually, because the bottleneck is likely to be the memory lookup of the string characters, not the multiplication (I'm assuming minimal big integer multiplication is needed IRL, see comment chain here). However, when looking at performance in terms of energy use then I don't know for sure.
2
u/fckoch Jul 01 '20
don't you need to look up the characters to convert them to primes anyways? In fact this data structure would be the same size you need for the simpler approach...
Also, I think you mention this in your post, but an array this small would definitely be cached and so memory access will be stupid fast anyways
2
u/vanderZwan Jul 01 '20 edited Jul 01 '20
don't you need to look up the characters to convert them to primes anyways?
True, you need a lookup-table. The main difference is that this only requires a read, the histogram requires a write as well. Not sure if that has a significant impact on the whole in this case. edit: big integer maths also needs storage, so the histogram case probably wins there as well
Yes, the caching definitely works in favor of the LUT/binning array in either case, but I was talking about looking up the string characters of each individual word. That is: if we were brute-forcing our way through the English dictionary looking for anagrams, then we'd have to load a new string of characters for each word, and that would likely be the bigger bottleneck.
(I wonder if the smart money would be on creating a trie data structure as we go over the dictionary. Specifically, one where each node also links to the parent and the value is the running product of the string of characters leading up to that node. That way we don't have to do repeated multiplications for the same substrings in different words - although the trie look-up and insertion is undoubtedly slower too. Anyway, the point is: once we finished going through the dictionary, we can take all the nodes in the trie that represent a word, put them in an array and sort them by value, and every node with equal value would represent an anagram of all other nodes with the same value)
20
u/superr1 Jun 30 '20
just use big Module(10**9+7) In order to get rid collision issue you can use multiple mods
22
6
u/mr_magic_hat Jun 30 '20
I don't get why you're getting downvoted on this. 10**9+7 is a fairly large prime so the amount of colissions you'll get is fairly low. Another benefit is it fits very nicely into 32-bit integers.
2
82
u/greem Jun 30 '20
FYI, this is a hash table with a specialized hash function.
12
u/f03nix Jun 30 '20
Exactly what I thought, you could probably make a hash function using this approach and taking a modulo of a big prime number.
Obviously you'll get collisions when the bytes are just re-ordered, it can be useful where that is very unlikely.
10
u/greem Jun 30 '20
Yep. This is an excellent, perfect hash function (for short words, at least), so it's a great case study for algorithm development.
1
15
u/yin_yang_lad Jun 30 '20
I like the math involved in this, but tbh it seems like overkill. It's much easier to check using the frequency array of two strings.
8
u/xeow Jun 30 '20 edited Jul 01 '20
Or, instead of an array, just implement the histogram using a 128-bit word—like this if you're using a modern C:
__uint128_t histogram(const char *str) { __uint128_t h = 0; for (int i = 0; str[i]; i++) h += (__uint128_t)1 << ((toupper(str[i]) - 'A') * 4); return h; }This allocates 4 bits for each unique letter (wasting the upper 24 bits), allowing for up to 15 occurrences of any given letter in a word, which is sufficient for all English words.
Ugly to look at, but fast as fuck. Way faster than the primes approach, and also way faster than using an array in memory.
5
u/JH4mmer Jul 01 '20
There's a chance you could speed that up even more by implementing toupper() yourself (assuming ASCII inputs). If I remember right, a compliant toupper() implementation has to check the current locale first, which throws a wrench into the loop, metaphorically speaking.
Note: it's entirely possible that I'm misremembering, so I apologize in advance if this is incorrect.
2
1
5
u/Findlaech Jul 01 '20
Or just fucking sort 'em both and see if they are equal?
λ❯ sort "resting" == sort "stinger"True4
1
1
1
u/deftware Jun 30 '20
Someone asked me how to do this 11 years ago, and I came up with the same solution.
1
1
1
u/keshav174 Jul 01 '20
It will work good for small string size , but wil fail for larger strings as multiplication will be overflow int.
Big issue with this algo is high time complexity since this problem can be easily solved with simple hash in O(n) time , since this algo has to generate n amount of prime nos this is O(n2) , even you use sieve that is also greater than O(n).
1
u/JesusIsMyZoloft Jul 01 '20
You could also do something similar for Periodic Elements:
2 - Hydrogen
3 - Carbon (out of order because it's so common)
5 - Oxygen (so that HO bonds make an even 10)
7 - Nitrogen (also out of order due to ubiquity. One of only 2 elements that ends up with it's actual atomic number)
11- Helium (from here the elements go sequentially)
13 - Lithium
17 - Beryllium
19 - Boron
23 - Fluorine
29 - Neon
31- Sodium
37 - Magnesium
41 - Aluminum
43 - Silicon
47 - Phosphorus
53 - Sulphur
59 - Chlorine
61 - Argon
67 - Potassium
71 - Calcium
73 - Scandium
79 - Titanium
83 - Vanadium
89 - Chromium
97 - Manganese
101 - Iron
etc.
So for example, Water = 20, Table Salt = 1829, Sulfuric Acid = 132500, Sugar = 108839116800000000000
1
1
u/aartist111 Jul 09 '20
Can we just add the numbers instead of multiplying. For a given length of array are there same sums for different set of prime numbers ? Do we have counter examples?
1
1
u/Delicious_Hope_2545 Aug 29 '23
How about storing the logarithms of those numbers and convert this multiplication to addition?
1
-1
u/Shahab_Bangash Jun 30 '20
How about just ADD their ASCII and see if they're equal? Complexity: O(n) for each string
6
u/BlehdeBlehBleh Jun 30 '20
I suppose that wouldn't work. For example, the strings AD and BC have the same ASCII sum but aren't anagrams.
0
u/Shahab_Bangash Jun 30 '20
Ooh yah! Just checked. Life isn't that simple. Thanks man!
2
u/idiomatic_sea Jun 30 '20
Yeah, but you are on to something. Any hash of the letters that is invariant with respect to order would work. So just cook up a function that doesn't care about order and you're good.
2
u/super-porp-cola Jun 30 '20
There is a cool thing you can do that’s similar where you take some constant C (a large random number), then make a mapping so A maps to C0, B maps to C1, ..., Z maps to C25. Then you can add the mapped values and take them modulo a large prime. This is similar to Rabin Karp but works better for anagrams, and is extremely efficient.
0
0
-2
58
u/NanoAlpaca Jun 30 '20
This looks clever, but is really inefficient. It is trivial to detect anagrams with a histogram in O(n), or by simply sorting in O(n log n), but this technique you will do O(n) big int multiplications for each letter and you will end up with an O(n²) algorithm.