John Crichton
John Crichton

Reputation: 77

Shuffling array properties in JavaScript

I have a data dictionary like this:

var data = {
    'text1': 1,
    'text2': 2,
    'text3': 3,
    ...
    'text20': 20
];

I need to pick a random selection of those keys and then shuffle it's values. In the example, it should write something like this:

> console.log(choose(data, 5));
[ { key: 'text15', value: 8 },
{ key: 'text6', value: 3 },
{ key: 'text3', value: 15 },
{ key: 'text19', value: 6 },
{ key: 'text8', value: 19 } ]

For now I'm extracting the keys into another array and sorting by Math.random() but I'm stuck at swaping the values because no key should have the same value it initially had.

How would you swap key/values here?

Thanks

Upvotes: 1

Views: 1506

Answers (5)

Adam Bergmark
Adam Bergmark

Reputation: 7536

Use an implementation of random that randomizes a discrete set of values, such as Math.rand seen here. For each index, randomize Math.rand(index, length-1) to get a list of random indexes, the location off all indices will change.

Upvotes: 0

Tabares
Tabares

Reputation: 4335

It uses a combination of three functions (including the Array shuffle prototype method).

Here is the complete code:

var obj = {
    "red":"RED",
    "blue":"BLUE",
    "green":"GREEN",
    "yellow":"YELLOW",
    "purple":"PURPLE"
};

Array.prototype.shuffle = function(){
    for (var i = 0; i < this.length; i++){
        var a = this[i];
        var b = Math.floor(Math.random() * this.length);
        this[i] = this[b];
        this[b] = a;
    }
}

obj = shuffleProperties(obj); // run shuffle

function shuffleProperties(obj) {
    var new_obj = {};
    var keys = getKeys(obj);
    keys.shuffle();
    for (var key in keys){
        if (key == "shuffle") continue; // skip our prototype method
        new_obj[keys[key]] = obj[keys[key]];
    }
    return new_obj;
}

function getKeys(obj){
    var arr = new Array();
    for (var key in obj)
        arr.push(key);
    return arr;
}


for(key in obj){
   alert(key);
}  

Check all post, Best Regards.

Upvotes: 0

RobG
RobG

Reputation: 147413

Been a while since this was answered, but I was working on shuffling and found the following to be by far the fastest implementation with an evenly random distribution.

It's fast because it only makes one call to Math.random on each iteration, all the rest is done by property access. It doesn't modify the array, just reassigns values.

  function shuffle(a) {
    var t, j, i=a.length, rand=Math.random;

    // For each element in the array, swap it with a random
    // element (which might be itself)
    while (i--) {
      k = rand()*(i+1)|0;
      t = a[k];
      a[k]=a[i];
      a[i]=t;
    }
    return a;
  }

Upvotes: 0

Gumbo
Gumbo

Reputation: 655309

You could do this:

  • collect names and corresponding values in two arrays names and values
  • shuffle both arrays independently of each other
  • take the first n items of both arrays and combine them

Here’s an example implementation:

Array.prototype.shuffle = function() {
    for (var i=this.length-1, j, tmp; i>0; i--) {
        j = Math.round(Math.random()*i);
        tmp = this[i], this[i] = this[j], this[j] = tmp;
    }
    return this;
};

function choose(data, number) {
    var names = [], values = [], pick = [];
    for (var name in data) {
        if (data.hasOwnProperty(name)) {
            names.push(name);
            values.push(data[name]);
        }
    }
    names = names.shuffle(), values = values.shuffle();
    for (var i=Math.min(number >>> 0, names.length-1); i>=0; i--) {
        pick.push({key: names[i], value: values[i]});
    }
    return pick;
}

Upvotes: 1

vitch
vitch

Reputation: 3214

I put together a possible solution using underscore.js to simplify traversing the object and arrays in a cross browser manner:

var data = {
    text1: 1,
    text2: 2,
    text3: 3,
    text4: 4,
    text5: 5,
    text6: 6,
    text7: 7,
    text8: 8,
    text9: 9,
    text10: 10
};

function choose(data, num)
{
    var keys = _.sortBy(
                    _.keys(data),
                    function(k)
                    {
                        return (Math.random() * 3) - 1;
                    }
                ),
        results = [],
        k1, k2;
    if (num > keys.length) {
        throw new Error('Impossible to retrieve more values than exist');
    }
    while (results.length < num) {
        k1 = k2 || keys.pop();
        k2 = keys.pop();
        results.push({key:k1, value: data[k2]});
    }
    return results;
}

console.log(choose(data, 5));

This isn't necessarily an optimal approach but it seems to meet your requirements. I first grab all of the keys and sort them randomly. I then loop through the random keys creating a new object with one key and the following keys value. That way you'll always end up with a different value associated with each key. If you need it to work when the value of num passed in to the function == the number of keys in the data then you'll have to add a little more code - I'll leave that as an exercise for the reader :)

You can have a play with this code on jsfiddle:

http://jsfiddle.net/zVyQW/1/

Upvotes: 1

Related Questions