Java: Identify common paths in ArrayList<String> using Lambdas - java

I got an array of elements like :
ArrayList<String> t = new ArrayList();
t.add("/folder1/sub-folder1");
t.add("/folder2/sub-folder2");
t.add("/folder1/sub-folder1/data");
I need to get output as /folder1/sub-folder1 which is mostly repeated path.
In python this can be achieved using the below function:
def getRepeatedPath(self, L):
""" Returns the highest repeated path/string in a provided list """
try:
pkgname = max(g(sorted(L)), key=lambda(x, v): (len(list(v)), -L.index(x)))[0]
return pkgname.replace("/", ".")
except:
return "UNKNOWN"
I am trying to work on equivalent lambda function in Java. I got struck and need some help in the lambda implementation.
public String mostRepeatedSubString(ArrayList<String> pathArray) {
Collections.sort(pathArray);
String mostRepeatedString = null;
Map<String,Integer> x = pathArray.stream.map(s->s.split("/")).collect(Collectors.toMap());
return mostRepeatedString;
}

Lots of tweaking, but I finally got it!
public static void main(String[] args) {
ArrayList<String> t = new ArrayList<String>();
t.add("folder1/sub-folder1");
t.add("folder2/sub-folder2");
t.add("folder1/sub-folder1/data");
System.out.println(mostRepeatedSubString(t));
}
public static String mostRepeatedSubString(List<String> pathArray) {
return pathArray
.stream()
// Split to lists of strings
.map(s -> Arrays.asList(s.split("/")))
// Group by first folder
.collect(Collectors.groupingBy(lst -> lst.get(0)))
// Find the key with the largest list value
.entrySet()
.stream()
.max((e1, e2) -> e1.getValue().size() - e2.getValue().size())
// Extract that largest list
.map(Entry::getValue)
.orElse(Arrays.asList())
// Intersect the lists in that list to find maximal matching
.stream()
.reduce(YourClassName::commonPrefix)
// Change back to a string
.map(lst -> String.join("/", lst))
.orElse("");
}
private static List<String> commonPrefix(List<String> lst1, List<String> lst2) {
int maxIndex = 0;
while(maxIndex < Math.min(lst1.size(), lst2.size())&& lst1.get(maxIndex).equals(lst2.get(maxIndex))) {
maxIndex++;
}
return lst1.subList(0, maxIndex);
}
Note that I had to remove the initial / from the paths, otherwise that character would have been used in the split, resulting in the first string in every path list being the empty string, which would always be the most common prefix. Shouldn't be too hard to do this in pre-processing though.

Related

Java 8 Comma Separated String to Object property

