Convert an alphabetic string into digits and recursively sum them so they're below a certain max

StackOverflow https://stackoverflow.com/questions/22509726

Question

Here's an arithmetical puzzler for you StackOverflowers. I'm working on a JS game that'll allow someone to enter their name and will generate another name based on what they enter. Sort of like this. Basically every time someone enters a particular string, I want to generate the same other string by taking items from a list.

So I have an array of words, numbering 20 in this example

"nouns": [
    "Nitwit",
    "Cretin",
    "Village Idiot"
    // ..
]

When the user inputs their name, I convert each ASCII alphabetic character to a digit. I'm going to add up all the resulting digits and use the total to select one of the words from my array.

// Convert alpha chars to numerical equivalents
function str2num (mystr) {

    mystr = mystr.toUpperCase();
    var conv = [],
        l = mystr.length,
        regex = /^[A-Za-z]+$/;

    for (var i = 0; i < l; i++) {
        if (regex.test(mystr.charAt(i))) {
            conv.push(mystr.charCodeAt(i) - 64);
        }
    }

    var c = conv.length,
        sum = 0;
    for (var j = 0; j < c; j++) {
        sum += conv[j];
    }
    return sumDigits(sum);

}

Since there are only 20 elements in the word array, I always want the sum to be less than 20. So if it's equal or greater than 20, I want to add up its digits again. This is how I'm currently doing it.

// Recursively add digits of number together
// till the total is less than length of word list
function sumDigits (number) {

    var listLength = adjectives.length;
    var sum = number % listLength;

    if (number > listLength) {
        var remainder = Math.floor(number / 10);
        sum += sumDigits(remainder);
    }

    if (sum > listLength) {
        sum = sumDigits(sum);
    }

    return sum;
}

And when I've got a result below 20, I return the value of nouns[sum] to the user. This code pretty much works - the resulting sum is always below the maximum allowed. But the result isn't very random - it seems that I'm getting a disproportionate number of sums in the lower end of the 0 to 20 range. I don't want users to keep seeing words from the beginning of my list. Is there any change I can make to sumDigits that will ensure an even spread of results? Or is this already correct? (I've done this JSFiddle to demo what I'm talking about.)

Était-ce utile?

La solution

I would make it dependent on the charcode of the characters in the name:

function getValue(name) {
    var letters = name.toLowerCase().split(''), 
        value = 0,
        i = 0;
    for(; i < letters.length; i ++) {
        value += letters[i].charCodeAt(0);
    }
    return value % 20;
}

Autres conseils

The output sum in your current implementation is indeed not uniformly distributed.

As an example, consider all the numbers with one or two digits (1 - 99):

1 of them is  summed up to  1 ( 1)
2 of them are summed up to  2 ( 2, 20)
3 of them are summed up to  3 ( 3, 30, 21)
4 of them are summed up to  4 ( 4, 40, 31, 22)
5 of them are summed up to  5 ( 5, 50, 41, 32, 23)
6 of them are summed up to  6 ( 6, 60, 51, 42, 33, 24)
7 of them are summed up to  7 ( 7, 70, 61, 52, 43, 34, 25)
8 of them are summed up to  8 ( 8, 80, 71, 62, 53, 44, 35, 26)
9 of them are summed up to  9 ( 9, 90, 81, 72, 63, 54, 45, 36, 27)
9 of them are summed up to 10 (10, 91, 82, 73, 64, 55, 46, 37, 28)
9 of them are summed up to 11 (11, 92, 83, 74, 65, 56, 47, 38, 29)
8 of them are summed up to 12 (12, 93, 84, 75, 66, 57, 48, 39)
7 of them are summed up to 13 (13, 94, 85, 76, 67, 58, 49)
6 of them are summed up to 14 (14, 95, 86, 77, 68, 59)
5 of them are summed up to 15 (15, 96, 87, 78, 69)
4 of them are summed up to 16 (16, 97, 88, 79)
3 of them are summed up to 17 (17, 98, 89)
2 of them are summed up to 18 (18, 99)
1 of them is  summed up to 19 (19)

This is probably closer to Normal Distribution than to Uniform Distribution.

In order to achieve the latter, simply return sum % c instead of sumDigits(sum).

I think sumDigits function returns a biased number. Do not return sumDigits(sum). Instead you can return sum % 20.

fzzle's answer is a good one.

Alternatively, you could do a sum reduction with a while:

var reduced = sum;
while(reduced > 20){
    reduced -= name.length;
}

This is a more complete example:

var sum = 0;
var name = 'John Smith'.split('');

for(var k in name){
    sum += name[k].charCodeAt(0);
}

var key = sum;
while(key > 20){
    key -= 'John Smith'.length;
}

If you test it you'll see it produced a different results to sumDigits % 20. Mind you, I don't know how this method will behave with unusually long names. Let me test it.

Confirmed. XD I tried with John Smith John Smith John Smith John Smithxx and broke it. Don't think this qualifies as an answer now. :(

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top