SklogW
SklogW

Reputation: 849

Get line-numbers in which a certain word/text occurs

What I have: I have a file which is read line by line. These lines are not counted within the file.

What I want to do: I want to count each line within ONE stream and return only the numbers in which a certain text occurs.

What I have so far:

public static Integer findLineNums(String word)
        throws IOException {

    final Map<String, Integer> map = new HashMap<>();
    final List<String> lines = Files.lines(Paths.get(PATH)).collect(Collectors.toList());                 
    IntStream.rangeClosed(0, lines.size()-1).forEach(f -> map.put(lines.get(f), f+1));

    return map.get(word);
}

QUESTION: How can I do this using only a SINGLE stream ?

EDITED QUESTION: I'd like to do everything within the Stream, this includes also the accumulation into a list.

Best case scenario would be something like:

Files.lines(Paths.get(PATH)).superAwesomeStreamFuncs().collect(Collectors.toList());

EDIT: In my case I would only return a single Integer, but I would like to get something like a Integer List.

Upvotes: 4

Views: 1226

Answers (3)

Vova Programmer
Vova Programmer

Reputation: 345

This method returns line mapped by its number in file.

public static Map<String, Integer> findLineNums(Path path, String word) throws IOException {

        final Map<String, Integer> map = new HashMap<>();
        int lineNumber = 0;
        Pattern pattern = Pattern.compile("\\b" + word + "\\b");

        try (BufferedReader reader = Files.newBufferedReader(path)) {
            String line = null;
            while ((line = reader.readLine()) != null) {
                lineNumber++;
                if (pattern.matcher(line).find()) {
                    map.put(line, lineNumber);
                }
            }
        }
        for (String line : map.keySet()) {
            Integer lineIndex = map.get(line);
            System.out.printf("%d  %s\n", lineIndex, line);
        }
        return map;
    }

BufferedReader reads file line-by-line as does Files.lines stream.

Upvotes: 0

Bohemian
Bohemian

Reputation: 425063

This works:

int[] i = new int[]{0}; // trick to make it final
List<Integer> hits = <your stream>
  .map(s -> s.contains(word) ? ++i[0] : - ++i[0])
  .filter(n -> n > 0)
  .collect(Collectors.toList());

The main "trick" here is the use of an array, whose reference doesn't change (ie it's "effectively final", but which lets us mutate its (only) element as the counter, which is incremented in-line regardless. A quick filter throws out non-matches.


Some test code:

String word = "foo";
int[] i = new int[]{0};
List<Integer> hits = Stream.of("foo", "bar", "foobar")
.map(s -> s.contains(word) ? ++i[0] : - ++i[0])
.filter(n -> n > 0)
.collect(Collectors.toList());
System.out.println(hits);

Output:

[1, 3]

Upvotes: 3

SubOptimal
SubOptimal

Reputation: 22963

Following snippet would create a List<Integer> with the lines which contains the word

String word = "foo";
List<Integer> matchedLines = new ArrayList<>();
final List<String> lines = Files.readAllLines(Paths.get("word_list.txt"));
IntStream.rangeClosed(0, lines.size() - 1).forEach(f -> {
    if (lines.get(f).contains(word)) {
        matchedLines.add(++f);
    }
});
System.out.println("matchedLines = " + matchedLines);

assuming the file word_list.txt as

foo
bar
baz
foobar
barfoo

the output is

matchedLines = [1, 4, 5]

edit To solve it with a single stream, create a custom Consumer

public class MatchingLines {

    static class MatchConsumer implements Consumer<String> {
        private int count = 0;
        private final List<Integer> matchedLines = new ArrayList<>();
        private final String word;

        MatchConsumer(String word) {
            this.word = word;
        }

        @Override
        public void accept(String line) {
            count++;
            if (line.contains(this.word)) {
                matchedLines.add(count);
            }
        }

        public List<Integer> getResult() {
            return matchedLines;
        }
    }

    public static void main(String[] args) throws IOException {
        MatchConsumer matchConsumer = new MatchConsumer("foo");
        Files.lines(Paths.get("word_list.txt")).forEach(matchConsumer);
        System.out.println("matchedLines = " + matchConsumer.getResult());
    }
}

Upvotes: 3

Related Questions