I have three comma-separated lists (list of bus, car, cycle) and I am trying to write them into Java object properties using Java 8 streams.
Please find below what I have tried :
public class Traffic {
public int car;
public int bus;
public int cycle;
public Traffic(int car, int bus,int cycle){
this.car = car;
this.bus = bus;
this.cycle = cycle;
}
}
public class Test {
public static void main(String[] args) {
String bus = "5,9,15,86";
String car = "6,12,18,51";
String cycle = "81,200,576,894";
String[] busArray = bus.split(",");
String[] carArray = car.split(",");
String[] cycleArray = cycle.split(",");
List<Traffic> trafficList =
Arrays.stream(values)
.mapToInt(Integer::parseInt)
.mapToObj((int i,j) -> new Traffic(i,j))
.collect(Collectors.toList());
}
}
I was struggling with getting all streams up and injected into object properties. (I want to create 4 objects in this case populating all 3 properties.)
Basically, I am looking for something like below:
List<Traffic> trafficList =
Arrays.stream(carArray,busArray,cycleArray)
.mapToInt(Integer::parseInt)
.mapToObj((int i,j,k) -> new Traffic(i,j,k))
.collect(Collectors.toList());
If you want to create 4 objects of Traffic then you can use the following :
List<Traffic> collect = IntStream.range(0, busArray.length)
.mapToObj(i -> new Traffic(Integer.parseInt(busArray[i]),
Integer.parseInt(carArray[i]),
Integer.parseInt(cycleArray[i])))
.collect(Collectors.toList());
You just have to split your string and then map each value to your object.
Here I assume the value can be passed through the constructor of your Traffic object. If not, you can create it and set its value in 2 separate lines. The mapToInt is necessary if the value is expected to be an integer.
String original = "5,9,15,86";
String[] values = original.split(",");
List<Traffic> trafficList =
Arrays.stream(values)
.mapToInt(Integer::parseInt)
.map(Traffic::new)
.collect(Collectors.toList());
Define a constructor in the class Traffic that takes an integer as argument and assigns it to value attribute in the class.
static class Traffic {
private int value;
public Traffic(int value) {
this.value = value;
}
}
Now assuming the comma delimited string is in a string commandList, something like below.
String commaList = "1,3,5,6,7,8,9,100";
Following stream instruction will return a list of Traffic objects with the value assigned.
List<Traffic> listOfIntegers =
Arrays.asList(commaList.split(","))
.stream()
.map(e -> new Traffic(Integer.valueOf(e)))
.collect(Collectors.toList());
If you really want an array, you can try the following
Arrays.stream("5,9,15,86".split(","))
.map(Traffic::new)
.toArray(Traffic[]::new);
If a List<Traffic> is also okay for you i recommend this one
Arrays.stream("5,9,15,86".split(","))
.map(Traffic::new)
.collect(Collectors.toList());
And lastly if you only have a constructor for Integer for example, you can map the stram to int like
Arrays.stream("5,9,15,86".split(","))
.mapToInt(Integer::valueOf)
.mapToObj(Traffic::new)
.collect(Collectors.toList());
EDIT
I answered this question before the question was edited, that's why it is only a partial answer
EDIT2
Okay i got it, i used map instead of mapToObj what a huge mistake... But i found it out thanks to #JavaMan's helpful answers (notice that if you are using IntelliJ it offers you to replace map with mapToObj)

java 8 stream map check previous elements

