3

I am trying to obtain a series of subsets from an array in javascript using underscore. This is what I would like to achieve:

Original array: [1,1,1,2,2,2,0,0,0,1,1,1]
Expected result: [1,1,1] [2,2,2] [0,0,0] [1,1,1]

When I use the filter method from underscore, I get three arrays: [1,1,1,1,1,1] [2,2,2] [0,0,0]; I would like that the last array of 1's would not mix.

What I've tried:

_.filter(array, function(e){
     return e === 1;
})

The criteria to split the original array is that the consecutive equal numbers must form a new array, but if the number appears later in the original array, that consecutive equal number must form a new array.

Is there a way to do this using underscore or should it be done with loops?

Thanks

3
  • 2
    What are your rules for splitting up the array? groups of three in the order they originally appear? Something else? Commented Jun 18, 2013 at 8:18
  • can jquery be part of a solution ? Commented Jun 18, 2013 at 9:09
  • 1
    jquery is often part of the problem :P Commented Jun 18, 2013 at 9:18

6 Answers 6

1

Not sure if there is something specific in underscrore.js to do this, but I'm sure it could be done in underscore syntax. However, you could do something like this in POJS.

Javascript

var testArray = [NaN, NaN, NaN, undefined, undefined, undefined, null, null, null, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1];

function is(x, y) {
    if (x === y) {
        if (x === 0) {
            return 1 / x === 1 / y;
        }

        return true;
    }

    var x1 = x,
        y1 = y;

    return x !== x1 && y !== y1;
}

function arraySplitToGroups(array) {
    var length = array.length,
        i = 0,
        newArray = [],
        subArray = [],
        current,
        last;

    while (i < length) {
        current = array[i];

        if (!is(last, current)) {
            if (subArray.length) {
                newArray.push(subArray);
            }

            subArray = [];
        }

        subArray.push(current);
        last = current;
        i += 1;
    }

    if (subArray.length) {
        newArray.push(subArray);
    }

    return newArray;
}

console.log(arraySplitToGroups(testArray));

Output

[ [NaN, NaN, NaN], [undefined, undefined, undefined], [null, null, null], [1, 1, 1], [2, 2, 2], [0, 0, 0], [1, 1, 1] ] 

On jsfiddle

UPDATE: Just as a pure interest thing, I took all the answers that were currently on the page a created a jsperf for them.

Sign up to request clarification or add additional context in comments.

Comments

1

A plain old Javascript solution:

Fiddle

var original = [1,1,1,2,2,2,0,0,0,1,1,1];

var newArr = [], currentValue = null, currentArr;

if(original.length > 0){
    for(var i=0; i<original.length; i++){
        if(currentValue == null){
            currentValue = original[i];
            currentArr = [currentValue];
        } else {
            if(currentValue == original[i]){
                currentArr.push(currentValue);
            } else {
                newArr.push(currentArr);
                currentArr = [original[i]];
            }
        }
        currentValue = original[i];
    }
    newArr.push(currentArr);
}

Outputs:

[[1,1,1],[2,2,2],[0,0,0],[1,1,1]] 

8 Comments

Why do you need if(original.length > 0){ ?
Without that, if original is empty then the result will be [null]. Whereas using the length check, the result will be [] if original was empty.
How about something like this (may never occur in the OPs situation but can't be sure)? [undefined,undefined, undefined, null, null, null, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1]
@Xotic750 based on what OP said about the criteria, I think it will only contain numbers so no null or undefined.
Yes, I can see a way of removing the if and moving the final push elsewhere inside a nested if, but I'll just leave it as is. As always in programming there are infinite ways to accomplish the same thing.
|
1

I think I've got something quite straight forward and short:

function groupValues(arr) {
    var o=[];
    var temp=0;
    for (var i=0;i<arr.length;i++){
        if (i<=0 || arr[i]!==arr[i-1]) {
            if (i>0) {
                ++temp;
            }
            o[temp]=[];
        }
        o[temp].push(arr[i]);
    }
    return o;
}

You may take a look at this working FIDDLE.

4 Comments

Where is the declaration of v?
@Xotic750 Oops, missed that when "functionalizing" the script, updated. Thanks
Not so shabby for someone that wanted to make a jquery solution ;)
@Xotic750 I think that an unwanted jQuery solution is a bad solution. On SO I'm the first to advise new users not to spam jQuery solutions if it isn't tagged in the OP so I prefer asking first :)
0
var original = [1,1,1,1,1,2,2,2,0,0,0,1,1,1,3,3];
var result = [];
var temp = [];
_.each(original,function(num,index){
     temp.push(num);
     if(original[index+1] != 'undefined' && num != original[index+1])
    {
        result.push(temp);
        temp = [];
   }
});
console.log(result);

Fiddle : http://jsfiddle.net/2AChG/

Comments

0

Write a simple function like this:

function splitArray(original) {
    var newArr = [], currentValue, currentArr;

    for (var i = 0; i < original.length; i++) {
       if (i === 0 || currentValue !== original[i]) {
         currentArr = [];
         newArr.push(currentArr);
       }
       currentArr.push(currentValue = original[i]);
    }

    return newArr;
}

See the working jsFiddle

It is based on the example of MrCode but with less code.

Comments

0

A oneliner that would do you the job would be:

var input = [1,1,1,2,2,2,0,0,0,1,1,1]; 
var output = (input.join(',')+',').match(/(\d*,)\1+/g).map(function(str){return str.replace(/,$/,'').split(',').map(Number);})

Basically what it does:

  (input.join(',') + ',') // concatenate all numbers with a ',', and add a trailing comma to help the matching
     .match(/(\d*,)\1+/g) // match groups of numbers of the same type with comma ( "1,1,2,"  > ["1,1", "2,"] ). The * is to support null/undefined values in the original array
     .map(                // replace every match (only works in modern browsers, but you can replace it with _.map )
           function() {
              return str.replace(/,$/, '')  // remove trailing ', '
                        .split(',')         // and split the string by , "1,1,1" > [1,1,1]
                        .map(Number)        // map everything back to Number (converts undefined / null values to 0 as a sideeffect, but keeps them as a group
           }
      );

If you don't want to rely on the availabilty of map provided by only modern browsers you can use underscore map like this:

var input = [1,1,1,2,2,2,0,0,0,1,1,1]; 
var output = _.map((input.join(',')+',').match(/(\d*,)\1+/g), function(str){ return _.map(str.replace(/,$/,'').split(','), Number);});

Bware

As it is visible in the jsperf analysis made by Xotic750, this is the least performant solution. I just listed it as an alternative, maybe shortest way.

2 Comments

Doesn't appear to give the same output as the OP requested: ["1,1,1,", "2,2,2,", "0,0,0,", "1,1,1,"]
Yes I've realized that I return strings, so I've updated the answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.