// The array of values is in the form { "var1", "val1", "var2", "val2",.. }
public static String replaceMethod(String template, String... values) {
String p = template;
for (int i = 0; i < values.length; i += 2) {
p = p.replace(values[i], values[i+1]);
}
return populatedTemplate;
}
This method replaces a set of sub-strings with respectives values in a string.
Ex:- string => abcd
sub-string array of values ["a","b","c","d"]
It means replace 'a' with 'b' and 'b' with 'c'.
So, now comes the issue.
1.My above implementation replaces 'a' with 'b' in first loop and then in second loop, it replaces 'b' with 'c' , making final string as "cccd". But I want the out as "bccd". Replacement in series uses the previous replaced string to work further.
Is there a way to parallel replace all the sub-strings in a more efficient way.
2.How can I implement above method using Map, instead of array (second argument of above method)? Is Map better, or above implementation is fine ?
You better use a map and iterate over every character in template. This way you avoid multiple substitutions at the same index position.
String template = "abcd";
StringBuilder populatedTemplate = new StringBuilder();
HashMap<Character, Character> map = new HashMap<>();
map.put('a', 'b');
map.put('b', 'c');
for (int i = 0; i < template.length(); i++) {
populatedTemplate.append(
map.getOrDefault(template.charAt(i), template.charAt(i)));
}
System.out.println(populatedTemplate);
Some pro arguments of this solution compared to OPs posted solution.
avoid multiple substitutions at the same index position, replace in a first step a to b and in a second step b to c which would lead into an unexpected effective replacement of a to c
iterate only once over template
don't create for each replacement pair a new String object, as it would have been here p = p.replace(values[i], values[i+1]);
In the particular case of your a to b, b to c and such comparisons, you can get the desired result by giving the values array in reverse order.
In a more general case, you could replace 'a' with 'b', then 'b' with 'c'... And so on, and finally remove all the underscores. Of course, is your template text contains underscores, you can choose another character that you know won't be there, and use it instead.
Go with the solution using Map instead of String array to avoid getting ArrayIndexOutOfBoundsException. for instance you will get exceptions with this call using String array:
replaceMethod("abcd", "a","b","c")
Using Map:
public static String replaceMethodMap(String template, Map<String, String> map) {
String p = template;
for (String key : map.keySet()) {
p = p.replace(key, map.get(key));
}
return p;
}
I have a CSV file which is a coauthorship network.Entries are like this:
1992,M_DINE,R_LEIGH,P_HUET,A_LINDE,D_LINDE
1992,C_BURGESS,J_CLINE,M_LUTY
1992,M_DINE,R_LEIGH,P_HUET,A_LINDE,D_LINDE
1992,F_ZWIRNER
1992,O_HERNANDEZ
...
I want to replace all the authors names with unique numbers using hashmaps
I want the output to be sth like this:
M_DINE 1
R_LEIGH 2
P_HUET 3
...
and I do not want the years to be included.
Maintain a collection of author names, Read each line, split on comma, discard year, for each author if collection does not contain author name then add it to the collection. Then read from the collection and assign number.
Conceptually, you've explained everything you want to do.
You'll need a few objects.
HashMap<String, Integer> authorList = new HashMap<>();
int authorCounter = 0;
The map, obviously, and a counter to track your index.
I don't know what your main method is, but you need to process the input.
public void foo() {
// ... handle the file import through scanner in
while(in.hasNext()) {
String input = in.next();
String[] authors = input.split(',');
for(int i = 1; i < authors.length; i++) { //skip index 0 because that's the year
addAuthor(authors[i]);
}
}
}
This is a really plain addAuthor function. You probably want to extract it into hasAuthor and getAuthorId and so on, but this will just add an author if it doesn't find it with the latest iterated counter.
void addAuthor(String s) {
if(authorList.get(s) == null)
authorList.put(s, ++authorCounter);
}
I am a beginner in Java. Basically, I have loaded each text document and stored each individual words in the text document in the hasmap. Afterwhich, I tried storing all the hashmaps in an ArrayList. Now I am stuck with how to retrieve all the words in my hashmaps that is in the arraylist!
private static long numOfWords = 0;
private String userInputString;
private static long wordCount(String data) {
long words = 0;
int index = 0;
boolean prevWhiteSpace = true;
while (index < data.length()) {
//Intialise character variable that will be checked.
char c = data.charAt(index++);
//Determine whether it is a space.
boolean currWhiteSpace = Character.isWhitespace(c);
//If previous is a space and character checked is not a space,
if (prevWhiteSpace && !currWhiteSpace) {
words++;
}
//Assign current character's determination of whether it is a spacing as previous.
prevWhiteSpace = currWhiteSpace;
}
return words;
} //
public static ArrayList StoreLoadedFiles()throws Exception{
final File f1 = new File ("C:/Users/Admin/Desktop/dataFiles/"); //specify the directory to load files
String data=""; //reset the words stored
ArrayList<HashMap> hmArr = new ArrayList<HashMap>(); //array of hashmap
for (final File fileEntry : f1.listFiles()) {
Scanner input = new Scanner(fileEntry); //load files
while (input.hasNext()) { //while there are still words in the document, continue to load all the words in a file
data += input.next();
input.useDelimiter("\t"); //similar to split function
} //while loop
String textWords = data.replaceAll("\\s+", " "); //remove all found whitespaces
HashMap<String, Integer> hm = new HashMap<String, Integer>(); //Creates a Hashmap that would be renewed when next document is loaded.
String[] words = textWords.split(" "); //store individual words into a String array
for (int j = 0; j < numOfWords; j++) {
int wordAppearCount = 0;
if (hm.containsKey(words[j].toLowerCase().replaceAll("\\W", ""))) { //replace non-word characters
wordAppearCount = hm.get(words[j].toLowerCase().replaceAll("\\W", "")); //remove non-word character and retrieve the index of the word
}
if (!words[j].toLowerCase().replaceAll("\\W", "").equals("")) {
//Words stored in hashmap are in lower case and have special characters removed.
hm.put(words[j].toLowerCase().replaceAll("\\W", ""), ++wordAppearCount);//index of word and string word stored in hashmap
}
}
hmArr.add(hm);//stores every single hashmap inside an ArrayList of hashmap
} //end of for loop
return hmArr; //return hashmap ArrayList
}
public static void LoadAllHashmapWords(ArrayList m){
for(int i=0;i<m.size();i++){
m.get(i); //stuck here!
}
Firstly your login wont work correctly. In the StoreLoadedFiles() method you iterate through the words like for (int j = 0; j < numOfWords; j++) { . The numOfWords field is initialized to zero and hence this loop wont execute at all. You should initialize that with length of words array.
Having said that to retrieve the value from hashmap from a list of hashmap, you should first iterate through the list and with each hashmap you could take the entry set. Map.Entry is basically the pair that you store in the hashmap. So when you invoke map.entrySet() method it returns a java.util.Set<Map.Entry<Key, Value>>. A set is returned because the key will be unique.
So a complete program will look like.
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Scanner;
public class FileWordCounter {
public static List<HashMap<String, Integer>> storeLoadedFiles() {
final File directory = new File("C:/Users/Admin/Desktop/dataFiles/");
List<HashMap<String, Integer>> listOfWordCountMap = new ArrayList<HashMap<String, Integer>>();
Scanner input = null;
StringBuilder data;
try {
for (final File fileEntry : directory.listFiles()) {
input = new Scanner(fileEntry);
input.useDelimiter("\t");
data = new StringBuilder();
while (input.hasNext()) {
data.append(input.next());
}
input.close();
String wordsInFile = data.toString().replaceAll("\\s+", " ");
HashMap<String, Integer> wordCountMap = new HashMap<String, Integer>();
for(String word : wordsInFile.split(" ")){
String strippedWord = word.toLowerCase().replaceAll("\\W", "");
int wordAppearCount = 0;
if(strippedWord.length() > 0){
if(wordCountMap.containsKey(strippedWord)){
wordAppearCount = wordCountMap.get(strippedWord);
}
wordCountMap.put(strippedWord, ++wordAppearCount);
}
}
listOfWordCountMap.add(wordCountMap);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if(input != null) {
input.close();
}
}
return listOfWordCountMap;
}
public static void loadAllHashmapWords(List<HashMap<String, Integer>> listOfWordCountMap) {
for(HashMap<String, Integer> wordCountMap : listOfWordCountMap){
for(Entry<String, Integer> wordCountEntry : wordCountMap.entrySet()){
System.out.println(wordCountEntry.getKey() + " - " + wordCountEntry.getValue());
}
}
}
public static void main(String[] args) {
List<HashMap<String, Integer>> listOfWordCountMap = storeLoadedFiles();
loadAllHashmapWords(listOfWordCountMap);
}
}
Since you are beginner in Java programming I would like to point out a few best practices that you could start using from the beginning.
Closing resources : In your while loop to read from files you are opening a Scanner like Scanner input = new Scanner(fileEntry);, But you never closes it. This causes memory leaks. You should always use a try-catch-finally block and close resources in finally block.
Avoid unnecessary redundant calls : If an operation is the same while executing inside a loop try moving it outside the loop to avoid redundant calls. In your case for example the scanner delimiter setting as input.useDelimiter("\t"); is essentially a one time operation after a scanner is initialized. So you could move that outside the while loop.
Use StringBuilder instead of String : For repeated string manipulations such as concatenation should be done using a StringBuilder (or StringBuffer when you need synchronization) instead of using += or +. This is because String is an immutable object, meaning its value cannot be changed. So each time when you do a concatenation a new String object is created. This results in a lot of unused instances in memory. Where as StringBuilder is mutable and values could be changed.
Naming convention : The usual naming convention in Java is starting with lower-case letter and first letter upper-case for each word. So its a standard practice to name a method as storeLoadedFiles as opposed to StoreLoadedFiles. (This could be opinion based ;))
Give descriptive names : Its a good practice to give descriptive names. It helps in later code maintenance. Say its better to give a name as wordCountMap as opposed to hm. So in future if someone tries to go through your code they'll get a better and faster understanding about your code with descriptive names. Again opinion based.
Use generics as much as possible : This avoid additional casting overhead.
Avoid repetition : Similar to point 2 if you have an operation that result in the same output and need to be used multiple times try moving it to a variable and use the variable. In your case you were using words[j].toLowerCase().replaceAll("\\W", "") multiple times. All the time the result is the same but it creates unnecessary instances and repetitions. So you could move that to a String and use that String elsewhere.
Try using for-each loop where ever possible : This relieves us from taking care of indexing.
These are just suggestions. I tried to include most of it in my code but I wont say its the perfect one. Since you are a beginner if you tried to include these best practices now itself it'll get ingrained in you. Happy coding.. :)
for (HashMap<String, Integer> map : m) {
for(Entry<String,Integer> e:map.entrySet()){
//your code here
}
}
or, if using java 8 you can play with lambda
m.stream().forEach((map) -> {
map.entrySet().stream().forEach((e) -> {
//your code here
});
});
But before all you have to change method signature to public static void LoadAllHashmapWords(List<HashMap<String,Integer>> m) otherwise you would have to use a cast.
P.S. are you sure your extracting method works? I've tested it a bit and had list of empty hashmaps all the time.
During my work with databases I noticed that I write query strings and in this strings I have to put several restrictions in the where-clause from a list/array/collection. Should look like this:
select * from customer
where customer.id in (34, 26, ..., 2);
You can simplify this by reducing this to the question that you have collection of strings and want to create a comma-separated list of this strings in just one string.
My approach I have used so far is something like that:
String result = "";
boolean first = true;
for(String string : collectionOfStrings) {
if(first) {
result+=string;
first=false;
} else {
result+=","+string;
}
}
But this is as you can see very ugly. You cannot see what happens there on the first look, especially when the constructed strings (like every SQL query) is getting complicated.
What is your (more) elegant way?
Use the Google Guava API's join method:
Joiner.on(",").join(collectionOfStrings);
Note: This answers was good when it was written 11 years ago, but now there are far better options to do this more cleanly in a single line, both using only Java built-in classes or using a utility library. See other answers below.
Since strings are immutable, you may want to use the StringBuilder class if you're going to alter the String in the code.
The StringBuilder class can be seen as a mutable String object which allocates more memory when its content is altered.
The original suggestion in the question can be written even more clearly and efficiently, by taking care of the redundant trailing comma:
StringBuilder result = new StringBuilder();
for(String string : collectionOfStrings) {
result.append(string);
result.append(",");
}
return result.length() > 0 ? result.substring(0, result.length() - 1): "";
I just looked at code that did this today. This is a variation on AviewAnew's answer.
collectionOfStrings = /* source string collection */;
String csList = StringUtils.join(collectionOfStrings.toArray(), ",");
The StringUtils ( <-- commons.lang 2.x, or commons.lang 3.x link) we used is from Apache Commons.
The way I write that loop is:
StringBuilder buff = new StringBuilder();
String sep = "";
for (String str : strs) {
buff.append(sep);
buff.append(str);
sep = ",";
}
return buff.toString();
Don't worry about the performance of sep. An assignment is very fast. Hotspot tends to peel off the first iteration of a loop anyway (as it often has to deal with oddities such as null and mono/bimorphic inlining checks).
If you use it lots (more than once), put it in a shared method.
There is another question on stackoverflow dealing with how to insert a list of ids into an SQL statement.
Since Java 8, you can use:
String String.join(CharSequence delimiter, CharSequence... elements)
String String.join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
If you want to take non-Strings and join them to a String, you can use Collectors.joining(CharSequence delimiter), e.g.:
String joined = anyCollection.stream().map(Object::toString).collect(Collectors.joining(","));
I found the iterator idiom elegant, because it has a test for more elements (ommited null/empty test for brevity):
public static String convert(List<String> list) {
String res = "";
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
res += iterator.next() + (iterator.hasNext() ? "," : "");
}
return res;
}
There's a lot of manual solutions to this, but I wanted to reiterate and update Julie's answer above. Use google collections Joiner class.
Joiner.on(", ").join(34, 26, ..., 2)
It handles var args, iterables and arrays and properly handles separators of more than one char (unlike gimmel's answer). It will also handle null values in your list if you need it to.
String.join(", ", collectionOfStrings)
available in the Java8 api.
alternative to (without the need to add a google guava dependency):
Joiner.on(",").join(collectionOfStrings);
Here's an incredibly generic version that I've built from a combination of the previous suggestions:
public static <T> String buildCommaSeparatedString(Collection<T> values) {
if (values==null || values.isEmpty()) return "";
StringBuilder result = new StringBuilder();
for (T val : values) {
result.append(val);
result.append(",");
}
return result.substring(0, result.length() - 1);
}
You could try
List collections = Arrays.asList(34, 26, "...", 2);
String asString = collection.toString();
// justValues = "34, 26, ..., 2"
String justValues = asString.substring(1, asString.length()-1);
This will be the shortest solution so far, except of using Guava or Apache Commons
String res = "";
for (String i : values) {
res += res.isEmpty() ? i : ","+i;
}
Good with 0,1 and n element list. But you'll need to check for null list.
I use this in GWT, so I'm good without StringBuilder there. And for short lists with just couple of elements its ok too elsewhere ;)
In case someone stumbled over this in more recent times, I have added a simple variation using Java 8 reduce(). It also includes some of the already mentioned solutions by others:
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import com.google.common.base.Joiner;
public class Dummy {
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "de", "fg");
String commaSeparated = strings
.stream()
.reduce((s1, s2) -> {return s1 + "," + s2; })
.get();
System.out.println(commaSeparated);
System.out.println(Joiner.on(',').join(strings));
System.out.println(StringUtils.join(strings, ","));
}
}
In Android you should use this:
TextUtils.join(",",collectionOfStrings.toArray());
I think it's not a good idea contruct the sql concatenating the where clause values like you are doing :
SELECT.... FROM.... WHERE ID IN( value1, value2,....valueN)
Where valueX comes from a list of Strings.
First, if you are comparing Strings they must be quoted, an this it isn't trivial if the Strings could have a quote inside.
Second, if the values comes from the user,or other system, then a SQL injection attack is possible.
It's a lot more verbose but what you should do is create a String like this:
SELECT.... FROM.... WHERE ID IN( ?, ?,....?)
and then bind the variables with Statement.setString(nParameter,parameterValue).
Just another method to deal with this problem. Not the most short, but it is efficient and gets the job done.
/**
* Creates a comma-separated list of values from given collection.
*
* #param <T> Value type.
* #param values Value collection.
* #return Comma-separated String of values.
*/
public <T> String toParameterList(Collection<T> values) {
if (values == null || values.isEmpty()) {
return ""; // Depending on how you want to deal with this case...
}
StringBuilder result = new StringBuilder();
Iterator<T> i = values.iterator();
result.append(i.next().toString());
while (i.hasNext()) {
result.append(",").append(i.next().toString());
}
return result.toString();
}
There are some third-party Java libraries that provide string join method, but you probably don't want to start using a library just for something simple like that. I would just create a helper method like this, which I think is a bit better than your version, It uses StringBuffer, which will be more efficient if you need to join many strings, and it works on a collection of any type.
public static <T> String join(Collection<T> values)
{
StringBuffer ret = new StringBuffer();
for (T value : values)
{
if (ret.length() > 0) ret.append(",");
ret.append(value);
}
return ret.toString();
}
Another suggestion with using Collection.toString() is shorter, but that relies on Collection.toString() returning a string in a very specific format, which I would personally not want to rely on.
If you use Spring, you can do:
StringUtils.arrayToCommaDelimitedString(
collectionOfStrings.toArray()
)
(package org.springframework.util)
List<String> collectionOfStrings = // List of string to concat
String csvStrings = StringUtils.collectionToDelimitedString(collectionOfStrings, ",");
StringUtils from springframeowrk:spring-core
I'm not sure how "sophisticated" this is, but it's certainly a bit shorter. It will work with various different types of collection e.g. Set<Integer>, List<String>, etc.
public static final String toSqlList(Collection<?> values) {
String collectionString = values.toString();
// Convert the square brackets produced by Collection.toString() to round brackets used by SQL
return "(" + collectionString.substring(1, collectionString.length() - 1) + ")";
}
Exercise for reader: modify this method so that it correctly handles a null/empty collection :)
What makes the code ugly is the special-handling for the first case. Most of the lines in this small snippet are devoted, not to doing the code's routine job, but to handling that special case. And that's what alternatives like gimel's solve, by moving the special handling outside the loop. There is one special case (well, you could see both start and end as special cases - but only one of them needs to be treated specially), so handling it inside the loop is unnecessarily complicated.
I've just checked-in a test for my library dollar:
#Test
public void join() {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
String string = $(list).join(",");
}
it create a fluent wrapper around lists/arrays/strings/etc using only one static import: $.
NB:
using ranges the previous list can be re-writed as $(1, 5).join(",")
The nice thing about the IN expression is that if you have repeated values, it does not change the result. So, just duplicate the first item and process the entire list. This assumes that there is at least one item in the list. If there are no items, I'd suggest checking for that first and then not executing the SQL at all.
This will do the trick, is obvious in what it is doing and does not rely on any external libraries:
StringBuffer inString = new StringBuffer(listOfIDs.get(0).toString());
for (Long currentID : listOfIDs) {
inString.append(",").append(currentID);
}
While I think your best bet is to use Joiner from Guava, if I were to code it by hand I find this approach more elegant that the 'first' flag or chopping the last comma off.
private String commas(Iterable<String> strings) {
StringBuilder buffer = new StringBuilder();
Iterator<String> it = strings.iterator();
if (it.hasNext()) {
buffer.append(it.next());
while (it.hasNext()) {
buffer.append(',');
buffer.append(it.next());
}
}
return buffer.toString();
}
if you have an array you can do:
Arrays.asList(parameters).toString()
Another option, based on what I see here (with slight modifications).
public static String toString(int[] numbers) {
StringBuilder res = new StringBuilder();
for (int number : numbers) {
if (res.length() != 0) {
res.append(',');
}
res.append(number);
}
return res.toString();
}
Join 'methods' are available in Arrays and the classes that extend AbstractCollections but doesn't override toString() method (like virtually all collections in java.util).
For instance:
String s= java.util.Arrays.toString(collectionOfStrings.toArray());
s = s.substing(1, s.length()-1);// [] are guaranteed to be there
That's quite weird way since it works only for numbers alike data SQL wise.
You may be able to use LINQ (to SQL), and you may be able to make use of the Dynamic Query LINQ sample from MS. http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
java.util.List<String> lista = new java.util.ArrayList<String>();
lista.add("Hola");
lista.add("Julio");
System.out.println(lista.toString().replace('[','(').replace(']',')'));
$~(Hola, Julio)
String commaSeparatedNames = namesList.toString().replaceAll( "[\\[|\\]| ]", "" ); // replace [ or ] or blank
The string representation consists of a list of the collection's
elements in the order they are returned by its iterator, enclosed in
square brackets ("[]"). Adjacent elements are separated by the
characters ", " (comma and space).
AbstractCollection javadoc
List token=new ArrayList(result);
final StringBuilder builder = new StringBuilder();
for (int i =0; i < tokens.size(); i++){
builder.append(tokens.get(i));
if(i != tokens.size()-1){
builder.append(TOKEN_DELIMITER);
}
}
builder.toString();