How to solve this CodingBat encoder problem? - java

Problem link: https://codingbat.com/prob/p238573
Requirement:
Write a function that replaces the words in raw with the words in code_words such that the first occurrence of each word in raw is assigned the first unassigned word in code_words.
encoder(["a"], ["1", "2", "3", "4"]) ā†’ ["1"]
encoder(["a", "b"], ["1", "2", "3", "4"]) ā†’ ["1", "2"]
encoder(["a", "b", "a"], ["1", "2", "3", "4"]) ā†’ ["1", "2", "1"]
I tried two different solutions but it still shows that my function doesn't work on "other tests"
First:
public String[] encoder(String[] raw, String[] code_words) {
HashMap<String, String> hm = new HashMap<String, String>();
for (int i=raw.length - 1; i >= 0; i--) {
hm.put(raw[i], code_words[i]);
}
String [] finalarray = new String[raw.length];
for (int i=0; i < raw.length; i++) {
String x = hm.get(raw[i]);
finalarray[i] = x;
}
return finalarray;
}
All tests were fine, but the "other tests" failed
so I thought it was because of this line in requirements
the first occurrence of each word in raw is assigned the first unassigned word in code_words
so I updated the code to this:
public String[] encoder(String[] raw, String[] code_words) {
HashMap<String, String> hm = new HashMap<String, String>();
for (int i=0; i < raw.length; i++) {
String word = raw[i];
String value = code_words[i];
if (!hm.containsKey(word)) {
if (hm.containsValue(value)) {
for (int i1=0; i1 < code_words.length; i1++) {
value = code_words[i1];
if (!hm.containsValue(value)) {
hm.put(word, value);
break;
}
}
}
else {
hm.put(word, value);
}
}
}
String[] finalarray = new String[raw.length];
for (int i=0; i < raw.length; i++) {
String x = hm.get(raw[i]);
finalarray[i] = x;
}
return finalarray;
}
But it failed and I don't know why is that.
EDIT:
The problem with my (Second) code was:
if we assume raw = {"a", "a", "b", "d"}
and code words = {"1", "2", "3", "4"}
my code would assign letter "a" to "1" and "b" to "3" and d to "4"
that would leave "2" unassigned even though, it was the first unassigned letter
the code I provided an work with few adjustments
public String[] encoder(String[] raw, String[] code_words) {
HashMap<String, String> hm = new HashMap<String, String>();
for (int i=0; i < raw.length; i++) {
String word = raw[i];
int assigned = 0;
String value = code_words[assigned];
if (!hm.containsKey(word)) {
if (hm.containsValue(value)) {
for (int i1=0; i1 < code_words.length; i1++) {
value = code_words[i1];
if (!hm.containsValue(value)) {
hm.put(word, value);
assigned++;
break;
}
}
}
else {
hm.put(word, value);
assigned++;
}
}
}
String[] finalarray = new String[raw.length];
for (int i=0; i < raw.length; i++) {
String x = hm.get(raw[i]);
finalarray[i] = x;
}
return finalarray;
}
but it's definitely more efficient to use the code provided below. thanks to the contributors!

The problem
Your first idea wasn't all that bad. The problem is, that you should replace all occurrences of a word in raw with the first unassigned word in code_words.
How to fix
Lets first analyse how to fix your first code. Your idea of using a HashMap is pretty good. Clearly, if a word of raw already exists in the HashMap you don't want to add it a second time, so you just skip it in your first iteration.
Now, if the ith word in raw has no assigned value in your HashMap, you should add it the first unassigned word of code_words, which may have a different index than i, so we assign it another index, let's say j. After that, the jth word has been assigned and the first unassigned word has index j+1.
After iterating like that once over raw, every word has an assigned code in your HashMap and you can iterate over it one more time and assign the values.
The Code
Your final code will look something like this:
public String[] encoder(String[] raw, String[] code_words) {
HashMap<String, String> dictionary = new HashMap<>();
String[] coded = new String[raw.length];
int j = 0;
for(int i = 0; i < raw.length; i++) {
if(!dictionary.containsKey(raw[i])) { //if it has no assigned value
dictionary.put(raw[i], code_words[j]); //add to hashmap
j++; //set index to next unassigned
}
//do nothing if already found before
}
for(int i = 0; i < raw.length; i++) {
coded[i] = dictionary.get(raw[i]); //get coded word and set in final array
}
return coded;
}
We can write this somewhat more compacter, which some may prefer and other might find more confusing, so it's up to you.
public String[] encoder(String[] raw, String[] code_words) {
HashMap<String, String> dictionary = new HashMap<>();
String[] coded = new String[raw.length];
int j = 0;
for(int i = 0; i < raw.length; i++) {
if(!dictionary.containsKey(raw[i])) { //if it has no assigned value
dictionary.put(raw[i], code_words[j++]); //add to hashmap and also increment index of code_words
}
coded[i] = dictionary.get(raw[i]);
}
return coded;
}
This last code passed all tests.

