I want to change a List to List, and then map as Map.
The key would be "img"+index , how can I do it.
example:
from List ["a", "b", "c"]
to Map {"img1": "a", "img2": "b", "img3": "c"}
Observable.from((Bitmap[])bitmapList.toArray())
.subscribeOn(Schedulers.io())
.map(new Func1<Bitmap, String>() {
#Override
public String call(Bitmap bitmap) {
return doSomething(bitmap);
}
})
.toMap(new Func1<String, String>() {
#Override
public String call(String s) {
return "img"; // how to got the index
}
})
...;
In order to combine a value with an index you need some internal state: you need to keep track of a counter within the stream. You can do this with the scan operator. Because you need to keep track of both that counter and the actual value, we first need to introduce a simple class that can hold two values:
private static class Tuple<T, S> {
final T first;
final S second;
Tuple(T k, S v) {
this.first = k;
this.second = v;
}
}
The scan operator requires two parameters: an initial value for the state and an accumulator function that takes the previous state and a new value and transforms them into a new state. The initial state is simple, it is the combination of the empty String ("") and an initial index (depending on which index you want to start, 0 or 1. The accumulator is easy now: it takes the new value and increments the counter from the previous state and combines them in a new Tuple.
Because the initial state is not what you want to see here, you need to do a skip(1) to get rid of the first emitted element.
Finally you can do toMap, but you need to take the version with two arguments: the keySelector and the valueSelector where you get the key and value out of the Tuple respectively.
The final code looks as follows:
public static void main(String[] args) {
Observable.from(Arrays.asList("a", "b", "c"))
.scan(new Tuple<>("", 0), (tuple, s) -> new Tuple<>(s, tuple.second + 1))
.skip(1)
.toMap(tuple -> "img" + tuple.second, tuple -> tuple.first)
.subscribe(System.out::println);
}
Notice that this combination of scan and skip is in fact a zipWithIndex, as it is called in for example RxScala. Java does not have tuples in the language so you cannot do this directly, but you have to create your own Tuple class for it to work.
Related
I have changed my Test to make it reproduce easier:
Minimize Test
public class test {
public static void main(String[] args) {
List<TestBean> obj_list = Arrays.asList(new TestBean("aa"), new TestBean("bb" ), new TestBean("bb")).stream()
.distinct().map(tt -> {
tt.col = tt.col + "_t";
return tt;
}).collect(Collectors.toList());
System.out.println(obj_list);
List<String> string_obj_list = Arrays.asList(new String("1"), new String("2"), new String("2")).stream().distinct().map(t -> t + "_t").collect(Collectors.toList());
System.out.println(string_obj_list);
List<String> string_list = Arrays.asList("1", "2", "2").stream().distinct().map(t -> t + "_t").collect(Collectors.toList());
System.out.println(string_list);
}
}
#Data
#AllArgsConstructor
#EqualsAndHashCode
class TestBean {
String col;
}
the result is below, the line one is abnormal for me to understand:
[TestBean(col=aa_t), TestBean(col=bb_t), TestBean(col=bb_t)]
[1_t, 2_t]
[1_t, 2_t]
----------original question is below -------------------------------
my logic step:
produce a list of stream
map each element to list stream
collect list stream to one stream
distinct element
map function apply to each element and collect the result to a list
however , the result does not do distinct logic (step 4) ,which is that i can not understand
public class test {
public static void main(String[] args) {
List<TestBean> stage1 = Arrays.asList(new TestBean("aa", null), new TestBean("bb", null), new TestBean("bb", null)).stream()
.map(tt -> {
return Arrays.asList(tt);
})
.flatMap(Collection::stream).distinct().collect(Collectors.toList());
List<Object> stage2 = stage1.stream().map(tt -> {
tt.setCol2(tt.col1);
return tt;
}).collect(Collectors.toList());
System.out.println(stage1);
System.out.println(stage2);
List<TestBean> stage_all = Arrays.asList(new TestBean("aa", null), new TestBean("bb", null), new TestBean("bb", null)).stream()
.map(tt -> {
return Arrays.asList(tt);
})
.flatMap(Collection::stream).distinct().map(tt -> {
tt.setCol2(tt.col1);
return tt;
}).collect(Collectors.toList());
System.out.println(stage_all);
}
}
#Data
#AllArgsConstructor
#EqualsAndHashCode
class TestBean {
String col1;
String col2;
}
the result is
[TestBean(col1=aa, col2=aa), TestBean(col1=bb, col2=bb)]
[TestBean(col1=aa, col2=aa), TestBean(col1=bb, col2=bb)]
[TestBean(col1=aa, col2=aa), TestBean(col1=bb, col2=bb), TestBean(col1=bb, col2=bb)]
line three is abnormal for me.
The distinct() operation is filtering the set of items in the stream using Object.equals(Object).
However you are mutating the items as they are streamed - a very bad idea for Set operations - so in theory it is possible in your runs that the first TestBean(col=bb) is changed to TestBean(col=bb_t) before the final TestBean(col=bb) is handled by distinct(). Therefore at that moment there are 3 unique items in the stream seen by the distinct() step and the last .map() sees all three items.
You can verify by re-processing the stream without the ".map()" side effect or add .distinct() after .map().
Takeaway from this: Don't use distinct() or other set like operations on data structures that change fields used in equals() or hashCode() as this gives misleading / duplicates into set.add() operations. This is where Java records are useful as they cannot be changed and would eliminate errors from these type of side-effects:
record TestBean(String col) {}
Example
The #EqualsAndHashCode tag on TestBean is generating the equals and hashCode calls which are essential for Set / distinct() operations to work. If the hashcode/equals changes after adding an item, the set won't work properly as it fails to match up the previous element as being a duplicate of a newly added element. Consider this simpler definition of TestBean which add same instance 5 times:
public static void main(String... args) {
class TestBean {
String col;
TestBean(String col) {
this.col = col;
}
// This is bad choice hashCode as it changes if "col" is changed:
public int hashCode() {
return col.hashCode();
}
}
Set<TestBean> set = new HashSet<>();
TestBean x = new TestBean("bb");
for (int i = 0; i < 5; i++) {
System.out.println("set.add(x)="+set.add(x));
System.out.println("set.size()="+set.size());
// Comment out next line or whole hashCode method:
x.col += "_t";
}
}
Run the above which adds same element to a set 5 times. You will see that set.size() may be 5 not 1. Comment out the line which causes the hashcode to change - or the hashCode() method, and set.size()=1 as expected.
I am creating a function that loops through a string, separates it by comma and then takes the key from the second item in the array and the value from the 1st after splitting the string.
I then want to place these values in a map. This works perfectly, however if i have two strings with the same key it doesn't add the value up it just replaces it.
For example if my string was
123,totti 100,roma, 100,totti
I would want
totti 223
roma 100
Here is my code
private void processCallLogs(String[] splitCalls) {
for (String individualCall : splitCalls) {
int duration = 0;
String[] singleCall = individualCall.split(",");
duration += DurationParser.returnDuration(singleCall[0]);
this.cost += CalculateCost.calculateCostPerCall(singleDuration);
if (totalCallDurations.containsKey(singleCall[1])) {
totalCallDurations.put(singleCall[1], singleDuration);
} else {
totalCallDurations.put(singleCall[1], duration);
}
}
}
You can replace the if with something like this:
if (totalCallDurations.containsKey(singleCall[1])) {
duration += totalCallDurations.get(singleCall[1]);
}
totalCallDurations.put(singleCall[1], duration);
Create a map and update the value if the key is present
public static void main(String[] args) {
myMap = new HashMap<>();
// 123,totti 100,roma, 100,totti
addToMap("totti", 123);
addToMap("roma", 100);
addToMap("totti", 100);
System.out.println(myMap);
}
private static void addToMap(String string, int i) {
int t = i;
if (myMap.get(string) != null) {
t += myMap.get(string);
}
myMap.put(string, t);
}
If you're using Java 8, you can do this easily with the Map.merge() method:
totalCallDurations.merge(singleCall[1], duration, Integer::sum);
If you want to make a map that will add the values together instead of replacing, I would recommend extending the Map type to make your own map. Since Map is very abstract. I would extend HashMap. (I suggest this both for code style and because it will make your code more extendable).
public class AdderMap extends HashMap<String, Integer> { // This extends the HashMap class
public Integer get(String key) { // This overrides the Map::get method
if(super.containsKey(key)) return super.get(key); // If the key-value pairing exists, return the value
else return 0; // If it doesn't exist, return 0
}
public Integer put(String key, Integer value) { // This overrides the Map::put method
Integer old_value = this.get(key); // Get the former value of the key-value pairing (which is 0 if it doesn't exist)
super.put(key, old_value + value); // Add the new value to the former value and replace the key-value pairing (this behaves normally when the former value didn't exist)
return old_value; // As per the documentation, Map::put will return the old value of the key-value pairing
}
}
Now, when you initialize your map, make it an AdderMap. Then, you can just use put(String, Integer) and it will add it together.
The advantage of this solution is that it helps with keeping your code clean and it allows you to use this type of map again in the future without needing separate code in your main code. The disadvantage is that it requires another class, and having too many classes can become cluttered.
A multiset is similar to a set except that the duplications count.
We want to represent multisets as linked lists. The first representation
that comes to mind uses a LinkedList<T> where the same item can occur at
several indices.
For example:the multiset
{ "Ali Baba" , "Papa Bill", "Marcus", "Ali Baba", "Marcus", "Ali Baba" }
can be represented as a linked list
of strings with "Ali Baba" at index 0, "Papa Bill" at index 1,
"Marcus" at index 2, "Ali Baba" at index 3, and so on, for a total of
6 strings.
The professor wants a representation of the multiset as pair <item,integer> where the integer, called the multiplication of item, tells us how many times item occurs in the multiset. This way the above multiset is represented as the linked list with Pair("Ali Baba" ,3) at index 0, Pair("Papa Bill", 1) at index 1, and Pair("Marcus",2) at index 2.
The method is (he wrote good luck, how nice of him >:[ )
public static <T> LinkedList<Pair<T,Integer>> convert(LinkedList<T> in){
//good luck
}
the method transforms the first representation into the Pair representation.
If in is null, convert returns null. Also feel free to modify the input list.
He gave us the Pair class-
public class Pair<T,S>
{
// the fields
private T first;
private S second;
// the constructor
public Pair(T f, S s)
{
first = f;
second = s;
}
// the get methods
public T getFirst()
{
return first;
}
public S getSecond()
{
return second;
}
// the set methods
// set first to v
public void setFirst(T v)
{
first = v;
}
// set second to v
public void setSecond(S v)
{
second = v;
}
}
I am new to programming and I've been doing well, however I have no idea how to even start this program. Never done something like this before.
If you are allowed to use a temporary LinkedList you could do something like that:
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
LinkedList<String> test = new LinkedList<String>();
test.add("Ali Baba");
test.add("Papa Bill");
test.add("Marcus");
test.add("Ali Baba");
test.add("Marcus");
test.add("Ali Baba");
LinkedList<Pair<String, Integer>> result = convert(test);
for(Pair<String, Integer> res : result) {
System.out.println(res.getFirst() + " :" + res.getSecond());
}
}
public static <T> LinkedList<Pair<T, Integer>> convert(LinkedList<T> in) {
LinkedList<Pair<T, Integer>> returnList = new LinkedList<>();
LinkedList<T> tmp = new LinkedList<T>();
// iterate over your list to count the items
for(T item : in) {
// if you already counted the current item, skip it
if(tmp.contains(item)) {
continue;
}
// counter for the current item
int counter = 0;
//iterate again over your list to actually count the item
for(T item2 : in) {
if(item.equals(item2)) {
counter ++;
}
}
// create your pair for your result list and add it
returnList.add(new Pair<T, Integer>(item, counter));
// mark your item as already counted
tmp.add(item);
}
return returnList;
}
}
With that i get the desired output of
Ali Baba :3
Papa Bill :1
Marcus :2
Your requirements put:
your input : LinkedList
your output : LinkedList>
1 - write a loop to read your input
2 - process / store it in a convenient way: user Map . In fact, use linkedhashmap which keeps the order
2bis - if you can't use a Map, do the same thing directly with two arrays: an array of T, and an array of integer. You must manager insertion, search, and keep count.
3 - iterate over your arrays, and create your output
It is easier to begin with 2, and if it works, replace with 2bis
I'm having trouble transforming my algo in a Java 8 view.
I have an arrayList composed of Articles
ArrayList<Article> listArticles = new ArrayList<>();
With an Article composed like this
public class Article {
private String titleArticle;
private String abstractArticle;
private String textArticle;
private Long value;
}
and on the other side I have map of words with each one a value associated
HashMap<String, Long> dictionary = new HashMap<>();
I want to get the value of an article. The value of an article is calculated based on the words in the title, abstract and text (all added up together)
In Java 7 I would do something like this (I hope I didn't make any mistake here)
for(Article article : dataArticles){
double valueArticle = 0;
for(Map.Entry<String, Long> word : dataDictionary.entrySet()){
//looping through the words in the title
for(String text : article.getTitle().split(" ")){
if(text.equalsIgnoreCase(word.getKey())){
valueArticle += word.getValue();
}
}
//looping through the words in the abstract
for(String text : article.getAbstractText().split(" ")){
if(text.equalsIgnoreCase(word.getKey())){
valueArticle += word.getValue();
}
}
//looping through the words in the abstract
for(String text : article.getText().split(" ")){
if(text.equalsIgnoreCase(word.getKey())){
valueArticle += word.getValue();
}
}
}
article.setValue(valueArticle);
}
How can I calculate the value of each article inside the Array by reducing the time process?
I was thinking of using lambdas but maybe it's a bad approach.
I'm new to Java 8 and trying to learn it.
After some developing
Still looking around how to make my ArrayList using streams. In the meantime I wanted, as well, to sort out the list from greatest article value to lowest article value.
I imagined that it would be something like this
Comparator<Article> byArticleValue = (a1, a2) ->
Integer.compare(a1.getValue(), a2.getValue());
dataArticles.stream()
.sorted(byArticleValue);
But my list comes out unsorted. What am I doing wrong in this case ?
The hash map can do very fast lookups. If you reorganize your code a bit, you can get huge runtime savings.
long getValueOfText(String text) {
long value = 0;
for(String word : text.split(" ")) {
Long v = dataDictionary.get(word);
if (v != null) {
value += v;
}
}
return value;
}
That call to get is almost free. No matter how many words you store in your map, it will take a constant time to look one up.
EDIT: it looks a bit nicer as a Java 8 stream
long getValueOfText(String text) {
return Arrays.stream(text.split(" "))
.map(word -> dataDictionary.get(word))
.filter(v -> v != null)
.reduce(Long::sum).get();
}
If your dictionary keys are not lower case, you should create a lower-cased version and re-use it:
/**
* Create a copy of the dictionary with all keys in lower case.
* #param lc a dictionary of lowercase words to their value
* #param article the article to be evaluated
*/
static Map<String, Double> convert(Map<String, Double> dictionary)
{
return
dictionary.entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey().toLowerCase(),
Map.Entry::getValue,
(p, q) -> p + q));
}
Then, for each article, you can quickly compute a value using a stream pipeline:
/**
* Compute the value of an article.
* #param lc a dictionary of lowercase words to their value
* #param article the article to be evaluated
*/
static double evaluate(Map<String, Double> lc, Article article)
{
return
Stream.of(article.getTitle(), article.getAbstractText(), article.getText())
.flatMap(s -> Arrays.stream(s.toLowerCase().split(" ")))
.mapToDouble(k -> lc.getOrDefault(k, 0D))
.sum();
}
For more flexibility in folding words together, you could use a Collator to index with a CollationKey rather than lowercase words. A similar enhancement could be made for tokenizing the text, rather than simply splitting on spaces.
The Java 8 way of doing this is by utilizing Streams.
You can read about them here: http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html & Part 2: http://www.oracle.com/technetwork/articles/java/architect-streams-pt2-2227132.html
Here is some sample code:
public static Map<string, integer=""> wordCount(Stream<String> stream) {
return stream
.flatMap(s -> Stream.of(s.split("\\s+")))
.collect(Collectors
.toMap(s -> s, s -> 1, Integer::sum));
}
Instead of looping through elements, you can process the data with a stream and use its methods to sort and organize through it. In the sample code above, the flatmap method separates lines of text into words and the collect method gathers them into a Map<String, Integer>, with the key being the word and the value being its count.
Java8 Streaming API is the way to go. It will make your code much faster and allows optional multi-threading.
I rewrote your code into this compilable example:
public class Snippet {
static ArrayList<Article> listArticles = new ArrayList<>();
static HashMap<String, Long> dictionary = new HashMap<>();
private static void calculateWordValueSums(ArrayList<Article> listArticles) {
// turn your list of articles into a stream
listArticles.stream()
// allow multi-threading (remove this line if you expect to have few articles)
.parallel()
// make calculation per article
.forEach(article -> {
// set the "value" field in the article as the result
article.value =
// combine title, abstract and text, since they are counting all together
Stream.of(article.titleArticle, article.abstractArticle, article.textArticle)
// split every text into words (consider "\s" for to allow tabs as separators)
.flatMap(text -> Arrays.stream(text.split(" ")))
// allow multi-threading (remove this line if you expect to have few words per article)
.parallel()
// convert words into their corresponding integer value
.mapToLong(dictionary::get)
// sum all Longs
.sum();
System.out.println(article.value);
});
}
public static void main(String[] args) {
Article a = new Article();
a.titleArticle = "a b c";
a.abstractArticle = "d e";
a.textArticle = "f g h";
listArticles.add(a);
dictionary.put("a", 1l);
dictionary.put("b", 1l);
dictionary.put("c", 1l);
dictionary.put("d", 1l);
dictionary.put("e", 1l);
dictionary.put("f", 1l);
dictionary.put("g", 1l);
dictionary.put("h", 1l);
calculateWordValueSums(listArticles);
}
}
class Article {
String titleArticle;
String abstractArticle;
String textArticle;
long value;
}
You should, however, rethink your Article class. The field value will be null, until the calculation is done. Consider having an Article class with just the inputs for the calculation and an ArticleWithResultValue class, which contains a reference to the article and the resulting value. This will give you compiler help, about whether or not the calculation is already done.
I have a list of Strings. I want to evaluate each string based on a function that returns a double. Then I want the first 5 strings, based on their calculated values. If there are fewer than 5, I want all of them (in order). Let's say the strings are chemical compounds and the function computes the mass. The function is computationally expensive; I need to evaluate it once per string. (I'm just making up data here, though.)
H2O => 18.5
C12H11O22 => 109.1
HeNe => 32.0
H2SO4 => 54.37
HCl => 19.11
4FeO3 => 82.39
Xe6 => 281.9
The program should return the first five strings arranged in order by their respective values. For this sample data: H20, HCl, HeNe, H2SO4, 4FeO3. Actually, I don't really care about the order; I just need the five lowest in any order.
I thought about how I'd do this in Perl. It's just a few lines:
foreach $s (#str) {
$strmap{$s} = f($s);
}
#sorted = sort { $strmap{$a} <=> $strmap{$b} } keys %strmap;
return #sorted[0, 4]
But I need to do it in Java. And it's driving me crazy.
First I tried populating a HashMap<String, Double>, then using Collections.sort with a custom comparator, just like the Perl version. But scoping on the Comparator prevented it from referring to the HashMap to look up the values.
Then I tried a TreeMap<String, Double>, but it only sorts by key and no amount of coercing could get it to order the entries by value.
So I tried a TreeMap<Double, String>. It will discard entries with the same Double. However, the likelihood of having Strings that map to the same Double is low, so I pressed forward. Adding the entries to the TreeMap is no problem, but I ran into issues trying to extract the values from it.
TreeMap supplies a method called subMap, but its parameters are the keys that delimit the subset. I don't know what they are; I just want the first five of them. So I tried using the values method to get all the values out of the TreeMap, hoping they'd be in order. Then I can just get the first ten.
ArrayList<String> strs = (ArrayList<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Nope. Runtime error: cannot cast TreeMap$Values to ArrayList.
List<String> strs = (List<String>)(treemap.values());
return new ArrayList<String>(strs.subList(0, 5));
Same. Runtime error trying to do the cast. OK, let's just assign to a Collection...
Collection<String> strs = treemap.values();
return new ArrayList<String>(strs.subList(0, 5));
Sorry, subList isn't a method of Collection.
Collection<String> strs = treemap.values();
ArrayList<String> a = new ArrayList<String>(strs);
return new ArrayList<String>(a.subList(0, 5));
Finally, something that works! But two extra data structures just to get the first five elements? And I'm not too wild about using Double as the key for TreeMap.
Is there a better solution?
I don't think you'll get more compact than the three lines above, not in Java.
Apart from that, I have the impression that a Map as a data structure is the wrong choice in the first place, since you do not seem to need by-string lookups (UNLESS you want in some way deal with multiple occurences of strings, but you didn't say so). An alternative approach would be to declare your own comparable data record class:
private static class Record implements Comparable<Record> {
// public final fields ok for this small example
public final String string;
public final double value;
public Record(String string, double value) {
this.string = string;
this.value = value;
}
#Override
public int compareTo(Record other) {
// define sorting according to double fields
return Double.compare(value, other.value);
}
}
// provide size to avoid reallocations
List<Record> records = new ArrayList<Record>(stringList.size());
for(String s : stringList)
records.add(new Record(s, calculateFitness(s));
Collections.sort(records); // sort according to compareTo method
int max = Math.min(10, records.size()); // maximum index
List<String> result = new ArrayList<String>(max);
for(int i = 0; i < max; i++)
result.add(records.get(i).string);
return result;
This is now much more verbose than the three lines above (this is Java, after all), but also includes the code that would be required to insert the key/value pairs into the map.
Would something like the following work for you?
Note that I've assumed you don't require the double value other than to sort the data.
public static void main(String[] args) throws Exception {
List<String> data = new ArrayList<>(Arrays.asList("t", "h", "i", "s", "i", "s", "t", "e", "s", "t", "d", "a", "t", "a"));
Collections.sort(data, new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
double o1Value = evaluate(o1);
double o2Value = evaluate(o2);
return Double.compare(o1Value, o2Value);
}
});
List<String> result = data.subList(0, 10); // Note the end point is exclusive
for (String s : result) {
System.out.println(s);
}
}
private static double evaluate(String s) {
return s.codePointAt(0); // Nonsense, I know
}
This example prints:
a
a
d
e
h
i
i
s
s
s
Why don't you just create a class to combine the String, Double and function that does the calculation - something like:
public Thing implements Comparable<Thing>
{
private String s;
private Double d;
public Thing(String s)
{
this.s = s;
this.d = calculateDouble(s);
}
public String getString()
{
return this.s;
}
public Double getDouble()
{
return this.d;
}
public int compareTo(Thing other)
{
return getDouble().compareTo(other.getDouble());
}
public Double calculateDouble(String s)
{
...
}
}
Then all you need is a List<Thing>, Collections.sort and List.subList.