Reputation: 519
Looking for a way to convert String to a map of "char" => int (index of). Obviously this assumes String has unique characters.
Is there a way to do it with Streams? I thought this would work but am running into errors trying to compile it:
String alphabet = "0123456789";
alphabet.chars().collect(Collectors.toMap(alphabet::charAt, Function.identity()));
Basically I want something equivalent to the Python code:
{c, i for i,c in enumerate("0123456789")}
Upvotes: 4
Views: 5878
Reputation: 77505
As you can see from the various solutions posted here, stream()
doesn't make things a whole lot easier.
While functional-style spaghetti code is all the rage right now, I suggest to use imperative programming for readability.
for(int i=0; i<s.length(); i++) map.put(s.charAt(i), i);
Seriously, procedural can be much nicer sometimes! People underappreciate the clarity of procedural code, and it's efficiency.
However, you probably want to use this code instead for your scenario:
if (c<'0' || c>'9') throw RuntimeException("Not a digit");
int digit = c - '0';
which is much cheaper (faster, and 0 memory) than a Map
.
Upvotes: 0
Reputation: 28183
You asked in comments how to do it with 3-argument IntStream#collect
. Here it is:
Map<Character, Integer> map = IntStream.range(0, alphabet.length())
.collect(HashMap::new, (m, i) -> m.put(alphabet.charAt(i), i), Map::putAll);
If you want to do duplicate check like toMap
does and still use the 3-argument form, you will have to update both 2nd (accumulator) and 3rd (combiner) arguments to do the check:
map = IntStream.range(0, alphabet.length())
.collect(
HashMap::new,
(m, i) -> m.merge(alphabet.charAt(i), i,
(a,b) -> {throw new IllegalStateException("duplicate!");}
),
(m1, m2) -> m2.forEach((c,i) ->
m1.merge(c, i, (a,b) -> {throw new IllegalStateException("duplicate!");})
)
);
As you see, this gets ugly and you would probably want to define a helper method to simplify this. If you are willing to disallow parallel stream, you can get away with only checking for duplicates in accumulator but then you will have to have the combiner throw an exception:
map = IntStream.range(0, alphabet.length())
.collect(
HashMap::new,
(m, i) -> m.merge(alphabet.charAt(i), i,
(a,b) -> {throw new IllegalStateException("duplicate!");}
),
(m1, m2) -> {throw new AssertionError("parallel not allowed");}
);
Unless you are doing this as an exercise, it's better to just leave all this mess for toMap
to deal with. That's what it's there for.
Upvotes: 6
Reputation: 12682
This creates a map from each character to its integer:
Map<Character, Integer> map = IntStream.range(0, alphabet.length())
.boxed().collect(Collectors.toMap(alphabet::charAt, Function.identity()));
Note that it won't work if you have repeated characters.
Upvotes: 5
Reputation: 8139
This works.
Map<Integer, Character> map =
IntStream.range(0, alphabet.length()).boxed()
.collect(Collectors.toMap(x -> x, alphabet::charAt));
edit:
of course if you used Scala instead (also runs on the JVM) you could write it much shorter. Then it would just be alphabet.zipWithIndex.toMap
Upvotes: 6