What's the most efficient way to lower case every element of a List or Set?
My idea for a List:
final List<String> strings = new ArrayList<String>();
strings.add("HELLO");
strings.add("WORLD");
for(int i=0,l=strings.size();i<l;++i)
{
strings.add(strings.remove(0).toLowerCase());
}
Is there a better, faster way? How would this example look like for a Set? As there is currently no method for applying an operation to each element of a Set (or List) can it be done without creating an additional temporary Set?
Something like this would be nice:
Set<String> strings = new HashSet<String>();
strings.apply(
function (element)
{ this.replace(element, element.toLowerCase();) }
);
Thanks,
Yet another solution, but with Java 8 and above:
List<String> result = strings.stream()
.map(String::toLowerCase)
.collect(Collectors.toList());
This seems like a fairly clean solution for lists. It should allow for the particular List implementation being used to provide an implementation that is optimal for both the traversal of the list--in linear time--and the replacing of the string--in constant time.
public static void replace(List<String> strings)
{
ListIterator<String> iterator = strings.listIterator();
while (iterator.hasNext())
{
iterator.set(iterator.next().toLowerCase());
}
}
This is the best that I can come up with for sets. As others have said, the operation cannot be performed in-place in the set for a number of reasons. The lower-case string may need to be placed in a different location in the set than the string it is replacing. Moreover, the lower-case string may not be added to the set at all if it is identical to another lower-case string that has already been added (e.g., "HELLO" and "Hello" will both yield "hello", which will only be added to the set once).
public static void replace(Set<String> strings)
{
String[] stringsArray = strings.toArray(new String[0]);
for (int i=0; i<stringsArray.length; ++i)
{
stringsArray[i] = stringsArray[i].toLowerCase();
}
strings.clear();
strings.addAll(Arrays.asList(stringsArray));
}
You can do this with Google Collections:
Collection<String> lowerCaseStrings = Collections2.transform(strings,
new Function<String, String>() {
public String apply(String str) {
return str.toLowerCase();
}
}
);
If you are fine with changing the input list here is one more way to achieve it.
strings.replaceAll(String::toLowerCase)
Well, there is no real elegant solution due to two facts:
Strings in Java are immutable
Java gives you no real nice map(f, list) function as you have in functional languages.
Asymptotically speaking, you can't get a better run time than your current method. You will have to create a new string using toLowerCase() and you will need to iterate by yourself over the list and generate each new lower-case string, replacing it with the existing one.
Try CollectionUtils#transform in Commons Collections for an in-place solution, or Collections2#transform in Guava if you need a live view.
This is probably faster:
for(int i=0,l=strings.size();i<l;++i)
{
strings.set(i, strings.get(i).toLowerCase());
}
I don't believe it is possible to do the manipulation in place (without creating another Collection) if you change strings to be a Set. This is because you can only iterate over the Set using an iterator or a for each loop, and cannot insert new objects whilst doing so (it throws an exception)
Referring to the ListIterator method in the accepted (Matthew T. Staebler's) solution. How is using the ListIterator better than the method here?
public static Set<String> replace(List<String> strings) {
Set<String> set = new HashSet<>();
for (String s: strings)
set.add(s.toLowerCase());
return set;
}
I was looking for similar stuff, but was stuck because my ArrayList object was not declared as GENERIC and it was available as raw List type object from somewhere. I was just getting an ArrayList object "_products". So, what I did is mentioned below and it worked for me perfectly ::
List<String> dbProducts = _products;
for(int i = 0; i<dbProducts.size(); i++) {
dbProducts.add(dbProducts.get(i).toLowerCase());
}
That is, I first took my available _products and made a GENERIC list object (As I were getting only strings in same) then I applied the toLowerCase() method on list elements which was not working previously because of non-generic ArrayList object.
And the method toLowerCase() we are using here is of String class.
String java.lang.String.toLowerCase()
not of ArrayList or Object class.
Please correct if m wrong. Newbie in JAVA seeks guidance. :)
Using JAVA 8 parallel stream it becomes faster
List<String> output= new ArrayList<>();
List<String> input= new ArrayList<>();
input.add("A");
input.add("B");
input.add("C");
input.add("D");
input.stream().parallel().map((item) -> item.toLowerCase())
.collect(Collectors.toCollection(() -> output));
Related
I have an custom class InfoAQ which has a method called public String getSeqInf(). Now I have an ArrayList<InfoAQ> infList and
I need an ArrayList<String>strList = new ArrayList<String>with the content from getSeqInf()for each element.
This is the way Im doing it right now ...
for(InfoAQ currentInf : infList)
strList.add(currentInf.getSeqInf());
Is there an alternative way to make it ? Maybe a faster one or one liner ?
Yes, there is:
strList = infList.stream().map(e -> g.getSeqInf()).collect(Collectors.toList());
The map step can be also written in another way:
strList = infList.stream().map(InfoAQ::getSeqInf).collect(Collectors.toList());
which is know as method reference passing. Those two solutions are equivalent.
Also maybe this one:
List<String> strList = new ArrayList<String>();
infList.forEach(e -> strList.add(e.getSeqInf()));
And there is another one (-liner, if you format it in a single line):
infList.forEach(currentInf -> {strList.add(currentInf.getSeqInf());});
while I would prefer a formatting in more lines:
infList.forEach(currentInf -> {
strList.add(currentInf.getSeqInf());
});
Using streams
infList.stream()
.map(InfoAQ::getSeqInf)
.collect(Collectors.toCollection(ArrayList::new))
Using Collectors.toCollection here to create an ArrayList that will hold the results as you do in your case. (Important if you do care about the result list type as Collectors.toList() does not guarantee this)
May not be the fastest as using stream has some overhead. You need to measure/benchmark to find out its performance
This code will iterate all the data in the list, as getSeqInf returns a String, the collect method will store all returns of the getSeqInf method in a list.
`List listString = infList.stream().map(InfoAQ::getSeqInf).collect(Collectors.toList());`
or
`
ArrayList<String> listString = new ArrayList<>();
for(int i = 0; i < infoAq.size(); i++) {
listString.add(infoAq.get(i).getSeqInf());
}`
Is there a way of appending an object to a list and returning the result in one line in a functional non-imperative way?
How would you do it if also the original list should not be mutated?
Java 8 is allowed.
I already know how to concat two lists in one line. (Source)
List listAB = Stream.concat(listA.stream(), listB.stream()).collect(Collectors.toList());
I also know how to make a list out of objects in one line.
List listO1 = Collections.singletonList(objectA);
List listO2 = Stream.of(objectA, objectB).collect(Collectors.toList());
List listOO = Arrays.asList(objectA, objectB);
Is there anything better than replacing listB in the first line with a part of the following lines?
You could use
List<Foo> newList =
Stream.concat(list.stream(), Stream.of(fooToAdd))
.collect(Collectors.toList());
Bt I find this a little bit convoluted. Strive for readability rather than finding single-line, more obscure solutions. Also, never use raw types as you're doing in your question.
You can use var args and create a stream from it to be appended to the stream of the actual list, e.g:
public static <T> List<T> append(List<T> list, T... args){
return Stream.concat(list.stream(), Stream.of(args))
.collect(Collectors.toList());
}
Alternatively:
List<Foo> updated = Stream.of(originalList, List.of(elementToAdd)).flatMap(Collection::stream).collect(Collectors.toList());
A bit late to the party, but the situation has not changed since the question was asked. So, what you are looking for is perfectly handled by io.vavr.
import io.vavr.collection.List;
List x0 = List.of(); // []
List x1 = x0.append(o1); // [o1]
List x2 = x0.prepend(o2); // [o2]
List x3 = x1.prependAll(x2); // [o2, o1]
To be clear I don't have any problems and don't really need help but I wanted to ask anyway:
Let's say we have a String array
String[] sarr = new String[]{"POTATO", "TOMATO"};
and we have an enum
public enum Food{POTATO, TOMATO, PIZZA}
If I wanted to check if all Strings in sarr are present in Food, I'd do the following:
ArrayList<String> foodstrings = new ArrayList<>();
Arrays.asList(Food.values()).forEach((in) -> foodstrings.add(in.toString()));
if (!foodstrings.containsAll(Arrays.asList(sarr))) doStuff();
Is there a way to do this in less lines of code? Or simply a more elegant way?
You want to determine if all element in your array are contained in the list of food names.
A possible solution is to convert the food names to a Set (to have a O(1) contains); then, we need to determine if all elements in the array are contained in this set:
public static void main(String[] args) {
String[] sarr = new String[]{"POTATO", "TOMATO"};
Set<String> set = Arrays.stream(Food.values()).map(Enum::name).collect(Collectors.toSet());
boolean result = Arrays.stream(sarr).allMatch(set::contains);
}
In your current solution, you are mutating an external variable with forEach, which is a bad practice.
I believe a better version of the first two lines would be:
Set<String> foodstrings = Arrays.stream(Food.values()).map(Enum::name).collect(Collectors.toSet());
Using Set instead of List will improve performance of containsAll, and the code is entirely streamed, instead of using forEach and an external collector.
The if is still good, although you could just combine it all into a single statement (formatted for readability):
if (! Arrays.stream(Food.values())
.map(Enum::name)
.collect(Collectors.toSet())
.containsAll(Arrays.asList(sarr))) {
doStuff();
}
if (Stream.of(sarr).allMatch(s -> Stream.of(Food.values()).anyMatch(t -> s.equals(t.name()))))
{
// all match
}
Create a stream out of sarr (which could be any Collection of objects amenable to the Stream API introduced in Java 1.8)
We ask for the value allMatch, which only returns true if a Predicate (i.e. a function that returns true/false).
For the Predicate expected by allMatch, we provide a lambda that iterates over a second set of objects via a stream, and calls anyMatch: a simple Predicate that will return true if any member object satisfies a provided condition (once again, a boolean function).
We provide yet another lambda to anyMatch which compares members of the 2 collections via their equals implementations.
This solution is semantically equivalent to the invariant
A \subset B
which in our case is
sarr \subset Food.values()
and the following Java < 1.8 code shown below with short-circuiting to mimic the specification (minus the streams overhead):
// assume success, since if both sets are empty the invariant holds
boolean subset = true;
for (String a : sarr)
{
if (null == a) continue;
boolean contained = false;
for (Food b : Food.values())
if (b.name().equals(a)) { contained = true; break; }
if (!contained) { subset = false; break; }
}
if (subset)
{
// all match
}
You could, of course, substitute different collection types and conditions, as well as use parallelStream() to make better use of the hardware available.
I am getting String of constants in List<String>. I need List<Integer>. By the basic way,
I will iterate and cast into Integer.
Is there any better solution?
Nope, there's no other way.
But casting is not possible in this case, you need to do use Integer.parseInt(stringValue).
List<String> listStrings = ...
List<Integer> listIntegers = new ArrayList<Integer>(listStrings.size());
for(String current:listStrings){
listIntegers.add(Integer.parseInt(current));
}
There is a way to do this.
You could use the Adapter Pattern and create a class which implements List<Integer>, but internally accesses your List<String> casting the values between Integer and String. As long as you fulfill all the contracts, any API which requires a List<Integer> will be able to work with this class just like with a native List<Integer>.
This might seem cumbersome and inefficient, but when you need to pass a List<Integer> to an API which only accesses some values of the list, it can be more efficient to cast some of them on-demand ("lazy evaluation") instead of casting all of them. It also saves memory, because you won't have both the string- and the integer representation of your whole list in memory at the same time.
for(String str : listOfString) {
listOfInteger.add(Integer.valueOf(str));
}
There's no direct way to do this.
Look at Google Guava's Lists class, you can do something as:
List<Integer> result = Lists.transform(stringList, new Function<String, Integer>() {
#Override
public Integer apply(#Nullable String input) {
return Integer.parsInt(input)
}
});
As far as I remember, it will transform each item lazily.
With Java 8 there is some new solution:
List<Integer> listOfIntegers = listOfStrings.stream().map(Integer::valueOf).collect(Collectors.toList());
There is not an explicit and direct way to change the parametrised type of a collection. You should create another one and transfer the data while modifying it.
The other way around has already been answered.
Looks like looping is the only option. If you do not 'want' to loop yourself, there are some classes which can be used (e.g. from Apache Commons).
Using guava:
List<String> strings = Lists.newArrayList("1", "2", "3");
List<Integer> ints = Lists.transform(strings, new Function<String, Integer>(){
public Integer apply(String input) {
return Integer.valueOf(input);
}
});
It's not just a matter of casting, because String and Integer are fundamentally different objects.
You must loop through the list and parse each one int a new Integer:
List<Integer> intList = new ArrayList<Integer>();
for(String numeric : stringList)
{
intList.add(Integer.parseInt(numeric));
}
Do Java collections have a built-in method to return multiple items from that collection? For example, the list below has n elements, some of which are duplicated in the list. How could I get all elements where the value = "one"? I realize it would be very easy to write my own method to achieve such functionality, I just wanted to make sure I am not missing a built in method to do this.
List<String> ls=new ArrayList<String>();
ls.add("one");
ls.add("two");
ls.add("three");
ls.add("one");
ls.add("one");
//some type of built in function????
//ls.getItems("one");
//should return elements 0,3,4
Thanks
Google Collections have Predicates for this purpose.
In this example, it's enough to know the number of times "one" appears in the list, which you can get with java.util.Collections.frequency(ls, "one").
You could also have been using a Multiset from google-collections, and called m.count("one"), which would be much more efficient.
There isn't a built-in method, but Apache Commons has a select method in CollectionUtils that will get all the elements that match some criterion. Example usage:
List<String> l = new ArrayList<String>();
// add some elements...
// Get all the strings that start with the letter "e".
Collection beginsWithE = CollectionUtils.select(l, new Predicate() {
public boolean evaluate(Object o) {
return ((String) o).toLowerCase().startsWith("e");
}
);
I think you could do the trick of retaining the elements of this list which are in another list with the retainall method of Collection class link text. In the other list you can add only the "one" object.
List<String> ls=new ArrayList<String>();
ls.add("one");
ls.add("two");
ls.add("three");
ls.add("one");
ls.add("one");
List<String> listToCompare = new ArrayList<String>();
listToCompare.add("one");
ls.retainAll(listToCompare);