Initializing a Set with an Iterable
Asked Answered
T

7

26

I want to initialize a Set Implementation (HashSet) in Java with an Iterable. However, the constructor of HashSet doesn't accept Iterables, but only Collections type objects.

Is there a way to convert from Iterable to some subtype of Collections.

Tunisia answered 8/5, 2013 at 20:44 Comment(3)
I think the question itself is different enough (since one is an ordered collection) or the other question is not general enough to support both ordered and unordered.Winger
Well, the linkled duplicate answers the literal question asked by providing "a way to convert from Iterable to some subtype of Collection". However, I agree that this (constructing a temporary ArrayList) is not necessarily the best way to intialize a HashSet from an Iterable.Histone
If you have Spring Data on the class path you may use Streamable.of(iterable).toSet(). If Spring Data happens to be the source of the Iterable you have even more options: https://mcmap.net/q/74121/-easy-way-to-convert-iterable-to-collectionHautegaronne
C
9

HashSet constructor relies on more than what Iterable offers: it wants to know the size of the collection up front in order to optimally construct the underlying HashMap. If you have a true, austere Iterable, which doesn't know its size, then you'll have to realize the Iterable up front by turning it into a regular Collection in any of a number of obvious ways.

If, on the other hand, you have a richer object that already knows its size, then it would pay to create a minimalist adapter class that wraps your Iterable into a collection, implementing just size in addition to forwarding the call to iterator.

public class IterableCollection<T> implements Collection<T>
{
   private final Iterable<T> iterable;

   public IterableCollection(Iterable<T> it) { this.iterable = it; }

   @Override public Iterator<T> iterator() { return iterable.iterator(); }

   @Override public int size() { return ... custom code to determine size ... }

   @Override .... all others ... { throw new UnsupportedOperationException(); }
}
Castanon answered 8/5, 2013 at 21:1 Comment(1)
Good explanation. I think I get your point and intuition.Tunisia
F
56

You can use Guava.

Set<T> set = Sets.newHashSet(iterable);

or to make it read like a sentence static import,

import static com.google.common.collect.Sets.*;

Set<T> set = newHashSet(iterable);
Fawcette answered 8/5, 2013 at 20:47 Comment(0)
C
9

HashSet constructor relies on more than what Iterable offers: it wants to know the size of the collection up front in order to optimally construct the underlying HashMap. If you have a true, austere Iterable, which doesn't know its size, then you'll have to realize the Iterable up front by turning it into a regular Collection in any of a number of obvious ways.

If, on the other hand, you have a richer object that already knows its size, then it would pay to create a minimalist adapter class that wraps your Iterable into a collection, implementing just size in addition to forwarding the call to iterator.

public class IterableCollection<T> implements Collection<T>
{
   private final Iterable<T> iterable;

   public IterableCollection(Iterable<T> it) { this.iterable = it; }

   @Override public Iterator<T> iterator() { return iterable.iterator(); }

   @Override public int size() { return ... custom code to determine size ... }

   @Override .... all others ... { throw new UnsupportedOperationException(); }
}
Castanon answered 8/5, 2013 at 21:1 Comment(1)
Good explanation. I think I get your point and intuition.Tunisia
S
6

Sure, it's shown in this answer. Basically, iterate over the iterable and copy its contents in a collection:

public static <T> List<T> copyIterable(Iterable<T> iterable) {
    Iterator<T> iter = iterable.iterator();
    List<T> copy = new ArrayList<T>();
    while (iter.hasNext())
        copy.add(iter.next());
    return copy;
}

Use it as follows, the resulting List object can be passed as a parameter to the HashSet constructor.

Iterable<Integer> list = Arrays.asList(1, 2, 3);
List<Integer> copy = copyIterable(list);
Set<Integer> aSet = new HashSet<Integer>(copy);

EDIT

I've been mistaken all along. Iterable is a superinterface of Collection, so a simple (but unsafe) cast will do the trick, as long as the Iterable was a Collection to begin with.

Iterable<Integer> list = Arrays.asList(1, 2, 3);
Set<Integer> aSet = new HashSet<Integer>((Collection)list); // it works!
Slapbang answered 8/5, 2013 at 20:46 Comment(10)
I was avoiding to explicitly iterating over Iterable or consuming an Iterator myself. I'm looking for a mechanism so that it is acceptable to the constructor of the HashSet or any Set Implementation. (BTW: I didn't down vote you). But I would definitely upvote you if you can show me a workaround. :-)Tunisia
I'm getting this on my Netbeans. no suitable constructor found for HashSet(Iterator<Integer>)Tunisia
@WChargin fixed it, thanks. please reconsider the downvote.Uralic
@Tunisia pass the list, not the iteratorUralic
@Tunisia I just realized how simple is the solution to this problem. Please take a look at the update, aboveUralic
A Collection is an Iterable, but an Iterable is not necessarily a Collection. So, your cast is unsafe (as you stated) and could fail at runtime if the concrete Iterable was not also a Collection.Swedenborgianism
casting an Iterable to a Collection is a bad ideaSoluble
@Swedenborgianism I was just about to say that. I don't have a list. Infact, I have a custom DT, which exposes Iterator() and Iterable () functionality.Tunisia
@Swedenborgianism I added a clarification stating this: "as long as the Iterable was a Collection to begin with"Uralic
@Tunisia then, absolutely, you have no option but to copy your custom iterable DT to a proper Collection. You should have mentioned this from the beginning!Uralic
P
3

The Iterable interface allows the "foreach" syntax to work, so the cleanest way is likely:

public <T> Set<T> toSet(Iterable<T> collection) {
    HashSet<T> set = new HashSet<T>();
    for (T item: collection)
        set.add(item);
    return set;
}
Pyrophotometer answered 8/5, 2013 at 20:54 Comment(0)
H
2

Just add each one.

public static <T> Set<T> setFromIterable(Iterable<T> i) {
    HashSet<T> set = new HashSet<T>();
    Iterator<T> it = i.iterator();
    while (it.hasNext()) {
        set.add(it.next());
    }
    return set;
}

Iterable<Integer> someIterable = ...;
Set<Integer> someSet = setFromIterable(someIterable);

Note that you don't use the constructor new HashSet<Integer>(someIterator), because that doesn't exist. Just call the static method.

Hypsometry answered 8/5, 2013 at 20:45 Comment(3)
I think you are confusing Iterable and Iterator.Rootless
Yes, you're right. But even iterator() doesn't work. The object I have also gives back an Iterator(), but even that is not acceptable to the constructor.Tunisia
Kirk: you're right; thanks. @Vaid please see my edit.Hypsometry
R
2

I use this one-liner (with Java 8+), which only relies on java.util.stream:

StreamSupport.stream(myIterable.spliterator(), false).collect(Collectors.toSet());

// or with static imports:
stream(myIterable.spliterator(), false).collect(toSet());
Rebhun answered 19/5, 2021 at 16:16 Comment(0)
S
-3

Putting somewhat a repeated answer for conciseness. Below worked for me for converting the Iterable of String type to a Set(Java8).

Iterable<String> stringIterable = Arrays.asList("str1", "str2", "str3");
Set<String> stringHashSet = new HashSet<>((Collection<? extends String>) stringIterable);
Superjacent answered 4/3, 2021 at 23:53 Comment(1)
This only works for a Collection, and if you already know that you have something more specific then you should type it correctly.Rebhun

© 2022 - 2024 — McMap. All rights reserved.