0

I have an observable array With the following Data

["Volkswagen",
 "Toyota",
 "Volkswagen",
 "Toyota",
 "Audi",
 "Volkswagen",
 "Toyota",
 "Audi"]

I would like to know how I can count the duplicate values and display them within my select box. Something like:

Volkswagen (3)
Audi (2)
Toyota (3)

What is the best way to achieve this?

Jacques Bronkhorst
  • 1,685
  • 6
  • 34
  • 64
  • 1
    You want to achieve GroupBy. See http://stackoverflow.com/questions/14446511/what-is-the-most-efficient-method-to-groupby-on-a-javascript-array-of-objects – roland May 02 '15 at 08:55

3 Answers3

1

I'd probably use a fairly straightforward computed:

viewModel.collapsedMakes = ko.computed({
  pure: true,
  owner: viewModel,
  read: function() {
    var makes = {}, rv;
    // Use an object to count them
    this.makes().forEach(function(make) {
      if (makes[make]) {
        ++makes[make];
      } else {
        makes[make] = 1;
      }
    });

    // Build the array
    rv = Object.keys(makes).sort().map(function(make) {
      return make + " (" + makes[make] + ")";
    });
    return rv;
  }
});

Live example:

var viewModel = {
  makes: ko.observableArray([
    "Volkswagen",
    "Toyota",
    "Volkswagen",
    "Toyota",
    "Audi",
    "Volkswagen",
    "Toyota",
    "Audi"
  ])
};

viewModel.collapsedMakes = ko.computed({
  pure: true,
  owner: viewModel,
  read: function() {
    var makes = {}, rv;
    // Use an object to count them
    this.makes().forEach(function(make) {
      if (makes[make]) {
        ++makes[make];
      } else {
        makes[make] = 1;
      }
    });

    // Build the array
    rv = Object.keys(makes).sort().map(function(make) {
      return make + " (" + makes[make] + ")";
    });
    return rv;
  }
});

ko.applyBindings(viewModel, document.body);
<select data-bind="options: collapsedMakes"></select>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

T.J. Crowder's solution is great, but I want to show a simplified version of his read function, which takes advantage of Knockout's remove function:

var arr= ko.observableArray(this.makes().slice()),
        s= [];

arr.sort().forEach(function(val) {
  var len= arr.remove(val).length;
  s.push(val+' ('+len+')');
});

return s;

Using slice clones the original array, so this is non-destructive.

var viewModel = {
  makes: ko.observableArray([
    "Volkswagen",
    "Toyota",
    "Volkswagen",
    "Toyota",
    "Audi",
    "Volkswagen",
    "Toyota",
    "Audi"
  ])
};

viewModel.collapsedMakes = ko.computed({
  pure: true,
  owner: viewModel,
  read: function() {
    var arr= ko.observableArray(this.makes().slice()),
        s= [];

    arr.sort().forEach(function(val) {
      var len= arr.remove(val).length;
      s.push(val+' ('+len+')');
    });

    return s;
  }
});

ko.applyBindings(viewModel, document.body);
<select data-bind="options: collapsedMakes"></select>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
0

In the spirit of DRY I suggest you check this out http://linqjs.codeplex.com/

This will give you most of LINQ client side in your spa. Summing is only one of the things you can then do in a single line of code.

Peter Wone
  • 17,965
  • 12
  • 82
  • 134