6

I know how to do both things separately, but I'm sure there must be a way to combine them.

I have an array of categories, which I am extracting from an array of objects:

 this.videoCategories = this.videos.map(v => v.category);

But of course there are duplicates in this array. So now I do

this.uniqueVideoCategories = this.videoCategories.filter((item, index) => {
  return this.videoCategories.indexOf(item) === index;
});

Which works fine, I get an array of the categories without dupes. But I'm trying to learn and dry up the code a bit by stringing them together, and this does not work - yields empty array

  constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = this.videos
      .map(v => v.category)
      .filter((item, index) => {
        return this.videoCategories.indexOf(item) === index;
      });
    console.log(this.videoCategories);
  }
1

6 Answers 6

5

Inside the filter() you are checking the index inside the array of objects. You can use the third argument of filter() method which will be the newly created array after map()

 constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = this.videos
      .map(v => v.category)
      .filter((item, index, arr) => {
        return arr.indexOf(item) === index;
      });
    console.log(this.videoCategories);
  }

Instead of using filter() and indexOf() you can use Set to remove duplicates. This will be the time-complexity O(N)

constructor(private videoService: VideoService) {
    this.videos = videoService.getVideos();
    this.videoCategories = [...new Set(this.videos.map(v => v.category))]
    console.log(this.videoCategories);
  }
Sign up to request clarification or add additional context in comments.

2 Comments

Filter is faster blog.usejournal.com/…
I always forget about that third argument. Thanks.
3

Sometimes the solution is choosing the right data structure. ES6 has introduced Set, which only contains unique objects.

Then you just do:

this.videoCategories = new Set(this.videos.map(v => v.category))

The uniqueness will be handled by browser implementation, instead of cluttering your codebase.

5 Comments

Filter is faster blog.usejournal.com/…
@WilliamLohan that example is spreading Set back to array...not a valid comparison. You may be correct but that's not a valid test for this use case
@WilliamLohan The article compares filter with expanding the set with ..., I didn't suggest expanding it. At any rate, if performance mattered, I would suggest normal for. The intermediate .map is one extra allocation and loop through the array, much more important than Set vs .filter nuances.
Oh right I see, I just googled which is faster because filter-indexOf is my "goto" and saw Set solutions here and was curious
The time complexity of Set is superior. The solution with indexOf will be slower for large enough arrays, as it has O(n²) time complexity. So for small arrays it will be fine, but once you pass a threshold it will become much slower very quickly.
2

var videos = [
  { category: 'category1', title: 'Category 1'},
  { category: 'category1', title: 'Category 1'},
  { category: 'category1', title: 'Category 1'},
  { category: 'category2', title: 'Category 2'},
  { category: 'category2', title: 'Category 2'}
];
var categoryVideos =
  videos
    .map(v => v.category)
    .filter((item, index, arr) => arr.indexOf(item) === index);
    
console.log(categoryVideos);

Array.prototype.filter

Syntax

var newArray = arr.filter(callback(element[, index[, array]])[, thisArg])

Parameters

callback

Function is a predicate, to test each element of the array. Return true to keep the element, false otherwise. It accepts three arguments:

  • element: The current element being processed in the array.
  • index: (Optional) The index of the current element being processed in the array.
  • array: (Optional) The array filter was called upon.
  • thisArg: (Optional) Value to use as this when executing callback.

Return value

A new array with the elements that pass the test. If no elements pass the test, an empty array will be returned.

Comments

0

Array is empty because when you does filtering array return this.videoCategories.indexOf(item) === index;, field this.videoCategories was empty.

Try it:

this.videoCategories = this.videos
    .map(v => v.category)
    .filter((item, index, array) => {
        return array.indexOf(item) === index;
    });

1 Comment

item, index, array! I always forget about passing the array. Thanks.
0
this.videoCategories = this.videos
.map(v => v.category)
.filter((item, index, array) => 
    array.indexOf(item) === index;
);

If you don't use brackets and is just a line, you don't need the return.

Comments

0

I would do it this way

constructor(private videoService: VideoService) {
  this.videos = videoService.getVideos();
  const categories = this.videos.map(v => v.category);
  this.videoCategories = [...new Set(categories)];
  console.log(this.videoCategories);
}

1 Comment

Welcome to SO, and thanks for your answer! Please note it is generally considered good practice to explain why your code works. We are not a code-writing service after all, but a place to learn. Also refer to how do I write a good answer for more information on this.

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.