I have a question I have a big text file that i'm currently reading, I want to have a list of the words in it and also find spesific pairs in it.
An example of my dataset is:
A random text file . I am <pair-starter> first second <pair-ender> and it goes on and on,
and hopefully it ends .
Now I read the file with streams like
List<String> words = Files.lines(Paths.get(filename), Charset.forName("UTF-8")).
.map(line -> line.split("[\\s]+"))
.flatMap(Arrays::stream)
.filter(this::filterPunctuation) //This removes the dot in example
.map(this::removePunctuation) //This removes the comma
//I think the method should be added here
.filter(this::removePairSpesifics) //To remove pair starter and ender
.collect(Collectors.toList());
Now with this code I can get the clean words I get a list that contains "A", "random", "text", "file", "I", "am", "first", "second", "and", "it", "goes", "on", "and", "on", "and", "hopefully", "it", "ends"
But I also want to get a hashmap that holds the pairs in it and I wonder if it is possible with adding a new method on the stream above. Couldn't find anything close to what I want from google, thanks in advance.
the method close to the one in my head is
private boolean pairStarted = false;
private String addToHashMap(String element){
if previous element was pair starter
pairStarted = true;
else if pairStarted and element is not pairEnder
MyPreviouslyConstructedHashMap.put(the previous one, element);
else if element is pairEnder
pairStarted = false;
return element;
} //This function will not change anything from the list as it returns the elements
//But it'll add the hashmap first-second pair
My current solution is:
List<String> words = Files.lines(Paths.get(filename), Charset.forName("UTF-8")).
.map(line -> line.split("[\\s]+"))
.flatMap(Arrays::stream)
.filter(this::filterPunctuation)
.map(this::removePunctuation)
.collect(Collectors.toList()); //Now not using removePairSpesifics
//as I need to check for them.
for(int i=words.size()-1; i>=0; i--) {
if(words.get(i).equals("<pair-ender>")){ //checking from end to modify in the loop
pairs.put(words.get(i-2), words.get(i-1));
i = i-4;
words.remove(i+1);
words.remove(i+4);
}
}
What I want to learn is to learn if it can be solved in the same stream as I read the values into the list.
At first, I tried to separate the split into two splits, and it worked out quite well:
public void split(Stream<String> lines)
{
Pattern pairFinder = Pattern.compile("<pair-starter|pair-ender>");
Pattern spaceFinder = Pattern.compile("[\\s]+");
Map<String, String> pairs = new HashMap<>();
List<String> words = lines.flatMap(pairFinder::splitAsStream).flatMap(pairOrNoPair -> {
if (pairOrNoPair.startsWith(">") && pairOrNoPair.endsWith("<"))
{
pairOrNoPair = pairOrNoPair.replaceAll("> +| +<", "");
String[] pair = spaceFinder.split(pairOrNoPair);
pairs.put(pair[0], pair[1]);
return Arrays.stream(pair);
}
else
{
return spaceFinder.splitAsStream(pairOrNoPair.trim());
}
})
.filter(this::filterPunctuation) // This removes the dot in example
.map(this::removePunctuation) // This removes the comma
.collect(Collectors.toList());
System.out.println(words);
System.out.println(pairs);
}
// Output
// [A, random, text, file, I, am, first, second, and, it, goes, on, and, on, and, hopefully, it, ends]
// {first=second}
boolean filterPunctuation(String s)
{
return !s.matches("[,.?!]");
}
String removePunctuation(String s)
{
return s.replaceAll("[,.?!]", "");
}
What happens here? First, we split the line into pairs and non-pairs. For each of those, we check whether they are a pair. If so, we remove the markers and add the pair to the list. In any case, we split the chunk by spaces, flatten it, and procede word by word.
But this implementation only deals with the input line by line.
To tackle the issue with multi-line pairs, we can try a custom Collector approach. Look at this rather quick and dirty attempt:
String t1 = "I am <pair-starter> first second <pair-ender>, <pair-starter> and";
String t2 = " hopefully <pair-ender> it ends .";
split(Stream.of(t1, t2));
public void split(Stream<String> lines)
{
PairResult result = lines.flatMap(Pattern.compile("[\\s]+")::splitAsStream)
.map(word -> word.replaceAll("[,.?!]", ""))
.filter(word -> !word.isEmpty())
.collect(new PairCollector());
System.out.println(result.words);
System.out.println(result.pairs);
}
// Output
// [I, am, first, second, and, hopefully, it, ends]
// {and=hopefully, first=second}
class PairCollector
implements Collector<String, PairResult, PairResult>
{
#Override
public Supplier<PairResult> supplier()
{
return PairResult::new;
}
#Override
public BiConsumer<PairResult, String> accumulator()
{
return (result, word) -> {
if ("<pair-starter>".equals(word))
{
result.inPair = true;
}
else if ("<pair-ender>".equals(word))
{
if (result.inPair)
{
result.pairs.put(result.words.get(result.words.size() - 2),
result.words.get(result.words.size() - 1));
result.inPair = false;
}
else
{
// starter must be in another result, keep ender for combiner
result.words.add(word);
}
}
else
{
result.words.add(word);
}
};
}
#Override
public BinaryOperator<PairResult> combiner()
{
return (result1, result2) -> {
// add completed pairs
result1.pairs.putAll(result2.pairs);
// use accumulator to finish split pairs
BiConsumer<PairResult, String> acc = accumulator();
result2.words.forEach(word2 -> acc.accept(result1, word2));
return result1;
};
}
#Override
public Function<PairResult, PairResult> finisher()
{
return Function.identity();
}
#Override
public Set<Characteristics> characteristics()
{
return new HashSet<>(Arrays.asList(Characteristics.IDENTITY_FINISH));
}
}
class PairResult
{
public boolean inPair;
public final List<String> words = new ArrayList<>();
public final Map<String, String> pairs = new HashMap<>();
}
This collector accepts word by word, and stores a bit of internal state to keep track of pairs. It should even work for parallel streams, combining the separate streams of words into one result.

