Returning items in a list

Viewed 177

Given a list of Strings, return a list that contains the distinct values. In other words, no value is to be included more than once in the returned list. (Hint: Think Set)

distinctValues( ["red", "yellow", "green", "yellow", "blue", "green", "purple"] ) -> ["red", "yellow", "green", "blue", "purple"]

distinctValues( ["jingle", "bells", "jingle", "bells", "jingle", "all", "the", "way"] ) -> ["jingle", "bells", "all", "the", "way"]

My Code:

public List<String> distinctValues(List<String> stringList) {
        
        Map<String, String> distinctValuesMap = new HashMap<String, String>();
         
        for (int i= 0; i < stringList.size(); i++) {
            distinctValuesMap.put(stringList.get(i), stringList.get(i));
            
        }
        Set<String>distinctValuesSet = distinctValuesMap.keySet();
        List<String>distinctValues = new ArrayList<String>(distinctValuesSet);
        System.out.println(distinctValues);
        
        return distinctValues;
    }

Expected results: red, yellow, green, blue, purple My results: red, green, blue, yellow, purple

Not sure where I'm going wrong, but I tried different ways and can't get the expect outcome.

3 Answers

You are losing the information about insertion order because you are using Unordered collection Set. Insertion order is preserved in data structures like LinkedList (and its set variant LinkedHashSet or its map variant LinkedHashMap).

The advantage of using a LinkedHashSet or LinkedHashMap over LinkedList is that while insertion, when check is performed for duplicate, in LinkedHashSet or LinkedHashMap it is O(1) where as for LinkedList it is O(n).

You can achieve same thing using Java 8 stream.distinct() also.

Here are few options:

public class MyApp {
  public static void main(String[] args) {

    List myList = Arrays.asList("red", "yellow", "green", "yellow", "blue", "green", "purple");
    distinctValuesUsingStreamDistinct(myList).forEach(System.out::println);

    System.out.println("==============");

    distinctValuesUsingLinkedHashSet(myList).forEach(System.out::println);
  }

  public static List<String> distinctValuesUsingStreamDistinct(List<String> stringList) {
    return stringList.stream().distinct().collect(Collectors.toList());
  }

  public static List<String> distinctValuesUsingLinkedHashSet(List<String> stringList) {
    LinkedHashSet<String> setSortedByInsertionOrder = new LinkedHashSet<String>();
    stringList.forEach(str -> setSortedByInsertionOrder.add(str));
    return new ArrayList<>(setSortedByInsertionOrder);
  }
}

A Set is simply a set of values. It has no indices, and therefore no order. When you turn a Set into an ArrayList, the elements could be in any order, and there is no way to change that. If you want to preserve the order of the elements, there's a neat little function of ArrayList you can use called retainAll(). This lets you retain only the elements present in the set, but keep the order of the ArrayList. Here's your code, slightly modified and simplified to demonstrate this:

public List<String> distinctValues(List<String> stringList) {
    Set<String> distinctValuesSet = new HashSet<>(stringList);
    List<String> distinctValues = new ArrayList<>(stringList);
    distinctValues.retainAll(distinctValuesSet);
    return distinctValues;
}

Stream::distinct

Just use Java stream distinct to get a distinct list of elements out of your list while retaining original order.

In a single line of code.

List<String> result = list.stream().distinct().collect(Collectors.toList());

Or multi-line.

List<String> result = 
    list
    .stream()
    .distinct()
    .collect(
        Collectors.toList()
    )
;

See this code run live at IdeOne.com.

Related