1

I have an object in which the values are integers. Something like: {cat: 2, dog: 1, bird: 4}

I want to create an array in which the key in the object is included in the array the same number of times as the value. So, in this instance: ["cat", "cat", "dog", "bird", "bird", "bird", bird"]

The keys will always be the same, but a value may be zero.

I can see one way I could do it - for each key I can retrieve the value then push the word to the array that number of times. But that requires several lines of code for each of my keys (of which there are significantly more than in this example). I've tried searching for a cleaner solution, but no luck. Any suggestions?

Andi
  • 13
  • 2
  • I can't think of a better way to do it than that. This isn't a common operation, so there's nothing built in that does it. – Barmar May 27 '16 at 22:45
  • If you use a library like underscore.js, it might provide a function to push a string N times, where N is the value from the object. – Barmar May 27 '16 at 22:46
  • You can loop over keys with a `for in` loop. No need to repeat the code for each of your keys. – Bergi May 27 '16 at 22:46
  • Notice that objects are unordered. You may or may not always get the same array. – Bergi May 27 '16 at 22:47
  • Thanks folks. The for in loop (as demoed below) was what I needed. Order isn't crucial here. – Andi May 27 '16 at 22:54

6 Answers6

2

Demo: https://jsfiddle.net/81LxL1qt/1/

var obj = {cat: 2, dog: 1, bird: 4};

var results = [];

for (var key in obj)
  for (var i = 0, count = obj[key]; i < count; i++)
    results.push(key);

console.log(results);
4castle
  • 32,613
  • 11
  • 69
  • 106
Cristi Pufu
  • 9,002
  • 3
  • 37
  • 43
2

A highly functional method of doing this would be:

var obj = {cat: 2, dog: 1, bird: 4};
var arr = [].concat(...Object.keys(obj).map(key => Array(obj[key]).fill(key)));
console.log(arr);

Basically what it does is, it gets the keys of the object and then maps each key to an array filled with that key. Then it flattens the resulting 2D array into a 1D array using .concat().

Additional reading: Arrow functions (=>), Spread operator (...)

Community
  • 1
  • 1
4castle
  • 32,613
  • 11
  • 69
  • 106
  • it actually isn't the most performant and it's hard for a beginner to understand, so it's just showing off.. – Cristi Pufu May 28 '16 at 08:33
  • @CristiPufu It's not showing off, it's teaching :) If I'm lucky, they will look up what each part does, and then they won't be a beginner anymore :) This post certainly taught me a few tricks also. (Especially Oriol, that was amazing) – 4castle May 28 '16 at 13:50
1

Using an immediately executed generator expression,

var obj = {cat: 2, dog: 1, bird: 4};
var output = [...function*() {
  for(var key of Object.keys(obj))
    for(var i=0; i<obj[key]; ++i)
      yield key;
}()];
console.log(output);
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • Why not `for(var key in obj)`? – 4castle May 27 '16 at 23:09
  • @4castle Use `for..in` if you want to iterate inherited properties too. – Oriol May 27 '16 at 23:11
  • Gotcha, though I doubt there are inherited properties. The question proposed a pretty simple object. – 4castle May 27 '16 at 23:12
  • 2
    I love how you posted [the answer](http://stackoverflow.com/a/23326623/5743988) for the question I googled to make my answer. Nice to meet you! – 4castle May 27 '16 at 23:17
  • @4castle Yep, I was going to use `fill` too, `yield* new Array(obj[key]).fill(key)`. But then I thought that would allocate unnecessary arrays, and Bergi has already complained about that in previous answers of mine XD – Oriol May 27 '16 at 23:24
1

With the Lodash function forOwn

var result = [];
_.forOwn({cat: 2, dog: 1, bird: 4}, function(value, key){
    result.push(...Array(value).fill(key));
});
JellyRaptor
  • 725
  • 1
  • 8
  • 20
  • `_.times` takes a function as the second parameter. `_.times(value, _.constant(key))` works though :) – 4castle May 28 '16 at 03:51
  • @4Castle you are absolutely right. Chose to use Array.fill() instead, though, since a third lodash function was a bit overkill. – JellyRaptor Jun 21 '16 at 21:54
0

This is how i would do it;

var arr = ["cat", "cat", "dog", "bird", "bird", "bird", "bird"],
    obj = arr.reduce((p,c) =>  (p[c] = p[c] ? ++p[c] : 1 ,p) ,{});
console.log(obj);

So ok .It's friday night and i won't be long till i get right one... (ft. Sia)

var obj = {cat: 2, dog: 1, bird: 4},
    arr = Object.keys(obj).reduce((p,c) => p.concat((new Array(obj[c])).fill(c)) ,[]);
console.log(arr);

Sorry again... I guess it's all fine now.

Redu
  • 25,060
  • 6
  • 56
  • 76
  • 2
    Great solution! But you solved the reverse operation :( They wanted your output to be the input. – 4castle May 27 '16 at 23:26
0

Throwing in a quick lodash solution, if that's an option for you:

function mapObjectToArray(obj) {
  var arr = [];
  _.forIn(obj, function(value, key) {
    _.times(value, function() {
      arr.push(key);
    });
  });
  return arr;
}

var petCt =  {'cat': 2, 'dog': 1, 'bird': 3, 'ocelot': 0, 'sugar glider': 1};

<!DOCTYPE html>
<html>

  <head>
    <script data-require="lodash.js@4.6.1" data-semver="4.6.1" src="https://cdn.jsdelivr.net/lodash/4.6.1/lodash.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>

  <body>
    <pre id="resultArray"></pre>


  <script>
    var petCt =  {'cat': 2, 'dog': 1, 'bird': 3, 'ocelot': 0, 'sugar glider': 1};
    
    function mapObjectToArray(obj) {
      var arr = [];
      _.forIn(obj, function(value, key) {
        _.times(value, function() {
          arr.push(key);
        });
      });
      return arr;
    }
    
    var elem = document.getElementById('resultArray');
    elem.innerHTML = mapObjectToArray(petCt);
    
  </script>
  
  </body>
</html>
Bennett Adams
  • 1,808
  • 14
  • 17