Java 8 find first instance of value in multidimensional array of objects

Trying to understand how to use some java 8 features and was playing around with multidimensional array of objects, if I wanted to find the first instance of a value in a multidimensional array of objects.
Objects[][] someArray= .....
Arrays.stream(someArray)
.map(someArrayFirst -> Arrays.stream(someArrayFirst))
.map(unsure what to do here)
.filter(a -> a.equals("some value"))
.findFirst();
edit, thanks for the input. Just to help others out here is what I have now.
Arrays.stream(someArray)
.flatMap(someArrayFirst -> Arrays.stream(someArrayFirst))
.filter(MyCustomClass.class::isInstance)
.map(MyCustomClass.class::cast)
.filter(v -> v.value().equalsIgnoreCase("SomeString"))
.findFirst();
You are on the right track. You need to turn the two dimensions into a single stream and then take the first element that satisfies your condition:
String[][] array;
Optional<String> result = Arrays.stream(array)
.flatMap(Arrays::stream)
.filter("some value"::equals).findFirst();
The first stream produces a Stream<String[]>. The flat map turns each of the array elements into a Stream<String>. Then it's just filtering for the value you want and getting the first element that satisfies your condition.
static String[][] arr = new String[][]{{"x","y"},{"z","v"},{"b","z"}};
static String searchStr = "x";
static String searchObj = null;
public static void main(String... args) {
Arrays.stream(arr)
.forEach((subarr)->{
Optional<String> opt = Arrays.stream(subarr)
.filter((obj)->obj.equals(searchStr))
.findFirst();
if (opt.isPresent())
searchObj = opt.get();
});
System.out.println(searchObj);
}
or
static public String mapFlatMethod(String[][] arr, String searchStr) {
return Arrays.stream(arr).flatMap(row -> Stream.of(row))
.filter((obj)->obj.equals(searchStr))
.findFirst().get();
}

How to get an String and the ArrayList stored in a Arraylist of objects of a class using Java stream

