9

Is there a more elegant way to get one item from a set? It seems surprisingly hard to come up with a clean looking way to both have the contains() method accessible and still be able to retrieve individual elements.

Set<String> cities = new Set<String> { 'Chicago', 'New York', 'Denver', 'San Francisco' };
String aCity;
for (String city : cities)
{
    aCity = city;
    break;
}

or

Set<String> cities = new Set<String> { 'Chicago', 'New York', 'Denver', 'San Francisco' };
String aCity = new List<String> ( cities )[0];

Both seem lacking.

EDIT: I do not care at all about the order of the set or which item specifically I pull out of it. I just want to be able to pop one out randomly so in a test I can use a valid value from said predefined set.

6
  • Create a List instead :) Aug 5, 2013 at 17:34
  • Not if you need the contains() method.
    – Adrian Larson
    Aug 5, 2013 at 17:35
  • 2
    You can use a map which is both a list and a set, and get both access to individual elements and a contains method, but you will also have data redundancy. I would just turn the code you have into a static pop method to get the "first" item out of the set.
    – Phil Rymek
    Aug 5, 2013 at 17:44
  • I'm with Phil, was just about to suggest that :) Aug 5, 2013 at 17:45
  • I was going to write an answer, but I realized I may not have the whole picture. Why do you need both contains and indexes? I've written over 100,000 lines of code and never needed this construct except for String.join. You can iterate over the set using for(datatype item:items) in a set, so indexes are largely unnecessary.
    – sfdcfox
    Aug 5, 2013 at 18:31

4 Answers 4

8

Try an identity map (i.e. have each element mapped to itself). This way you still have the convenience of a set (the map's keyset) while still being able to call specific items in the set.

Your methods are not really "lacking" as they are both quite functional. Sets are cool because you are guaranteed uniqueness of elements but the trade-off is you don't have a nice order of its elements. Lists are cool because the elements have an ordering, and if you have some knowledge of the ordering (e.g. from how the list was produced (e.g. SOQL query) or by using the sort() method), you can choose and use certain elements (like the first or last element) creatively. However, you lose (or gain) the uniqueness of elements constraint (good/bad depending on what you're trying to do).

You gotta deal with the language as is, and really, it's quite accommodating. Everything has a trade-off. But in the end, you can always do what you need to. If sets were able to everything you wanted to, I think you'd be left with a paradox hence it is not possible. And this complexity makes it fun while providing job security :)

The methods you have offered are both efficient examples of how to achieve your goal. It's quite similar to what I've used before.

1
  • 2
    It should be noted that the elements in an identity map's list may not be in the inserted order (if this matters). Salesforce makes no guarantee that the list will remain in the same order in the map when adding or removing elements due to the nature of the set that holds the keys for the values.
    – sfdcfox
    Aug 5, 2013 at 22:50
2

If you are expecting either of those code samples in your question to consistently return 'Chicago' (the deceptive: first value added to the Set) get ready for many frustrating hours of debugging.

In both of your code samples aCity is assigned the first returned element from the Set. Set's don't have an order so in both your examples aCity could theoretically be any element in the cities Set.
The apex runtime is pretty consistent, so with real execution of this code you will probably observe consistent results, but this is not guaranteed, Set's are not ordered. Granted this is basic collections theory, and I'm sure you're aware of it, but I wanted to be super clear with my answer.

When it comes to your problem of using contains, none of your code samples use contains, so I don't know what you are trying to do that is inelegant? What I can infer from your examples is you want a unique and ordered collection, which to put it bluntly does not exist in apex.

The suggestion of an identity map seems redundant to me, a quick code sample will hopefully demonstrate why:

String cityToFind;
if(cities.contains(cityToFind)) {
    //you now know cityToFind in in the set, and if you need to use that value, the variable is: cityToFind
}

Edit: (in response to your edit)
It basically sounds like you want a List.contains() method or a Set.pop() method. Neither or which exist in apex. There is an idea you could vote on: Contains/IndexOf Method For Lists
But for now you are going to have to continue with either of the inelegant ways you suggested in your question.

1
  • Note that "List.contains(Object) and List.indexOf(Object) are now available in Spring 18", API v42 and higher Mar 15, 2021 at 12:38
2

Easy Way will be using Safe Navigation '?' operator

Set<String> setNames = new Set<String> { 'Anand', 'Bala' };
system.debug(setNames.iterator().next());
// expected output: 'Anand'

setNames = null;
system.debug(setNames?.iterator().next());
// without error - expected output: null 
1

You can do:

Set<String> someSet = new Set<String> { 'first', 'second' };
if (!someSet.isEmpty()) {
    String first = someSet.iterator().next();
    System.debug(first);
}

But be aware that iterator can raise an exception if the set is empty. So, you first need to check if the set is not empty

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .