Split 1 ArrayList into multiple ones based on condition - java

I have a big ArrayList containing strings. I want to split it, based on a condition that an element meets. For example, it could be the string length if ArrayList contained String. What is the most efficient (not the easiest) way to do that ?
['a', 'bc', 'defe', 'dsa', 'bb']
after would result to :
['a'], ['bc', 'bb'], ['dsa'], ['defe']

It's easy and fairly efficient to do it using Java 8 streams:
Collection<List<String>> output = input.stream()
.collect(Collectors.groupingBy(String::length))
.values();
If you run it with this input:
List<String> input = Arrays.asList("a", "bc", "defe", "dsa", "bb");
You will get this output:
[[a], [bc, bb], [dsa], [defe]]
A non-stream version would do the same thing, i.e. build a Map<K, List<V>> where V is your value type (e.g. String in your case), and K is the type of the grouping value (e.g. Integer for the length).
Doing it yourself (like shown in answer by palako) might be slightly more efficient at runtime, but likely not in any way that matters.
Staying with Java 8, that would be this:
Map<Integer, List<String>> map = new HashMap<>();
for (String value : input)
map.computeIfAbsent(value.length(), ArrayList::new).add(value);
Collection<List<String>> output = map.values();
For earlier versions of Java, you can't use computeIfAbsent(), so:
Map<Integer, List<String>> map = new HashMap<Integer, List<String>>();
for (String value : input) {
Integer length = Integer.valueOf(value.length()); // box only once
List<String> list = map.get(length);
if (list == null)
map.put(length, list = new ArrayList<String>());
list.add(value);
}
Collection<List<String>> output = map.values();

The most efficient way is to iterate the original list just once. What you do is you create buckets and add to those buckets.
public class Q1 {
public static void main(String[] args) {
String[] original = {"a","bc","defe","dsa","bb"};
List<String> originalValues = new ArrayList<String>(Arrays.asList(original));
Map<Integer, List<String>> orderedValues = new HashMap<Integer, List<String>>();
Iterator<String> it = originalValues.iterator();
while (it.hasNext()) {
String currentElement = it.next();
int length = currentElement.length();
if(!orderedValues.containsKey(length)) {
orderedValues.put(length, new ArrayList<String>());
}
orderedValues.get(length).add(currentElement);
}
System.out.println(orderedValues.values());
}
}
You might be tempted to use an array of arrays instead of a Map, and use the size of the string as the index to the array position, but then you need to watch out for a case where you don't have strings of a certain length. Imagine a case where you only have a string in the original list, but it has 100 characters. You would have 99 empty positions and one string in the array at position 100.

Related

Possible permutations of string based on LinkedHashMap?

So this problem has been taunting me for days. Any help is greatly appreciated!
I have made a LinkedHashMap which stores possible combinations for each part of a string and I'm trying to get all the permutations in an ArrayList of Strings, while maintaing the string order.
For example if the map is:
a=ab, b=c
The combinations would be:
ab
ac
abb
abc
I have tried simply looping each keys and values list, heap's algorithm which didn't work out for keeping the order of elements and also tried using recursion but i wasn't sure how. If anyone could point me in the right direction or hint me, that would be great. Thanks
Another map example of what im trying to do.
If the map is:
A=a, B=b
Output is:
AB (key1, key2)
Ab (key1, value2)
aB (value1, key2)
ab (value1, value2)
I basically want every combination of the whole map in order while alternating between keys and values of the map.
Try this.
static List<String> possiblePermutations(Map<String, String> map) {
int size = map.size();
List<Entry<String, String>> list = new ArrayList<>(map.entrySet());
List<String> result = new ArrayList<>();
new Object() {
void perm(int i, String s) {
if (i >= size) {
result.add(s);
return;
}
Entry<String, String> entry = list.get(i);
perm(i + 1, s + entry.getKey());
perm(i + 1, s + entry.getValue());
}
}.perm(0, "");
return result;
}
public static void main(String[] args) {
Map<String, String> map = new LinkedHashMap<>();
map.put("a", "ab");
map.put("b", "c");
map.put("x", "y");
List<String> result = possiblePermutations(map);
System.out.println(result);
}
output:
[abx, aby, acx, acy, abbx, abby, abcx, abcy]
It's very simple ...
Let's say the number of entries in your map is N
There is a 1-to-1 mapping between each possible permutation of such strings and an array boolean of length N. If the array has a true in position K, we pick the key from map entry K, otherwise we pick the value.
Therefore, to generate all possible permutations you need to generate all possible boolean arrays (same as binary numbers) of length N and then use each one to create a corresponding string.

How to set list of chars and list of integers into one list?

How to set list of chars and list of integers into one list? I have two lists which I took from hash map and now I have to format it into pretty view. After formatting I have two lists one of integers and other one of characters. Is it possible to put this values into one array list
private List<Character> convertToList(){
Set<Character> mapKeyToList = output().keySet();
List<Character> keys = new ArrayList<>(mapKeyToList);
return keys;
}
private List<Integer> convertInts(){
Collection<Integer> values = output().values();
List<Integer> quantity = new ArrayList<>(values);
return quantity;
}
Example of output :
"char" - int
"char" - int
"char" - int
You can get the entrySet from your map and get in new ArrayList
List<Entry<Character, Integer>> res = new ArrayList<>(output().entrySet());
You can get key and value using .getKey() and .getValue()
for (Entry<Character, Integer> entry : res) {
Character c = entry.getKey();
Integer i = entry.getValue();
// formatting
}
Here you can directly use resultMap.entrySet() instead of res in loop also.
Assuming that both lists are the same size (which they should be, if they are created from the same map), you can just use a for loop to pull elements from each list and populate the new list.
List<String> newList = new ArrayList<>();
for (int i = 0; i < ints.size(); i++) {
newList.add(String.format("%s - %d", chars.get(i), ints.get(i)));
}
Of course, you could also loop over the map and do this with the Streams API if you are using Java 8 or later.
List<String> result = values.entrySet().stream()
.map(x -> String.format("%s - %d", x.getKey(), x.getValue()))
.collect(Collectors.toList());

Pluck only relevent keys from a map

I have an int arry input, for example : [1,3,4].
I also have a fixed/constant map of :
1 -> A
2 -> B
3 -> C
4 -> D
5 -> E
I want my output to be a corresponding string of all the relevant keys.
For our example of input [1,3,4], the output should be : "A,C,D".
What's the most efficient way of achieving that?
My idea was to iterate over the whole map, each time.
The problem with that, is that I have a remote call in android that fetches a long list of data items, and doing that for each item in the list seems a bit.. inefficient. Maybe there's something more efficient and/or more elegant. Perhaps using Patterns
Assuming the array is defined as below along with the HashMap:
int arr[] = { 1, 3, 4 };
HashMap<Integer, String> hmap = new HashMap<>();
// data in the map
hmap.put(1, "A"); hmap.put(2, "B"); hmap.put(3, "C"); hmap.put(4, "D"); hmap.put(5, "E");
Instead of iterating over the entire map, you can iterate over the array
String[] array = Arrays.stream(arr).mapToObj(i -> hmap.get(i))
.filter(Objects::nonNull)
.toArray(String[]::new);
This gives the output :
A C D
As per your comment, to join it as one String you can use :
String str = Arrays.stream(arr).mapToObj(i -> hmap.get(i))
.filter(Objects::nonNull)
.collect(Collectors.joining("/"));
You can iterate over the list of input instead of a map. That's the benefit of using a Map by the way, it has a lookup time of O(1) with proper hashing implemented. It would somewhat like :
Map<Integer, String> data = new HashMap<>();
List<Integer> query = new ArrayList<>(); // your query such as "1,3,4"
List<String> information = new ArrayList<>();
for (Integer integer : query) {
String s = data.get(integer); // looking up here
information.add(s);
}
which with the help of java-stream can be changed to
List<String> information = query.stream()
.map(data::get) // looking up here
.collect(Collectors.toList()); // returns "A,C,D"
Note: I have used String and Integer for just a representation, you can use your actual data types there instead.

Remove duplicates (both values) - duplicate values from an ArrayList

I have an ArrayList with the following strings;
List<String> e = new ArrayList<String>();
e.add("123");
e.add("122");
e.add("125");
e.add("123");
I want to check the list for duplicates and remove them from the list. In this case my list will only have two values, and in this example it would be the values 122 and 125, and the two 123s will go away.
What will be the best way to this? I was thinking of using a Set, but that will only remove one of the duplicates.
In Java 8 you can do:
e.removeIf(s -> Collections.frequency(e, s) > 1);
If !Java 8 you can create a HashMap<String, Integer>. If the String already appears in the map, increment its key by one, otherwise, add it to the map.
For example:
put("123", 1);
Now let's assume that you have "123" again, you should get the count of the key and add one to it:
put("123", get("aaa") + 1);
Now you can easily iterate on the map and create a new array list with keys that their values are < 2.
References:
ArrayList#removeIf
Collections#frequency
HashMap
You can also use filter in Java 8
e.stream().filter(s -> Collections.frequency(e, s) == 1).collect(Collectors.toList())
You could use a HashMap<String, Integer>.
You iterate over the list and if the Hash map does not contain the string, you add it together with a value of 1.
If, on the other hand you already have the string, you simply increment the counter. Thus, the map for your string would look like this:
{"123", 2}
{"122", 1}
{"125", 1}
You would then create a new list where the value for each key is 1.
Here is a non-Java 8 solution using a map to count occurrences:
Map <String,Integer> map = new HashMap<String, Integer>();
for (String s : list){
if (map.get(s) == null){
map.put(s, 1);
}
else {
map.put(s, map.get(s) + 1);
}
}
List<String> newList = new ArrayList<String>();
// Remove from list if there are multiples of them.
for (Map.Entry<String, String> entry : map.entrySet())
{
if(entry.getValue() > 1){
newList.add(entry.getKey());
}
}
list.removeAll(newList);
Solution in ArrayList
public static void main(String args[]) throws Exception {
List<String> e = new ArrayList<String>();
List<String> duplicate = new ArrayList<String>();
e.add("123");
e.add("122");
e.add("125");
e.add("123");
for(String str : e){
if(e.indexOf(str) != e.lastIndexOf(str)){
duplicate.add(str);
}
}
for(String str : duplicate){
e.remove(str);
}
for(String str : e){
System.out.println(str);
}
}
The simplest solutions using streams have O(n^2) time complexity. If you try them on a List with millions of entries, you'll be waiting a very, very long time. An O(n) solution is:
list = list.stream()
.collect(Collectors.groupingBy(Function.identity(), LinkedHashMap::new, Collectors.counting()))
.entrySet()
.stream()
.filter(e -> e.getValue() == 1)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
Here, I used a LinkedHashMap to maintain the order. Note that static imports can simplify the collect part.
This is so complicated that I think using for loops is the best option for this problem.
Map<String, Integer> map = new LinkedHashMap<>();
for (String s : list)
map.merge(s, 1, Integer::sum);
list = new ArrayList<>();
for (Map.Entry<String, Integer> e : map.entrySet())
if (e.getValue() == 1)
list.add(e.getKey());
List<String> e = new ArrayList<String>();
e.add("123");
e.add("122");
e.add("125");
e.add("123");
e.add("125");
e.add("124");
List<String> sortedList = new ArrayList<String>();
for (String current : e){
if(!sortedList.contains(current)){
sortedList.add(current);
}
else{
sortedList.remove(current);
}
}
e.clear();
e.addAll(sortedList);
I'm a fan of the Google Guava API. Using the Collections2 utility and a generic Predicate implementation it's possible to create a utility method to cover multiple data types.
This assumes that the Objects in question have a meaningful .equals
implementation
#Test
public void testTrimDupList() {
Collection<String> dups = Lists.newArrayList("123", "122", "125", "123");
dups = removeAll("123", dups);
Assert.assertFalse(dups.contains("123"));
Collection<Integer> dups2 = Lists.newArrayList(123, 122, 125,123);
dups2 = removeAll(123, dups2);
Assert.assertFalse(dups2.contains(123));
}
private <T> Collection<T> removeAll(final T element, Collection<T> collection) {
return Collections2.filter(collection, new Predicate<T>(){
#Override
public boolean apply(T arg0) {
return !element.equals(arg0);
}});
}
Thinking about this a bit more
Most of the other examples in this page are using the java.util.List API as the base Collection. I'm not sure if that is done with intent, but if the returned element has to be a List, another intermediary method can be used as specified below. Polymorphism ftw!
#Test
public void testTrimDupListAsCollection() {
Collection<String> dups = Lists.newArrayList("123", "122", "125", "123");
//List used here only to get access to the .contains method for validating behavior.
dups = Lists.newArrayList(removeAll("123", dups));
Assert.assertFalse(dups.contains("123"));
Collection<Integer> dups2 = Lists.newArrayList(123, 122, 125,123);
//List used here only to get access to the .contains method for validating behavior.
dups2 = Lists.newArrayList(removeAll(123, dups2));
Assert.assertFalse(dups2.contains(123));
}
#Test
public void testTrimDupListAsList() {
List<String> dups = Lists.newArrayList("123", "122", "125", "123");
dups = removeAll("123", dups);
Assert.assertFalse(dups.contains("123"));
List<Integer> dups2 = Lists.newArrayList(123, 122, 125,123);
dups2 = removeAll(123, dups2);
Assert.assertFalse(dups2.contains(123));
}
private <T> List<T> removeAll(final T element, List<T> collection) {
return Lists.newArrayList(removeAll(element, (Collection<T>) collection));
}
private <T> Collection<T> removeAll(final T element, Collection<T> collection) {
return Collections2.filter(collection, new Predicate<T>(){
#Override
public boolean apply(T arg0) {
return !element.equals(arg0);
}});
}
Something like this (using a Set):
Set<Object> blackList = new Set<>()
public void add(Object object) {
if (blackList.exists(object)) {
return;
}
boolean notExists = set.add(object);
if (!notExists) {
set.remove(object)
blackList.add(object);
}
}
If you are going for set then you can achieve it with two sets. Maintain duplicate values in the other set as follows:
List<String> duplicateList = new ArrayList<String>();
duplicateList.add("123");
duplicateList.add("122");
duplicateList.add("125");
duplicateList.add("123");
duplicateList.add("127");
duplicateList.add("127");
System.out.println(duplicateList);
Set<String> nonDuplicateList = new TreeSet<String>();
Set<String> duplicateValues = new TreeSet<String>();
if(nonDuplicateList.size()<duplicateList.size()){
for(String s: duplicateList){
if(!nonDuplicateList.add(s)){
duplicateValues.add(s);
}
}
duplicateList.removeAll(duplicateValues);
System.out.println(duplicateList);
System.out.println(duplicateValues);
}
Output: Original list: [123, 122, 125, 123, 127, 127]. After removing
duplicate: [122, 125] values which are duplicates: [123, 127]
Note: This solution might not be optimized. You might find a better
solution than this.
With the Guava library, using a multiset and streams:
e = HashMultiset.create(e).entrySet().stream()
.filter(me -> me.getCount() > 1)
.map(me -> me.getElement())
.collect(toList());
This is pretty, and reasonably fast for large lists (O(n) with a rather large constant factor). But it does not preserve order (LinkedHashMultiset can be used if that is desired) and it creates a new list instance.
It is also easy to generalise, to instead remove all triplicates for example.
In general the multiset data structure is really useful to keep in ones toolbox.

