My question is how do I change my code so that it prints out my first print line along with the lines printed from the testBuildCodonMap method? The print line that currently doesn't print out is System.out.println(s+"\t"+codonMap.get(s));. I want this to be included with the other print statements.
import java.util.*;
import edu.duke.*;
public class CodonCount {
private HashMap<String,Integer> codonMap;
public CodonCount() {
codonMap = new HashMap<String,Integer>();
}
private void buildCodonMap(int start, String dna) {
//clear out map before building
codonMap.clear();
//This method will build a new map of codons mapped to
//their counts from the string dna with the reading frame
//with the position start (a value of 0, 1, or 2).
for (int index=start; dna.length() - index > 3;index+=3) {
String currentCodon = dna.substring(index,index+3);
if (!codonMap.containsKey(currentCodon)) {
codonMap.put(currentCodon,1);
}
else {
codonMap.put(currentCodon,codonMap.get(currentCodon)+1);
}
}
}
private String getMostCommonCodon() {
//get the codon in a reading frame that has the largest count
//this method assumes the HashMap of codons to counts has already been built
int currentHigh = 0;
String mostCommonCodon = "";
for (String s : codonMap.keySet()) {
int currentCount = codonMap.get(s);
if (currentCount > currentHigh) {
mostCommonCodon = s;
currentHigh = currentCount;
}
}
return mostCommonCodon;
}
private void printCodonCounts(int start, int end) {
//This method prints all the codons in the HashMap along with their
//counts if their count is between start and end, inclusive.
for (String s : codonMap.keySet()) {
if (codonMap.get(s) >= start && codonMap.get(s) <= end) {
System.out.println(s+"\t"+codonMap.get(s));
}
}
}
public void testBuildCodonMap() {
FileResource fileResource = new FileResource();
String dna = fileResource.asString();
dna = dna.toUpperCase();
for (int index=0;index <= 2;index++) {
System.out.println("\nTesting with start position "+index+":\n");
buildCodonMap(index,dna);
String mostCommonCodon = getMostCommonCodon();
System.out.println("Total unique codons found: "+codonMap.size());
System.out.println("\nMost common codon: "+mostCommonCodon
+"\t"+codonMap.get(mostCommonCodon));
printCodonCounts(4,8);
}
}
}
Sample file for testing: CGTTCAAGTTCAA
EDIT: I want the output to look something like this:
Reading frame starting with 0 results in 3 unique codons
and most common codon is TCA with count 2
Counts of codons between 1 and 5 inclusive are:
CGT 1
TCA 2
AGT 1
Reading frame starting with 1 results in 2 unique codons
and most common codon is CAA with count 2
Counts of codons between 1 and 5 inclusive are:
CAA 2
GTT 2
Reading frame starting with 2 results in 2 unique codons
and most common codon is TTC with count 2
Counts of codons between 1 and 5 inclusive are:
TTC 2
AAG 1
I got it! The line printCodonCounts(4,8); inside of the public void testBuildCodonMap() method needs to be changed to be printCodonCounts(1,3);.
That allows the print statement inside of method private void printCodonCounts(int start, int end) to execute.
Related
So in this Swedish card game (I think it's called 31 or Scat), we have to calculate a deck of 3 cards. If u have 2 spades and 1 diamond your score is either the score of the 2 spades values' added together or the value of that one diamond dependent on which alternative gives you the most points. My system is that I create a method that will evaluate the string array of 3 cards like this: if u have a spade card with a 3 then as a string it's stored "S03" in the array.
In order to see which cards points can be added together I will see if the first character of their string is the same and if so.. Then the 2 other characters will be converted into a number like 03 (3)
Which is what I tried here:
int Card1Value = Integer.parseInt(String.valueOf(CardDeck[1].charAt(2))+String.valueOf(CardDeck[1].charAt(3)));
The if conditions are my attempt of determining the score.
The reason why my method is a return int is so I can get the playerdeckvalue scores determined
My code:
public class CardDecks {
public static int DeckValue( String[] CardDeck) {
int Card1Value = Integer.parseInt( String.valueOf(CardDeck[1].charAt(2))+String.valueOf(CardDeck[1].charAt(3)));
int Card2Value = Integer.parseInt( String.valueOf(CardDeck[2].charAt(2))+String.valueOf(CardDeck[2].charAt(3)));
int Card3Value = Integer.parseInt( String.valueOf(CardDeck[3].charAt(2))+String.valueOf(CardDeck[3].charAt(3)));
int AltValue1;
int AltValue2;
int AltValue3;
int CardDeckValue = 0;
if (CardDeck[0].charAt(1)==CardDeck[1].charAt(1) &&
CardDeck[0].charAt(1)==CardDeck[2].charAt(1)) {
CardDeckValue = Card1Value + Card2Value + Card3Value;
return CardDeckValue;
}
else if (CardDeck[0].charAt(1)==CardDeck[1].charAt(1)) {
AltValue1 = Card3Value;
AltValue2 = Card1Value+Card2Value;
CardDeckValue = Math.max(AltValue1, AltValue2);
return CardDeckValue;
}
else if (CardDeck[0].charAt(1)==CardDeck[2].charAt(1)) {
AltValue1 = Card2Value;
AltValue2 = Card1Value+Card3Value;
CardDeckValue = Math.max(AltValue1, AltValue2);
return CardDeckValue;
}
else if (CardDeck[1].charAt(2)==CardDeck[2].charAt(1)) {
AltValue1 = Card1Value;
AltValue2 = Card2Value+Card3Value;
CardDeckValue = Math.max(AltValue1, AltValue2);
return CardDeckValue;
}
else if (CardDeck[0].charAt(1)!=CardDeck[1].charAt(1) &&
CardDeck[0].charAt(1)!=CardDeck[2].charAt(1)) {
AltValue1 = Card1Value;
AltValue2 = Card2Value;
AltValue3 = Card3Value;
CardDeckValue= Math.max(AltValue2, Math.max(AltValue2, AltValue3));
return CardDeckValue;
}
return CardDeckValue;
}
public static void main(String[] args) {
String[] PlayerDeck = new String[]{"K01", "K03", "D10" };
System.out.println( DeckValue(PlayerDeck) );
}
}
The feedback I got was:
Exception in thread "main" java.lang.StringIndexOutOfBoundsException:
String index out of range: 3 at
java.base/java.lang.StringLatin1.charAt(StringLatin1.java:48) at
java.base/java.lang.String.charAt(String.java:1515) at
problem3.DeckValue(problem3.java:8) at
problem3.main(problem3.java:69)
CharAt(i) actually returns the character at position i in strings. It works because strings are arrays of characters.
In programming, all arrays start at position 0.
When you type CharAt(3), it's looking for the forth character of your string.
0 -> S
1 -> 0
2 -> 3
3 -> out of bounds
Does anyone know how to search word form ArrayList by keyword? If the programfind the word, returns the found word as many times as it occurs, applying paging and limiting the result to the input data.
For example:
Input data:
(2, 1, " ")
Result:
Items: University, Java; Total items 11; Pages 6
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static void main(String[]args)
{
ArrayList<String> arraylist = new ArrayList<String>();
arraylist.add("University");
arraylist.add("Java");
arraylist.add("JavaScript");
arraylist.add("PHP");
arraylist.add("HTML");
arraylist.add("CSS");
arraylist.add("Spring");
arraylist.add("Vue");
arraylist.add("Angular");
arraylist.add("CSS");
arraylist.add("CSS");
int pageSize = 2;
int pageNumber = 0;
int from = Math.min(0,pageNumber * pageSize);
int to = Math.min(arraylist.size(),(pageNumber+1)*pageSize);
arraylist.subList(from,to);
System.out.println(pageSize);
System.out.println(pageNumber);
Scanner userInput = new Scanner(System.in);
System.out.println("Enter keyword");
String input = userInput.nextLine();
Since cannot figure out the entire requirement even with additional explanations, maybe the following code will fit your needs.
public class TestOccurencePerPage {
public static void main(String[]args)
{
ArrayList<String> arraylist = new ArrayList<String>();
arraylist.add("University");
arraylist.add("Java");
arraylist.add("JavaScript");
arraylist.add("PHP");
arraylist.add("HTML");
arraylist.add("CSS");
arraylist.add("Spring");
arraylist.add("Vue");
arraylist.add("Angular");
arraylist.add("CSS");
arraylist.add("CSS");
String key="CSS";
int[] test1 = getOccurencesPages(arraylist,2, key);
//11 entries last 2 CSS are on different pages(5 and 6)
//so each count individually onPage_counter
int[] test2 = getOccurencesPages(arraylist,6, key);
int[] test3 = getOccurencesPages(arraylist,20, key);
System.out.format("key=%s, total=%s, onPages=%s ,pageSize=%2s, listSize=%s\n",
key,test1[0],test1[1],2,arraylist.size());
System.out.format("key=%s, total=%s, onPages=%s ,pageSize=%2s, listSize=%s\n" ,
key,test1[0],test2[1],6,arraylist.size());
System.out.format("key=%s, total=%s, onPages=%s ,pageSize=%2s, listSize=%s\n" ,
key,test1[0],test3[1],20,arraylist.size());
}
public static int[] getOccurencesPages(List<String> lst,int pageSize, String key)
{
//[0]-Occurrences,[1]-occur in n-pages
int result[] = {0,0};
//track for pages
//if word have more the 1 occurrence per page
//will not be added multiple time on page counter for same page)
boolean flag=false;
//element index is needed at onPage
//so foreach is not fit so well without adding additional var
//to track element index
for(int i=0;i<lst.size();i++)
{
if(i%pageSize==0) flag=false;
if(lst.get(i).equals(key))
{
//count total occ
result[0]++;
//count occ per pages
if(!flag)
{
result[1]++;
flag=true;
}
}
}
return result;
}
}
Output
//last 2 CSS are on different pages so each count
key=CSS, total=3, onPages=3 ,pageSize= 2, listSize=11
//last 2 CSS are on same page so both count as 1
key=CSS, total=3, onPages=2 ,pageSize= 6, listSize=11
//all CSS are on the same page so all(3) count as 1
key=CSS, total=3, onPages=1 ,pageSize=20, listSize=11
//pageSize can be even greater then listSize
//meaning : there are still places for additional elements on page_1
//adding "CSS" till fill entire page_1, will not modify onPages_result
I have an assignment which I have to program that creates a standings table from match list input. I used the word "infinite" because input size is unknown so I have to create a program that works until there's no matches left. I created a football class for this(input contains 2 other sports and their own matches and teams indicating the sports type with first letter of the sport with "F, B, V" for example, they're only different in scoring part, so I though if I can make football work, I can make anything else work) that contains everything required in standings table, and methods for match results which looks like this:
public class Football {
private int scoredGoals;
private int receivedGoals;
private String teamName;
private int Score;
private int wins;
private int losses;
private int MatchCount;
private int draws;
public void teamValues(String teamName, int Sgoals, int Rgoals) {
this.teamName = teamName;
this.scoredGoals = Sgoals;
this.receivedGoals = Rgoals;
}
public void matched() {
MatchCount++;
}
public void winner() {
wins++;
}
public void draw() {
draws++;
}
public void loser() {
losses++;
}
public void winScore() {
Score += 3;
}
public void drawScore() {
Score += 1;
}
public String showTeams() {
return (teamName + " " + MatchCount + " " + wins + " " + draws + " " + losses + " " + scoredGoals+":"+receivedGoals + " " + Score);
}
}
And in main class I'm calling methods in if blocks to calculate wins, score, matches count etc. And main looks like this:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.HashSet;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
File file = new File("input.txt");
Scanner scan = new Scanner(file);
String fileString = "";
Football teams[] = new Football[2];
HashSet<String> teamsArray = new HashSet<String>();
while(scan.hasNextLine()) {
fileString = scan.nextLine();
String[] match = fileString.split("\\t|:");
if(match[0].equals("F")) {
int team1score = Integer.valueOf(match[3].trim());
int team2score = Integer.valueOf(match[4].trim());
teams[0] = new Football();
teams[0].teamValues(match[1], team1score, team2score);
teams[1] = new Football();
teams[1].teamValues(match[2], team2score, team1score);
teams[0].matched();
teams[1].matched();
if(team1score>team2score) {
teams[0].winner();
teams[1].loser();
teams[0].winScore();
}
if(team1score==team2score) {
teams[0].draw();
teams[1].draw();
teams[0].drawScore();
teams[1].drawScore();
}
if(team1score<team2score) {
teams[1].winner();
teams[0].loser();
teams[1].winScore();
}
String team0 = teams[0].showTeams();
String team1 = teams[1].showTeams();
teamsArray.add(team0);
teamsArray.add(team1);
}
}
scan.close();
}
}
Since the input is static, I used arrays to work around. My problem with my code is I cant find a way to store my teams without duplicates and the variables that comes within and update whenever that team has another match.
I tried;
Storing them in a 2D string array but since the amount of teams is unknown I think it won't be a healthy way to approach to the problem.
Storing them in a String[] array list, which ended up storing the adresses instead of the values of the teams.
Set which I still use to check if at least the methods are working as intended.
It feels like I hit the wall with this program and I need to start over, so any kind of advice is appreciated.
Here's an example of input and output:
Input:
Home Team Guest Team H : G
F Manchester U. Chelsea 2 : 2
F Liverpool Manchester City 3 : 2
F Leicester City Everton 1 : 3
V Team A Team B 3 : 0
F Tottenham Liverpool 3 : 1
B Team B Team A 90 : 96
F West Ham Manchester U. 2 : 1
F Arsenal Manchester City 0 : 2
F Chelsea Arsenal 3 : 3
Output:
Name Matches Wins Draw Lose Scored:Received Score
1. Manchester U. 10 6 2 2 27:22 20
2. Arsenal 10 6 2 2 25:24 20
3. Chelsea 10 5 3 2 28:20 18
4. Liverpool 10 4 4 2 22:19 16
5. Tottenham 10 4 4 2 22:21 16
There are teams with same scores, because calculating average of scored and received goals is another way to sort the teams.
First some changes to the Football class:
Override equals to be able to search the list
Override compareTo for sorting
Override toString instead of showTeams
Create a constructor
Combine most functions into teamValues
import java.util.Formatter;
public class Football implements Comparable<Football> {
private int scoredGoals;
private int receivedGoals;
private String teamName;
private int score;
private int wins;
private int losses;
private int draws;
private int matchCount;
public int compareTo(Football f) {
return score - f.score;
}
public boolean equals(Object o) {
if (o == null) {
return false;
}
else if (o instanceof Football) {
return teamName.equals(((Football)o).teamName);
}
else if (o instanceof String) {
return teamName.equals((String)o);
}
return false;
}
public Football(String teamName) {
this.teamName = teamName;
}
public void teamValues(int scoredGoals, int receivedGoals) {
this.scoredGoals += scoredGoals;
this.receivedGoals += receivedGoals;
matchCount++;
if (scoredGoals < receivedGoals) {
losses++;
}
else if (scoredGoals > receivedGoals) {
wins++;
score += 3;
}
else {
draws++;
score += 1;
}
}
public String toString() {
return new Formatter().format("%-20s %3d %3d %3d %3d %3d:%-3d %d",
teamName, matchCount, wins, draws, losses, scoredGoals, receivedGoals, score)
.toString();
}
}
For the main program, you don't want to create a new team every time - only when a team is first encountered in the file. Put all the teams in a List. When parsing a new game, first try to find the team in the list. If it is not in there, add it.
List<Football> teamsArray = new ArrayList<>();
while (scan.hasNextLine()) {
fileString = scan.nextLine();
String[]match = fileString.split("\\t|:");
if (match.length == 5 && match[0].equals("F")) {
int team1score = Integer.valueOf(match[3].trim());
int team2score = Integer.valueOf(match[4].trim());
// Create a temp team to search the List
Football team1 = new Football(match[1]);
// Search the list
if (!teamsArray.contains(team1)) {
// Not in the list already. Add it
teamsArray.add(team1);
}
else {
// Already in the List. Use that one.
team1 = teamsArray.get(teamsArray.indexOf(team1));
}
// Repeat for team 2
Football team2 = new Football(match[2]);
if (!teamsArray.contains(team2)) {
teamsArray.add(team2);
}
else {
team2 = teamsArray.get(teamsArray.indexOf(team2));
}
team1.teamValues(team1score, team2score);
team2.teamValues(team2score, team1score);
}
}
System.out.println("Name M W D L S:R S");
// Sort and print
teamsArray
.stream()
.sorted(Comparator.reverseOrder())
.forEach(t -> System.out.println(t));
I am learning java and I hit into a snag as I could not figure out my loop or array.
I have an array which contains class objects containing a string and integer parameters, in my code, it will be name and dollars.
I am trying to print out the array in which, if there is a same name, it is to print once and with the sum of the dollars (from the same name).
In my Dollars.java
public class Dollars
{
private String name;
private int dollars;
public dollars (String name, int dollars)
{
this.name = name;
this.dollars = dollars;
}
public String getName()
{
return name;
}
public int getChange()
{
return dollars;
}
}
In my main file/ TestDollars.java
public class TestDollars
{
public static void displayArray(Dollars[] dol)
{
int sum = 0;
for (int n=0; n<dol.length; n++)
{
for (int m=n+1; m<dol.length; m++)
{
if (dol[n].getName().equals(dol[m].getName()))
{
// System.out.printf("%s -- %d\n", dol[n].getName(), dol[n].getChange());
sum = dol[n].getChange() + dol[m].getChange();
System.out.printf("%s -- %d\n", dol[m].getName(), sum);
break;
}
}
System.out.printf("%s -- %d\n", dol[n].getName(), dol[n].getChange());
}
}
public static void main(String[] args)
{
// Test with 5 records
Dollars[] dollarsArr = new Dollars[5];
dollarsArr[0] = new Dollars("john", 10);
dollarsArr[1] = new Dollars("peter", 12);
dollarsArr[2] = new Dollars("sam", 5);
dollarsArr[3] = new Dollars("alvin", 16);
dollarsArr[4] = new Dollars("peter", 30);
displayArray(dollarsArr);
}
}
Irregardless where I place my print statement in the displayArray, the record 'peter' will gets printed twice.
Expected output:
john -- 10
peter -- 42
sam -- 5
alvin -- 16
Current output:
john -- 10
peter -- 42
peter -- 12
sam -- 5
alvin -- 16
peter -- 30
You want to group your list by name, please use JAVA 8+ API Stream and the collector group by
public static void displayArray(Dollars[] dol)
{
Stream.of(dol)
// Group by name
.collect(Collectors.groupingBy(Dollars::getName))
.entrySet().stream()
// Collect a map name and calculate the sum
.collect(
Collectors.toMap(x -> {
int total= x.getValue().stream().mapToInt(Dollars::getChange).sum();
return new Dollars(x.getKey(),total);
}, Map.Entry::getValue))
// Print
.forEach((dollarsTotal, vals) -> {
System.out.println(dollarsTotal.getName()+ " -- "+ dollarsTotal.getChange());
// Bonus : Display transactions :
for(Dollars transaction : vals)
{
System.out.println(" \t "+transaction.getName() + " add -- " + transaction.getChange());
}
});
}
If you want only the values you can collect the keyset
Set<Dollars> groupedByName = Stream.of(dol)
// Group by name
.collect(Collectors.groupingBy(Dollars::getName))
.entrySet().stream()
// Collect a map name and calculate the sum
.collect(
Collectors.toMap(x -> {
int total= x.getValue().stream().mapToInt(Dollars::getChange).sum();
return new Dollars(x.getKey(),total);
}, Map.Entry::getValue)).keySet();
The other answer guides you on fixing your code (Buy they require more work to avoid double counting).
You can reduce the time complexity from O(n2) to O(n) (and make it simpler) by having a data structure (like a map) to aggregate the result.
Let us create a Map<String, Integer> to map a name to the total dollars for that name.
Map<String, Integer> result = new HashMap<>();
for (int i = 0; i < dol.length; i++) {
if (!result.containsKey(dol[i].getName())) { //first time you encounter a name
result.put(dol[i].getName(), dol[i].getChange());
} else {
//add the current change to the already existing sum
int sumSoFar= result.get(dol[i].getName());
result.put(dol[i].getName(), sumSoFar + dol[i].getChange());
}
}
System.out.println(result);
Result is,
{peter=42, alvin=16, john=10, sam=5}
You can simplify the above code using Map's merge method as:
for (Dollars dollars : dol) {
result.merge(dollars.getName(), dollars.getChange(), Integer::sum);
}
The third argument is a BiFunction which sums up the old value and new value (the sum accumulated so far and the current change value). When written as a lambda expression, Integer::sum can be written as (sumSoFar, currentChange) -> sumSoFar + currentChange.
A stream evangelist way would be to use Collectors.groupingBy and Collectors.summingInt.
Arrays.stream(dol)
.collect(Collectors.groupingBy(Dollars::getName, Collectors.summingInt(Dollars::getChange)));
While navigating the array, assign the change of each non-null array-element to a variable e.g. sum and then add the change of the succeeding duplicate elements to it. Make sure to assign null to the indices where duplicate elements are found so that they can not be counted again. It also means that you will have to perform a null check before performing any operation on the array elements. Print the value of sum once you have checked the complete array for duplicate elements.
public static void displayArray(Dollars[] dol) {
for (int n = 0; n < dol.length; n++) {
if (dol[n] != null) {
int sum = dol[n].getChange();
for (int m = n + 1; m < dol.length; m++) {
if (dol[m] != null && dol[n].getName().equals(dol[m].getName())) {
sum += dol[m].getChange();
dol[m] = null;
}
}
System.out.printf("%s -- %d\n", dol[n].getName(), sum);
}
}
}
Output:
john -- 10
peter -- 42
sam -- 5
alvin -- 16
Note: If you want to keep the original array intact, pass the clone of the array to the method, displayArray instead of passing the array itself as shown below:
displayArray(dollarsArr.clone());
You are given a list of file names and their lengths in bytes.
Example:
File1: 200 File2: 500 File3: 800
You are given a number N. We want to launch N threads to read all the files parallelly such that each thread approximately reads an equal amount of bytes
You should return N lists. Each list describes the work of one thread: Example, when N=2, there are two threads. In the above example, there is a total of 1500 bytes (200 + 500 + 800). A fairway to divide is for each thread to read 750 bytes. So you will return:
Two lists
List 1: File1: 0 - 199 File2: 0 - 499 File3: 0-49 ---------------- Total 750 bytes
List 2: File3: 50-799 -------------------- Total 750 bytes
Implement the following method
List<List<FileRange>> getSplits(List<File> files, int N)
Class File {
String filename; long length }
Class FileRange {
String filename Long startOffset Long endOffset }
I tried with this one but it's not working any help would be highly appreciated.
List<List<FileRange>> getSplits(List<File> files, int n) {
List<List<FileRange>> al=new ArrayList<>();
long s=files.size();
long sum=0;
for(int i=0;i<s;i++){
long l=files.get(i).length;
sum+=(long)l;
}
long div=(long)sum/n; // no of bytes per thread
long mod=(long)sum%n;
long[] lo=new long[(long)n];
for(long i=0;i<n;i++)
lo[i]=div;
if(mod!=0){
long i=0;
while(mod>0){
lo[i]+=1;
mod--;
i++;
}
}
long inOffset=0;
for(long j=0;j<n;j++){
long val=lo[i];
for(long i=0;i<(long)files.size();i++){
String ss=files.get(i).filename;
long ll=files.get(i).length;
if(ll<val){
inOffset=0;
val-=ll;
}
else{
inOffset=ll-val;
ll=val;
}
al.add(new ArrayList<>(new File(ss,inOffset,ll-1)));
}
}
}
I'm getting problem in startOffset and endOffset with it's corresponding file. I tried it but I was not able to extract from List and add in the form of required return type List>.
The essence of the problem is to simultaneously walk through two lists:
the input list, which is a list of files
the output list, which is a list of threads (where each thread has a list of ranges)
I find that the easiest approach to such problems is an infinite loop that looks something like this:
while (1)
{
move some information from the input to the output
decide whether to advance to the next input item
decide whether to advance to the next output item
if we've reached (the end of the input _OR_ the end of the output)
break
if we advanced to the next input item
prepare the next input item for processing
if we advanced to the next output item
prepare the next output item for processing
}
To keep track of the input, we need the following information
fileIndex the index into the list of files
fileOffset the offset of the first unassigned byte in the file, initially 0
fileRemain the number of bytes in the file that are unassigned, initially the file size
To keep track of the output, we need
threadIndex the index of the thread we're currently working on (which is the first index into the List<List<FileRange>> that the algorithm produces)
threadNeeds the number of bytes that the thread still needs, initially base or base+1
Side note: I'm using base as the minimum number bytes assigned to each thread (sum/n), and extra as the number of threads that get an extra byte (sum%n).
So now we get to the heart of the algorithm: what information to move from input to output:
if fileRemain is less than threadNeeds then the rest of the file (which may be the entire file) gets assigned to the current thread, and we move to the next file
if fileRemain is greater than threadNeeds then a portion of the file is assigned to the current thread, and we move to the next thread
if fileRemain is equal to threadNeeds then the rest of the file is assigned to the thread, and we move to the next file, and the next thread
Those three cases are easily handled by comparing fileRemain and threadNeeds, and choosing a byteCount that is the minimum of the two.
With all that in mind, here's some pseudo-code to help get you started:
base = sum/n;
extra = sum%n;
// initialize the input control variables
fileIndex = 0
fileOffset = 0
fileRemain = length of file 0
// initialize the output control variables
threadIndex = 0
threadNeeds = base
if (threadIndex < extra)
threadNeeds++
while (1)
{
// decide how many bytes can be assigned, and generate some output
byteCount = min(fileRemain, threadNeeds)
add (file.name, fileOffset, fileOffset+byteCount-1) to the list of ranges
// decide whether to advance to the next input and output items
threadNeeds -= byteCount
fileRemain -= byteCount
if (threadNeeds == 0)
threadIndex++
if (fileRemain == 0)
fileIndex++
// are we done yet?
if (threadIndex == n || fileIndex == files.size())
break
// if we've moved to the next input item, reinitialize the input control variables
if (fileRemain == 0)
{
fileOffset = 0
fileRemain = length of file
}
// if we've moved to the next output item, reinitialize the output control variables
if (threadNeeds == 0)
{
threadNeeds = base
if (threadIndex < extra)
threadNeeds++
}
}
Debugging tip: Reaching the end of the input, and the end of the output, should happen simultaneously. In other words, you should run out of files at exactly the same time as you run out of threads. So during development, I would check both conditions, and verify that they do, in fact, change at the same time.
Here's the code solution for your problem (in Java) :
The custom class 'File' and 'FileRange' are as follows :
public class File{
String filename;
long length;
public File(String filename, long length) {
this.filename = filename;
this.length = length;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
}
public class FileRange {
String filename;
Long startOffset;
Long endOffset;
public FileRange(String filename, Long startOffset, Long endOffset) {
this.filename = filename;
this.startOffset = startOffset;
this.endOffset = endOffset;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public Long getStartOffset() {
return startOffset;
}
public void setStartOffset(Long startOffset) {
this.startOffset = startOffset;
}
public Long getEndOffset() {
return endOffset;
}
public void setEndOffset(Long endOffset) {
this.endOffset = endOffset;
}
}
The main class will be as follows :
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicInteger;
public class MainClass {
private static List<List<FileRange>> getSplits(List<File> files, int N) {
List<List<FileRange>> results = new ArrayList<>();
long sum = files.stream().mapToLong(File::getLength).sum(); // Total bytes in all the files
long div = sum/N;
long mod = sum%N;
// Storing how many bytes each thread gets to process
long thread_bytes[] = new long[N];
// At least 'div' number of bytes will be processed by each thread
for(int i=0;i<N;i++)
thread_bytes[i] = div;
// Left over bytes to be processed by each thread
for(int i=0;i<mod;i++)
thread_bytes[i] += 1;
int count = 0;
int len = files.size();
long processed_bytes[] = new long[len];
long temp = 0L;
int file_to_be_processed = 0;
while(count < N && sum > 0) {
temp = thread_bytes[count];
sum -= temp;
List<FileRange> internal = new ArrayList<>();
while (temp > 0) {
// Start from the file to be processed - Will be 0 in the first iteration
// Will be updated in the subsequent iterations
for(int j=file_to_be_processed;j<len && temp>0;j++){
File f = files.get(j);
if(f.getLength() - processed_bytes[j] <= temp){
internal.add(new FileRange(f.getFilename(), processed_bytes[j], f.getLength()- 1));
processed_bytes[j] = f.getLength() - processed_bytes[j];
temp -= processed_bytes[j];
file_to_be_processed++;
}
else{
internal.add(new FileRange(f.getFilename(), processed_bytes[j], processed_bytes[j] + temp - 1));
// In this case, we won't update the number for file to be processed
processed_bytes[j] += temp;
temp -= processed_bytes[j];
}
}
results.add(internal);
count++;
}
}
return results;
}
public static void main(String args[]){
Scanner scn = new Scanner(System.in);
int N = scn.nextInt();
// Inserting demo records in list
File f1 = new File("File 1",200);
File f2 = new File("File 2",500);
File f3 = new File("File 3",800);
List<File> files = new ArrayList<>();
files.add(f1);
files.add(f2);
files.add(f3);
List<List<FileRange>> results = getSplits(files, N);
final AtomicInteger result_count = new AtomicInteger();
// Displaying the results
results.forEach(result -> {
System.out.println("List "+result_count.incrementAndGet() + " : ");
result.forEach(res -> {
System.out.print(res.getFilename() + " : ");
System.out.print(res.getStartOffset() + " - ");
System.out.print(res.getEndOffset() + "\n");
});
System.out.println("---------------");
});
}
}
If some part is still unclear, consider a case and dry run the program.
Say 999 bytes have to be processed by 100 threads
So the 100 threads get 9 bytes each and out of the remaining 99 bytes, each thread except the 100th gets 1 byte. By doing this, we'll make sure no 2 threads differ by at most 1 byte. Proceed with this idea and follow up with the code.