You're making it a lot more complex than it is.
Yes, you need the hm map, and yes, you only add to it if the raw word isn't already a key in the map.
But to keep track of the next unassigned code_word, all you need is an index into the code_words array.
Map<String, String> hm = new HashMap<>();
int unassigned = 0;
for (String word : raw) {
if (! hm.containsKey(word)) {
hm.put(word, code_words[unassigned]);
unassigned++;
}
}
The code of the entire method can be compacted to:
public String[] encoder(String[] raw, String[] code_words) {
String[] encoded = new String[raw.length];
Map<String, String> hm = new HashMap<>();
for (int i = 0, unassigned = 0; i < raw.length; i++)
if ((encoded[i] = hm.get(raw[i])) == null)
hm.put(raw[i], encoded[i] = code_words[unassigned++]);
return encoded;
}

Just update one line
hm.put(raw[i], code_words[raw[i].charAt(0)-'a']);

Related

How do I break this array into sub-array(at continuous position) of similar type of alphabets

For eg.,
the input array is :
String array = {"0","0","0","K","K","B","P","P","P","Z",
"Z","D","D","E","E","F","N","O","O}
Output:
first sub-array = {"O,O,O"}
second sub-array = {"K","K"}
third sub-array = {"O","O"}
You can do this using stack for that checkout below code for that.
String data[] = { "0", "0", "0", "K", "K", "B", "P", "P", "P", "Z", "Z", "D", "D", "E", "E", "F", "N" };
// a = ['0','0','0','K','K','P','P','P','Z']
Stack<String> stack = new Stack<String>();
String prevValue = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i].equals(data[i - 1])) {
prevValue = prevValue + data[i];
} else {
stack.push(prevValue);
prevValue = data[i];
}
}
stack.push(prevValue);
System.out.println(stack);
Assuming you don't know how many different characters you're looking for one possible solution would be using a Map:
Map<String,List<String>> map = new HashMap<>();
for(int i = 0; i < array.length; i++){
if(map.containsKey(array[i])
map.get(array[i]).add(array[i]);
else
map.put(array[i],array[i]);
}
However, personally I think what you're asking can be simplified with a Parameter style approach. This is, instead of storing each ocurrence of each string pattern you're looking for, you simply store a counter. So, and still assuming that you don't know how many distinct patterns you're looking for,you could do this:
Map<String,Integer> map = new HashMap<>();
for(int i = 0; i < array.length; i++){
map.put(array[i], new Integer(map.get(array[i]).intValue() + 1);
}
You can create a map with string as index and integer as value.
Then you can loop this array and assign the values of array as index of the map and keep increasing the integer value.
For example, you can add these lines inside the loop, and you will have a map:
Map<String, Integer> myCharMap = new HashMap<String, Integer>();
myCharMap.put(array[index], new Integer(myCharMap.get(array[index]).intValue()+1));
If you're looking for continuous regions, you can use looping since the order matters.
List<List<String>> continuous = new ArrayList<>();
List<String> current;
String last = null;
for(String s: array){
if(!s.equals(last)){
current = new ArrayList<>();
continuous.add(current);
}
current.add(s);
last=s;
}
In your example, you could use a stream and the grouping by collector since the regions are also unique characters.
Map<String, List<String>> grouped = Arrays.stream(array).collect(
Collectors.groupingBy(String::toString)
);
If you really need the String[] instead of List, you can use List.toArray.
String[] arr_version = grouped.get("0").toArray(new String[0]);

Look for duplicate values in a String[] without using Sets, Lists, ArrayLists?

Lets say we have this array: String[] arr1 = {"a", "b", "c", "a"};
What I'm trying to do is remove duplicate String (In this case "a") and add its value to another String[] called duplicates. When the duplicate is added to the "duplicates" array, the amount of times it occured wrongfully in the array arr1 is concatenated next to it. (recurredValue + amount) so in this example it would be a 1. I have searched for this before and all of them included usage of Lists, ArrayLists, or Sets. Please do not use any of them.
Use below code:-
public static String[] removeDuplicates(String[] numbersWithDuplicates) {
// Sorting array to bring duplicates together
Arrays.sort(numbersWithDuplicates);
String[] result = new String[numbersWithDuplicates.length];
String[] duplicate = new String[numbersWithDuplicates.length];
String previous = numbersWithDuplicates[0];
result[0] = previous;
int counter=1;
int duplicateCounter=0;
for (int i = 1; i < numbersWithDuplicates.length; i++) {
String ch = numbersWithDuplicates[i];
if (previous != ch) {
result[counter++] = ch;
}
else
{
duplicate[duplicateCounter++]=ch;
}
previous = ch;
}
for (int i = 0; i < result.length; i++) {
System.out.println(result[i]);
}
System.out.println("Duplicate Values are ---");
for (int i = 0; i < duplicate.length; i++) {
System.out.println(duplicate[i]);
}
return result;
}
maybe something like this you can use
public static void main(String[] args) {
String[] arr1 = {"a", "b", "c", "a", "a"};
String[] duplicates = new String[arr1.length];
boolean d = false;
int count = 0;
String dup = "";
for(int i = 0;i < arr1.length;i++){
count = 0;
dup = "";
d = false;
for(int j = 0;j < arr1.length;j++){
if(i != j){
if(arr1[i].equals(arr1[j]) && !arr1[i].equals("")){
arr1[j] = "";
d = true;
count++;
dup = arr1[i];
}
}
}
if(d){
duplicates[i] = dup + count;
d = false;}
}
for(int k = 0;k < duplicates.length;k++)
System.out.println(duplicates[k]);
}
Well, one solution is to create 2 arrays and have one hold the unique strings and the other its integer counter. As you index through your sample string array, add the unique strings and increment the incidents. This is not a very elegant or efficient solution but it should work.

Partition an Array with duplicate elements into arrays with unique elements

I have an Array which is structured like this :
String Array = {"1","2","3","41","56","41","72","72","72","78","99"}
and I want to partition this array into a number of arrays which values are not duplicates... like this :
String Array1 = {"1","2","3","41","56","72","78","99"}
String Array2 = {"41","72"}
String Array3 = {"72"}
is there any straight way to do this in Java or I have to do this with ugly loops (Just kidding !) ?
Thanks !
UPDATE
I'm gonna make the question a bit harder... now I have a Map which structure is like below :
Map<String,String> map = new HashMap(){{
put("1##96","10");
put("2##100","5");
put("3##23","100");
put("41##34","14");
put("56##22","25");
put("41##12","100");
put("72##10","100");
put("72##100","120");
put("72##21","0");
put("78##22","7");
}}
note that the values are not important BUT the keys are important...
what can I do to partition this map to submaps which are like :
Map map1 = {"1##96" => "10"
"2##100" => "5"
"3##23" => "100"
"41##34" => "14"
"56##22" => "25"
"72##10" => "100"
"78##22" => "7"
}
Map map2 = {
"41##12" => "100"
"72##100" => "120"
}
Map map3 = {
"72##100" => "120"
}
like before the first part of the map (before '##') is the ID which I want the uniqueness be based upon... this is just like the Array Example but a bit harder and more complex...
Sorry for changing the question midway...
Probably nothing in libs (seems not generic enough) but some ideas:
O(n) time and O(n) space complexity. Here you just count how many times each number occurs and then put them in that many resulting arrays.
#Edit: as #mpkorstanje pointed out if you change the input from numbers to strings or any other objects in the worst-worst case this will degrade to O(n^2). But in that case you should revise your hashing imho for the data on which you're working as it's not well distributed.
public List<List<Integer>> split(int[] input) {
Map<Integer, Integer> occurrences = new HashMap<>();
int maxOcc = 0;
for (int val : input) {
int occ = 0;
if (occurrences.containsKey(val)) {
occ = occurrences.get(val);
}
if (occ + 1 > maxOcc) {
maxOcc = occ + 1;
}
occurrences.put(val, occ + 1);
}
List<List<Integer>> result = new ArrayList<>(maxOcc);
for (int i = 0; i < maxOcc; i++) {
result.add(new LinkedList<>());
}
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
for (int i = 0; i < entry.getValue(); i++) {
result.get(i).add(entry.getKey());
}
}
return result;
}
O(nlogn) time and O(1) space complexity (not counting the resulting arrays) but doesn't retain order and "destroys" the input array. Here you utilize the fact that the array is already sorted so you can just go over it and keep adding the element to an appropriate resulting list depending on whether you're looking at a duplicate or a "new" entry.
public List<List<Integer>> split(int[] input) {
Arrays.sort(input);
int maxDup = getMaxDuplicateNumber(input);
List<List<Integer>> result = new ArrayList<>(maxDup);
for(int i = 0; i < maxDup; i++) {
result.add(new LinkedList<>());
}
int count = 0;
result.get(0).add(input[0]);
for(int i = 1; i < input.length; i++) {
if(input[i] == input[i-1]) {
count++;
} else {
count = 0;
}
result.get(count).add(input[i]);
}
return result;
}
private int getMaxDuplicateNumber(int[] input) {
int maxDups = 1;
int currentDupCount = 1;
for(int i = 1; i < input.length; i++) {
if(input[i] == input[i - 1]) {
currentDupCount++;
} else {
currentDupCount = 1;
}
if(currentDupCount > maxDups) {
maxDups = currentDupCount;
}
}
return maxDups;
}
You can't do this without loops. But you can use a set to remove some loops. You can add data structure trappings to your own liking.
I'm assuming here that the order of elements in the bins must be consistent with the order of the elements in the input array. If not this can be done more efficiently.
public static void main(String[] args) {
String[] array = { "1", "2", "3", "41", "56", "41", "72", "72", "72",
"78", "99" };
List<Set<String>> bins = new ArrayList<>();
for (String s : array) {
findOrCreateBin(bins, s).add(s);
}
System.out.println(bins); // Prints [[1, 2, 3, 41, 56, 72, 78, 99], [41, 72], [72]]
}
private static Set<String> findOrCreateBin(List<Set<String>> bins, String s) {
for (Set<String> bin : bins) {
if (!bin.contains(s)) {
return bin;
}
}
Set<String> bin = new LinkedHashSet<>();
bins.add(bin);
return bin;
}

compare list and array

I need to compare the value from List with the value from array.
I wrote the following:
public class JavaApplication3 {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic hereut
List<String> l = new ArrayList<String>();
l.add("test");
l.add("b");
String v = "";
String s = "";
String[] arr = {"test", "c", "b"};
for (int i = 0; i < l.size(); i++){
v = "";
s = "";
//System.out.println(l.get(i));
for (int j = 0; j < arr.length; j++){
if (l.get(i).equals(arr[j])){
s = i + "";
}else{
s = arr[i];
}
v = v + s + ",";
}
System.out.println(v);
}
}
}
I obtain the following
0,test,test,
c,c,1
but I need the result like this:
0, c, 1,
Looking at your expected result I guess the requirement like that:
for each element in the array, check if it is on the list. If it is on the list, print the index from the list for this element, otherwise print the element itself. So the algorithm should do:
array[0] = "test" -> found at index 0 -> print "0"
array[1] = "c" -> not found -> print "c"
array[2] = "b" -> found at index 1 -> print "1"
The outer loop should iterate over the array. Then, for each array item, iterate over the list until you find the same element. For a first draft, don't collect the output in a string but print it immediatly. You can create the string when the algorithm works as expected.
You have six iterations, each of which inserts something into the output.
You want three iterations, each of which checks for membership in the first list. You can do that with the List.contains() method. (If the list were long, you might want to consider using a Set instead of a List, to allow checking set membership more quickly.)
How about this:
public static void main(String[] args) {
// TODO code application logic hereut
List<String> l = new ArrayList<String>();
l.add("test");
l.add("b");
String v = "";
String s = "";
String[] arr = {"test", "c", "b"};
int pointer = 0;
for (int i = 0; i < l.size(); i++){
//System.out.println(l.get(i));
for (; pointer < arr.length;){
if (l.get(i).equals(arr[pointer])){
s = i + "";
v = v + s + ",";
pointer++;
break;
}else{
s = arr[i];
}
pointer++;
v = v + s + ",";
}
}
System.out.println(v);
}
Try to break things down to their high level steps.
For each string in the array
find its place in the list
if the item is in the list
print its position
else
print the missing string
print a common and space
Once you have this you can spot that find its place in the list could be a method that returns the place in the list or -1 if it isn't in the list. Here's what I made (might have renamed a few things and used a StringBuilder but you can ignore that for the moment).
import java.util.ArrayList;
import java.util.List;
public class Example {
public static void main(final String[] args) {
final List<String> listToSeach = new ArrayList<String>();
listToSeach.add("test");
listToSeach.add("b");
final String[] arrayElementsToFind = { "test", "c", "b" };
final StringBuilder output = new StringBuilder();
for (final String string : arrayElementsToFind) {
final int firstIndex = findFirstIndex(listToSeach, string);
if (firstIndex > -1) {
output.append(firstIndex);
} else {
output.append(string);
}
output.append(", ");
}
System.out.println(output);
}
private static int findFirstIndex(final List<String> list,
final String element) {
for (int i = 0; i < list.size(); i++) {
if (list.get(i).equals(element)) {
return i;
}
}
return -1;
}
}
Well I suggest this:
List<String> l = new ArrayList<String>();
l.add("test");
l.add("b");
String[] arr = {"test", "c", "b"};
for(int i=0;i<arr.length;++i){
if(l.contains(arr[i]))
s = ""+l.indexOf(arr[i]);
else
s = arr[i];
v = v + s + ",";
}
If got what you saying correct,I think this is less verbose