How to convert a Set<Set> to an ArrayList<ArrayList>

How would I add all the elements of a Set<<Set<String>> var to an ArrayList<<ArrayList<String>>? Of course I'm aware of the naive approach of just adding them.
private static ArrayList<ArrayList<String>> groupAnagrams(ArrayList<String> words){
ArrayList<ArrayList<String>> groupedAnagrams = new ArrayList<>();
AbstractMap<String, String> sortedWords = new HashMap<>();
Set<Set<String>> sameAnagramsSet = new HashSet<>();
for(String word : words){
char[] wordToSort = word.toCharArray();
Arrays.sort(wordToSort);
sortedWords.put(word, new String(wordToSort));
}
for(Map.Entry<String, String> entry: sortedWords.entrySet() ){
Set<String> sameAnagrams = new HashSet<>();
sameAnagrams.add(entry.getKey());
for(Map.Entry<String, String> toCompare : sortedWords.entrySet()){
if(entry.getValue().equals(toCompare.getValue())){
sameAnagrams.add(toCompare.getKey());
}
}
if(sameAnagrams.size()>0){
sameAnagramsSet.add(sameAnagrams);
}
}
//-->this line does not work! return new ArrayList<ArrayList<String>>(sameAnagramsSet);
}
With Java 8, you can do:
return sameAnagramsSet.stream()
.map(ArrayList::new)
.collect(toList());
although it returns a List<ArrayList<String>>.
What it does:
.stream() returns a Stream<Set<String>>
.map(ArrayList::new) is equivalent to .map(set -> new ArrayList(set)) and basically replaces each set by an array list
collect(toList()) places all the newly created lists in one list
Since you want to convert each element from a Set to an ArrayList, you'll have to do at least a little of this with an explicit loop, I think (unless you're using Java 8 or a third-party library):
Set<Set<String>> data = . . .
ArrayList<List<String>> transformed = new ArrayList<List<String>>();
for (Set<String> item : data) {
transformed.add(new ArrayList<String>(item));
}
Note that I changed the type of the transformed list from ArrayList<ArrayList<String>> to ArrayList<List<String>>. Generally it's preferable to program to an interface, but if you really need a list that must contain specifically instances of ArrayList, you can switch it back.

Categories

Resources