I have modified the code and trying to get an ArrayList and the String stored in an Arraylist of Objects on a specific condition(say 'str' string equal to 2). I'm not able to convert the Stream to ArrayList. Please help me understand what needs to be done to get the ArrayList from this stream.
I have a class 'SampleClass' like below:
import java.util.ArrayList;
public class SampleClass {
String str;
ArrayList<String> al;
String check;
public SampleClass(String str, ArrayList<String> al, String check) {
super();
this.str = str;
this.al = al;
this.check = check;
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public ArrayList<String> getAl() {
return al;
}
public void setAl(ArrayList<String> al) {
this.al = al;
}
public String getCheck() {
return check;
}
public void setCheck(String check) {
this.check = check;
}
}
I have another class 'GetTheArrayListStoredInAnotherArrayList' like below where I'm trying to get the ArrayList stored inside the ArrayList of objects. Please correct me where I'm wrong.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Collectors;
public class GetTheArrayListStoredInAnotherArrayList{
public static void main(String[] args) {
String test = "qw,rer,try,try,erh5,wertgw45t,45";
ArrayList<String> al = new ArrayList<String>();
al.addAll(new ArrayList<String>(Arrays.asList(test.split(","))));
System.out.println(al);
ArrayList<SampleClass> sca = new ArrayList<SampleClass>();
SampleClass sc1 = new SampleClass("1", al,"ch1");
SampleClass sc2 = new SampleClass("2", al,"cc2");
SampleClass sc3 = new SampleClass("3", al,"fr3");
SampleClass sc4 = new SampleClass("4", al,"fg4");
sca.add(sc1);
sca.add(sc2);
sca.add(sc3);
sca.add(sc4);
ArrayList<String> als1 = null;
ArrayList<String> als = sca.stream().filter( s -> s.getStr().equals("2")).flatMap(sc -> sc.getAl().stream()).collect(Collectors.toCollection(ArrayList::new));
System.out.println(als);
String ch = (String) sca.stream().filter(s -> s.getStr().equals("1")).map(ac -> ac.getCheck());
System.out.println(ch);
}
}
I got the below error when I executed the code :
Exception in thread "main" java.lang.Error: Unresolved compilation problem:
Cannot cast from Stream<String> to String
at GetTheArrayListStoredInAnotherArrayList.main(GetTheArrayListStoredInAnotherArrayList.java:24)
Not entirely sure what you are trying to do, but you need to change your code a bit:
List<String> als = sca.stream()
.filter(s -> s.getStr().equals("2"))
.flatMap(sc -> sc.getAl().stream())
.collect(Collectors.toList());
A few things :
flatMap must return a Stream (in your case you are returning a List)
Collectors.toList makes no guarantee of the List in returns, so the assignment is to a List, not an ArrayList.
EDIT
This:
Stream<String> stream = sca.stream().filter(s -> s.getStr().equals("1"))
.map(ac -> ac.getCheck());
Will produce a Stream<String>. You can't simply cast that to a String, you have to collect/reduce that to whatever you want. Like let's say a List:
List<String> list = sca.stream()
.filter(s -> s.getStr().equals("1"))
.map(ac -> ac.getCheck())
.collect(Collectors.toList());
Or a single String for example:
String r = sca.stream()
.filter(s -> s.getStr().equals("1"))
.map(ac -> ac.getCheck())
.collect(Collectors.joining(","));
This is actually basic stuff... you should really study some samples and the documentation.
Change
ArrayList<String> als = sca.stream().filter( s -> s.getStr().equals("2")).flatMap( sc -> sc.getAl());
To
ArrayList<String> als = sca.get(0).getAl();
First you have to use List instead of ArrayList. So with List you code will looks like
List<String> als1 = null;
List<String> als = sca.stream().
filter(s -> s.getStr().equals("2")). //Comparing
map(s -> s.getAl()) // Converting List<SampleClass> to list of all al list inside all SampleClass in format List<List<Straing>>
.flatMap(ArrayList::stream) //Creating a flat list from list of list of List :: List<List<Straing>> --To--> List<String>
.collect(Collectors.toList()); // Collecting as list
I have commented this code with details. But here if there are two SampleCalss objects in the list with str=2 then it will merge the al list of both objects. hope it will help you .
I'm trying to get the ArrayList stored inside the ArrayList of objects.
Well, the basic algorithm is as follows: Filter sca so it only leaves elements where str is "2" -> Get a single element from all the left over elements -> Get the al stored inside of that element.
You have done the first part correctly:
sca.stream().filter( s -> s.getStr().equals("2"))
But now you need to get a single element from the filtered result (filter can result in multiple elements being left over), so you call findFirst:
.findFirst().get()
This get call will throw an exception if there is no element left after the filter. If you don't want it to throw an exception, you can replace it with an orElse call:
.findFirst.orElse(new SampleClass("", null))
If you use orElse, the method chain will evaluate to null if no element with str being "2".
Now you just need to get the array list by calling getAl():
.getAl();
Now we combine all this together:
ArrayList<String> als = sca.stream()
.filter( s -> s.getStr().equals("2"))
.findFirst().orElse(new SampleClass("", null)).getAl();

How to convert a String of Integers to an ArrayList in Java?

I try to parse a textfile which has lines which look like the following:
#KEY,_,0,1,2,_,4,5,6, ...
The #KEY is just an identifier in the beginning while the following numbers are my data which I want to store in an ArrayList<Integer>.
I have a metadata class which contains the arraylist in which I want to insert there integers:
class MetaD {
public List<Integer> key1, key2, key3 = new ArrayList<Integer>();
}
I parse the textfile line by line; when the line starts with #KEY, I want to add the elements to the key1 list. If there is an _, it should be replaced with an empty value:
if(line.startsWith("#KEY")){
metaObject.key1 = Arrays.asList(line.replace("#KEY,", "").replace("_", "").trim().split("\\s*,\\s*"));
}
I found out that this does not work with ArrayList<Integer>. key1 has to be of the type ArrayList<String> or ArrayList<Object> to make it work.
Is there a way to convert Integers in the same way?
If not, my idea would be the following:
Convert everything to an ArrayList<String>
Iterate every item of this new ArrayList and convert it with Integer.parseInt() into an Integer.
Adding this new Integer to my ArrayList<Integer>
Would there be a more efficient or better way to archive my needs?
Edit:
Since Tunaki wrote in the comments, that my idea will probably be the only possible way I tried to do the following:
if(line.startsWith("#KEY")){
List<String> channelTemp = Arrays.asList(line.replace("#KEY,", "").replace("_", "1").split("\\s*,\\s*"));
channelTemp.forEach(item -> metaObject.channel.add(Integer.parseInt(item)));
System.out.println("done");
}
Unfortunately, this throws a NullPointerException in the third line here and I don't have a clue why. I replaced _ with 1 for testing purposes to avoid a NumberFormatException. When I print out every object in the lambda function instead of adding them to my ArrayList<Integer>, I can see that all items have an Integer value. So why do I get an exception here?
Since you're almost there I'll give you a hand.
String line = "#KEY,_,0,1,2 , _,4,5,6,";
List<Integer> collect = Arrays.stream(line.replaceAll("#KEY|_", "").split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(Integer::valueOf).collect(Collectors.toList());
System.out.println(collect);
EDIT
To obtain the null you can alter the mapping process like:
List<Integer> collect = Arrays.stream(line.split(","))
.skip(line.startsWith("#KEY") ? 1 : 0)
.map(String::trim)
.filter(s -> !s.isEmpty())
.map(s -> "_".equals(s) ? null : Integer.valueOf(s)).collect(Collectors.toList());
You're trying to put in list of Integer a String:
metaObject.key1 = Arrays.asList(line.replace("#KEY,", "").replace("_", "").trim().split("\\s*,\\s*"));
Here line.replace(...) and trim() return a String, and split(...) returns a String[].
Therefore Arrays.asList(...) returns a List<String> here, that's not compatible with your definition of key1 (List<Integer>).
Yes, you can convert it to List<Integer> by call Integer.valueOf(...) or Integer.parseInt(...).
But I would recommend to
Use a new instance of List instead of Arrays.asList(...) because the latest one will produce an unmodifiable collection. Sometines it's not what you want :)
Use something less specific than your own text format. What about JSON? There are a lot of libraries to simplify parsing/storing of the data.
Firstly, you should split your string with ",", then you try if your each String is an integer or not with an isIntegerMethod. If it is an integer, you can add it into the list.
public static void main(String[] args) throws IOException {
String str = "#KEY,_,0,1,2,_,4,5,9";
String [] strArr = str.split(",");
List<Integer> intList = new ArrayList<Integer>();
for (String string : strArr) {
if (isInteger(string, 10)) {
intList.add(Integer.valueOf(string));
} else {
System.out.println(string + " is not an integer");
}
}
System.out.println(intList.toString());
}
public static boolean isInteger(String s, int radix) {
if(s.isEmpty()) return false;
for(int i = 0; i < s.length(); i++) {
if(i == 0 && s.charAt(i) == '-') {
if(s.length() == 1) return false;
else continue;
}
if(Character.digit(s.charAt(i),radix) < 0) return false;
}
return true;
}

Categories

Resources