APixel Visuals
APixel Visuals

Reputation: 1648

JavaScript - String.split() but for Arrays?

Let's say I have this array of strings (they're HTML elements, but we can use strings to keep it simple):

["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]

I need a quick way to split this array up by "d". Kinda like String.split(), except for arrays. The end result should be something like this:

[["something", "else", "and"], ["more", "things", "in", "the"], ["array", "etc"]]

Are there any simple one-liners for this? Maybe there's a function built into JS and I'm just missing it?

Upvotes: 1

Views: 168

Answers (9)

Mulan
Mulan

Reputation: 135197

Here's a functional encoding that works for any iterable input (including arrays)

const None =
  Symbol ()

const prepend = (xs, x) =>
 [ x ] .concat (xs)

const split = (f, [ x = None, ...xs ], then = prepend) =>
  x === None
    ? then ([], [])
    : split
        ( f
        , xs
        , (l, r) =>
            f (x)
              ? then (prepend (l, r), [])
              : then (l, prepend (r, x))
        )

const data = 
  [ 'something', 'else', 'and', 'd', 'more', 'things', 'in', 'the', 'd', 'array', 'etc' ]

console .log
  ( split (x => x === 'd', data)
  )

// [ [ 'something', 'else', 'and' ]
// , [ 'more', 'things', 'in', 'the' ]
// , [ 'array', 'etc' ]
// ]

And an optimization that works for any array-like input

const prepend = (xs, x) =>
 [ x ] .concat (xs)

const split = (f, xs = [], i = 0, then = prepend) =>
  i >= xs.length
    ? then ([], [])
    : split
        ( f
        , xs
        , i + 1
        , (l, r) =>
            f (xs[i])
              ? then (prepend (l, r), [])
              : then (l, prepend (r, xs[i]))
        )

const data = 
  [ 'something', 'else', 'and', 'd', 'more', 'things', 'in', 'the', 'd', 'array', 'etc' ]

console .log
  ( split (x => x === 'd', data)
  )

// [ [ 'something', 'else', 'and' ]
// , [ 'more', 'things', 'in', 'the' ]
// , [ 'array', 'etc' ]
// ]

Both implementations are O(n).

Upvotes: 1

Mark
Mark

Reputation: 92440

You can make a pretty elegant recursive function with something like:

let arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]

const spliton = (v, arr, i = arr.indexOf(v)) => (i < 0) 
    ? [arr]
    : [arr.slice(0, i), ...spliton(v, arr.slice(i+1))]


console.log(spliton('d', arr))

Upvotes: 1

Akrion
Akrion

Reputation: 18515

If you do not care about mutating the array this is also pretty trivial with while and Array.shift:

let r = [[]], data = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]

while(data.length) {
  let item = data.shift()
  item != 'd' ? r[r.length-1].push(item) : r.push([]) 
}

console.log(r)

And if you do then even shorter with Array.reduce:

let arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]

let f = arr.reduce((r,c) => (c!='d' ? r[r.length-1].push(c) : r.push([]),r),[[]])

console.log(f)

The idea in both is to start with [[]] and then the only check you need is if the current element of the iteration is d and if so push new array or push to r[r.length-1] which is the previous sub array.

Upvotes: 0

icecub
icecub

Reputation: 8773

Well if it's a one-liner you want, here you go:

var myArray = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];

const result = myArray.reduce((a, c) => c === "d" ? (a.arr[++a.i] = []) && a : a.arr[a.i].push(c) && a, {arr: [[]], i: 0}).arr;

console.log(result);

Upvotes: 2

Adrian Brand
Adrian Brand

Reputation: 21638

Use reduce starting with an accumulator that has an array containing an empty array. If the current item is the split value add an extra empty array to the end, otherwise spread the last array with the current item.

const arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];

const splitArray = (array, val) =>
  array && array.length
    ? array.reduce(
        (results, item) =>
          item === val
            ? [...results, []]
            : [...results.filter((_, i) => i < results.length - 1), [...results[results.length - 1], item]],
        [[]]
      )
    : array;
  
  
console.log(splitArray(arr, 'd'));

Upvotes: 1

vol7ron
vol7ron

Reputation: 42099

To answer your question, there aren't any concise one-liners that come to mind, but you may accomplish what you want with just a few lines of code by iterating over your values and if the word isn't 'd' store it; if it is, then create a new array to hold the next non-'d' value:

const words = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"]

let grouped = words.reduce((response,word)=>{
  if (word!=='d')
    response[response.length-1].push(word)
  else
    response[response.length]=[]
  return response
},[[]])

console.log(grouped)

Upvotes: 1

Vignesh Raja
Vignesh Raja

Reputation: 8751

A simple forEach approach is enough.

var arr = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
var result = [], temp = [];

arr.forEach(function(elem, index){
    elem !=='d' ? temp.push(elem) : (result.push(temp), temp = []);
    index==arr.length-1 && (result.push(temp));
});

console.log(result)

Upvotes: 1

sridhar
sridhar

Reputation: 622

let myArray = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
let splitArray = [],
    tempArray = [];
myArray.forEach((ele, index) => {
    if(ele !== 'd') {
      tempArray.push(ele);
    }
    if(ele === 'd' || index === myArray.length - 1) {
      splitArray.push(tempArray);
      tempArray = []; 
    }
})

console.log(': ', splitArray);

Upvotes: 1

CertainPerformance
CertainPerformance

Reputation: 370679

One option would be to join by spaces, then split by ' d ', then split each subarray by spaces:

const input = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const output = input
  .join(' ')
  .split(' d ')
  .map(str => str.split(' '));
console.log(output);

Or, without joining, figure out the index of every d, and slice every section of the input around the ds:

const input = ["something", "else", "and", "d", "more", "things", "in", "the", "d", "array", "etc"];
const dIndicies = input.reduce((a, item, i) => {
  if (item === 'd') a.push(i);
  return a;
}, []);
const output = dIndicies.reduce((a, dIndex, i, arr) => {
  const nextDIndex = arr[i + 1];
  a.push(input.slice(dIndex + 1, nextDIndex));
  return a;
}, [input.slice(0, dIndicies[0] - 1)]);
console.log(output);

Upvotes: 2

Related Questions