Java count occurrence of each item in an sorted array

I have an Array of Strings and want to count the occurrences of any single String.
I have already sorted it. (It's a long Array and I wanted to get rid of the O(nĀ²)-loop)
Here my code.. obviously it runs out in an ind.outOfB. exc.. the reason is clear but I donno how to solve..
for (int i = 0; i < patternsTest.length-1; i++) {
int occ=1;
String temp=patternsTest[i];
while(temp.equals(patternsTest[i+1])){
i++;
occ++;
}
}
This would be a good place for a HashMap, the key would be the Word, and the value the Number of times it occurs. The Map.containsKey and Map.get methods are constant time lookups which are very fast.
Map<String,Integer> map = new HashMap<String,Integer>();
for (int i = 0; i < patternsTest.length; i++) {
String word=patternsTest[i];
if (!map.containsKey(word)){
map.put(word,1);
} else {
map.put(word, map.get(word) +1);
}
}
As a side benefit you don't even need to sort beforehand!
You can use Java HashMap:
Map<String, Integer> occurrenceOfStrings = new HashMap<String, Integer>();
for(String str: patternsTest)
{
Integer currentValue = occurrenceOfStrings.get(str);
if(currentValue == null)
occurrenceOfStrings.put(str, 1);
else
occurrenceOfStrings.put(str, currentValue + 1);
}
This does not have index out of bounds:
String[] patternsTest = {"a", "b"};
for (int i = 0; i < patternsTest.length-1; i++) {
int occ=1;
String temp=patternsTest[i];
while(temp.equals(patternsTest[i+1])){
i++;
occ++;
}
}
You can cause an Index Out of Bounds by changing the data to:
String[] patternsTest = {"a", "a"};
you could try a map and only one loop
Map<String, Integer> occurences = new HashMap<String, Integer>();
String currentString = patternsTest[0];
Integer count = 1;
for (int i = 1; i < patternsTest.length; i++) {
if(currentString.equals(patternsTest[i]) {
count++;
} else {
occurrences.put(currentString, count);
currentString = patternsTest[i];
count = 1;
}
}
occurrences.put(currentString, count);
Guava Multiset solution (two lines of code):
Multiset<String> multiset = HashMultiset.create();
multiset.addAll(Arrays.asList(patternsTest));
//Then you could do...
multiset.count("hello");//Return count the number of occurrences of "hello".
We could use it both sorted and un-sorted arrays. Easy to maintain code.
My solution is:
public int cantOccurences(String pattern, String[] values){
int count = 0;
for (String s : values) {
count += (s.replaceAll("[^".concat(pattern).concat("]"), "").length());
}
return count;
}

Categories

Resources