My purpose is to punch multiple strings into a single (shortest) string that will contain all the character of each string in a forward direction. The question is not specific to any language, but more into the algorithm
part. (probably will implement it in a node server, so tagging nodejs/javascript
).
So, to explain the problem:
Let's consider I have few strings
["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
The Resultant string should be something like:
"sjmachppoalidveonrk"
jack: sjmachppoalidveonrk
apple: sjmachppoalidveonrk
solid: sjmachppoalidveonrk
====================================>>>> all in the forward direction
These all are manual evaluation and the output may not 100% perfect in the example.
So, the point is all the letters of each string have to exist in the output in FORWARD DIRECTION (here the actual problem belongs), and possibly the server will send the final strings and numbers like 27594
will be generated and passed to extract the token, in the required end. If I have to punch it in a minimal possible string it would have much easier (That case only unique chars are enough). But in this case there are some points:
Letters can be present multiple time, though I have to reuse any letter if possible, eg: for solid
and hold
o > l > d
can be reused as forward direction but for apple
(a > p
) and spark
(p > a
) we have to repeat a
as in one case it appears before p
for apple
, and after p
for sparks
so either we need to repeat a
or p
. Even, we cannot do p > a > p
as it will not cover both the case because we need two p
after a
for apple
We directly have no option to place a single p
and use the same index twice in a time of extract, we need multiple p
with no option left as the input string contains that
I have tried, by starting with an arbitrary string, and then made an analysis of next string and splitting all the letters, and place them accordingly, but after some times, it seems that current string letters can be placed in a better way, If the last string's (or a previous string's) letters were placed according to the current string. But again that string was analysed and placed based on something (multiple) what was processed, and placing something in the favor of something that is not processed seems difficult because to that we need to process that. Or might me maintaining a tree of all processed/unprocessed tree will help, building the building the final string? Any better way than it, it seems a brute force?
Note: I know there are a lot of other transformation possible, please try not to suggest anything else to use, we are doing a bit research on it.
I came up with a somewhat brute force method. This way finds the optimal way to combine 2 words then does it for each element in the array.
This strategy works by trying finding the best possible way to combine 2 words together. It is considered the best by having the fewest letters. Each word is fed into an ever growing "merged" word. Each time a new word is added the existing word is searched for a matching character which exists in the word to be merged. Once one is found both are split into 2 sets and attempted to be joined (using the rules at hand, no need 2 add if letter already exists ect..). The strategy generally yields good results.
The join_word
method takes 2 words you wish to join, the first parameter is considered to be the word you wish to place the other into. It then searches for the best way to split into
and word
into 2 separate parts to merge together, it does this by looking for any shared common characters. This is where the splits_on_letter
method comes in.
The splits_on_letter
method takes a word and a letter which you wish to split on, then returns a 2d array of all the possible left and right sides of splitting on that character. For example splits_on_letter('boom', 'o')
would return [["b","oom"],["bo","om"],["boo","m"]]
, this is all the combinations of how we could use the letter o
as a split point.
The sort()
at the beginning is to attempt to place like elements together. The order in which you merge the elements generally effects the results length. One approach I tried was to sort them based upon how many common letters they used (with their peers), however the results were varying. However in all my tests I had maybe 5 or 6 different word sets to test with, its possible with a larger, more varying word arrays you might find different results.
Output is
spmjhooarckpplivden
var words = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]; var result = minify_words(words); document.write(result); function minify_words(words) { // Theres a good sorting method somewhere which can place this in an optimal order for combining them, // hoever after quite a few attempts i couldnt get better than just a regular sort... so just use that words = words.sort(); /* Joins 2 words together ensuring each word has all its letters in the result left to right */ function join_word(into, word) { var best = null; // straight brute force each word down. Try to run a split on each letter and for(var i=0;i<word.length;i++) { var letter = word[i]; // split our 2 words into 2 segments on that pivot letter var intoPartsArr = splits_on_letter(into, letter); var wordPartsArr = splits_on_letter(word, letter); for(var p1=0;p1<intoPartsArr.length;p1++) { for(var p2=0;p2<wordPartsArr.length;p2++) { var intoParts = intoPartsArr[p1], wordParts = wordPartsArr[p2]; // merge left and right and push them together var result = add_letters(intoParts[0], wordParts[0]) + add_letters(intoParts[1], wordParts[1]); if(!best || result.length <= best.length) { best = result; } } } } // its possible that there is no best, just tack the words together at that point return best || (into + word); } /* Splits a word at the index of the provided letter */ function splits_on_letter(word, letter) { var ix, result = [], offset = 0;; while((ix = word.indexOf(letter, offset)) !== -1) { result.push([word.substring(0, ix), word.substring(ix, word.length)]); offset = ix+1; } result.push([word.substring(0, offset), word.substring(offset, word.length)]); return result; } /* Adds letters to the word given our set of rules. Adds them starting left to right, will only add if the letter isnt found */ function add_letters(word, addl) { var rIx = 0; for (var i = 0; i < addl.length; i++) { var foundIndex = word.indexOf(addl[i], rIx); if (foundIndex == -1) { word = word.substring(0, rIx) + addl[i] + word.substring(rIx, word.length); rIx += addl[i].length; } else { rIx = foundIndex + addl[i].length; } } return word; } // For each of our words, merge them together var joinedWords = words[0]; for (var i = 1; i < words.length; i++) { joinedWords = join_word(joinedWords, words[i]); } return joinedWords; }
A first try, not really optimized (183% shorter):
function getShort(arr){ var perfect=""; //iterate the array arr.forEach(function(string){ //iterate over the characters in the array string.split("").reduce(function(pos,char){ var n=perfect.indexOf(char,pos+1);//check if theres already a possible char if(n<0){ //if its not existing, simply add it behind the current perfect=perfect.substr(0,pos+1)+char+perfect.substr(pos+1); return pos+1; } return n;//continue with that char },-1); }) return perfect; }
In action
This can be improved trough simply running the upper code with some variants of the array (200% improvement):
var s=["jack",...]; var perfect=null; for(var i=0;i<s.length;i++){ //shift s.push(s.shift()); var result=getShort(s); if(!perfect || result.length<perfect.length) perfect=result; }
In action
Thats quite close to the minimum number of characters ive estimated ( 244% minimization might be possible in the best case)
Ive also wrote a function to get the minimal number of chars and one to check if a certain word fails, you can find them here
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With