20
\$\begingroup\$

In Windows, when you perform double-click in a text, the word around your cursor in the text will be selected.

(This feature has more complicated properties, but they will not be required to be implemented for this challenge.)

For example, let | be your cursor in abc de|f ghi.

Then, when you double click, the substring def will be selected.

Input/Output

You will be given two inputs: a string and an integer.

Your task is to return the word-substring of the string around the index specified by the integer.

Your cursor can be right before or right after the character in the string at the index specified.

If you use right before, please specify in your answer.

Specifications (Specs)

The index is guaranteed to be inside a word, so no edge cases like abc |def ghi or abc def| ghi.

The string will only contain printable ASCII characters (from U+0020 to U+007E).

The word "word" is defined by the regex (?<!\w)\w+(?!\w), where \w is defined by [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_], or "alphanumeric characters in ASCII including underscore".

The index can be 1-indexed or 0-indexed.

If you use 0-indexed, please specify it in your answer.

Testcases

The testcases are 1-indexed, and the cursor is right after the index specified.

The cursor position is for demonstration purpose only, which will not be required to be outputted.

string    index     output    cursor position
abc def   2         abc       ab|c def
abc def   5         def       abc d|ef
abc abc   2         abc       ab|c abc
ab cd ef  4         cd        ab c|d ef
ab   cd   6         cd        ab   c|d
ab!cd     1         ab        a|b!cd
\$\endgroup\$
  • 2
    \$\begingroup\$ Can the string contain newlines? \$\endgroup\$ – orlp Jul 18 '16 at 15:04
  • \$\begingroup\$ @orlp The challenge was edited to restrict the input to printable ASCII so the input will not contain newlines. \$\endgroup\$ – FryAmTheEggman Jul 18 '16 at 15:15
  • \$\begingroup\$ Your testcases do not contain any other delimiters than spaces. What about a word like we're? \$\endgroup\$ – orlp Jul 18 '16 at 15:27
  • 2
    \$\begingroup\$ What should "ab...cd", 3 return? \$\endgroup\$ – Titus Jul 18 '16 at 16:22
  • 5
    \$\begingroup\$ @Titus "The index is guaranteed to be inside a word" \$\endgroup\$ – Martin Ender Jul 18 '16 at 16:26

24 Answers 24

10
\$\begingroup\$

V, 10, 9 7 bytes

À|diwVp

Try it online!

This answer uses 1-based indexing.

This could be shorter if we do exactly what the title says: "Select the word around the given index in a string". We could do

À|viw

Which literally selects the word, but unfortunately doesn't change the output at all. So we need a little workaround to make it work by cutting it into a register, deleting the rest of the text, then pasting the register back in.

Explanation:

À|          " Jump the position of argument 1
  diw       " (d)elete (i)nside this (w)ord.
     V      " Select this line
      p     " And replace it with the word we just deleted
\$\endgroup\$
5
\$\begingroup\$

C, 104 bytes

p[99];i,d;main(l){for(scanf("%d",&i);scanf("%[^a-zA-Z0-9_]%[a-zA-Z0-9_]%n",&d,&p,&l),i>l;i-=l);puts(p);}

Expects the input on stdin to be the 0-based index followed by one space or newline, followed by the string. Maximal length for a word is 99 characters. E.g.:

2 abc def
\$\endgroup\$
  • \$\begingroup\$ It's really cool to see C and perl tied on a string based challenge. :D \$\endgroup\$ – DJMcMayhem Jul 18 '16 at 15:26
  • \$\begingroup\$ Can the input string have longer than 100 characters? \$\endgroup\$ – Leaky Nun Jul 18 '16 at 15:27
  • \$\begingroup\$ @LeakyNun Yes, but one word can not be longer than 100 characters. \$\endgroup\$ – orlp Jul 18 '16 at 15:27
  • \$\begingroup\$ Do you feel like putting that requirement inside your answer? \$\endgroup\$ – Leaky Nun Jul 18 '16 at 15:28
  • \$\begingroup\$ @DrGreenEggsandIronMan Too bad I had to fix my answer because it was delimited on whitespace :( \$\endgroup\$ – orlp Jul 18 '16 at 15:36
4
\$\begingroup\$

C (gcc), 94 bytes

f(n,p)char*p;{for(p+=n-1;isalnum(*p)|*p==95&&n--;--p);for(;isalnum(*++p)|*p==95;putchar(*p));}

Zero-indexed, defines a function taking the index, then the string.

\$\endgroup\$
  • \$\begingroup\$ I think isalnum(*++p)|*p==95 is undefined behavior. \$\endgroup\$ – owacoder Jul 18 '16 at 17:06
  • \$\begingroup\$ @owacoder It is, but what matters is that gcc spits out an executable that works. *++p^95?isalnum(*p):1 is one byte longer, but works on every compiler. \$\endgroup\$ – orlp Jul 18 '16 at 17:07
  • \$\begingroup\$ I assume the leading space is a typo? Also, here's a lazy IDEone link. \$\endgroup\$ – FryAmTheEggman Jul 18 '16 at 17:11
  • \$\begingroup\$ isalnum(*++p)||*p==95 also works, for an added one byte. \$\endgroup\$ – owacoder Jul 18 '16 at 17:12
  • \$\begingroup\$ @FryAmTheEggman Yes it is, fixed now. \$\endgroup\$ – orlp Jul 18 '16 at 17:13
3
\$\begingroup\$

Retina, 22

(1)+¶(?<-1>.)*\b|\W.+

Try it online! or verify all test cases. The regular program takes the cursor position in unary followed by a newline and then the string. The test suite has additional code to run in per line mode, and uses a \ as a delimiter, and it uses decimal, for convenience.

Uses balancing groups to find the cursor position, then backtracks up to a word boundary. Deletes the text up to the word, and then after the word.

\$\endgroup\$
2
\$\begingroup\$

C, 115 bytes

Function f() requires the string and index (1-indexed) as parameters and prints the result to stdout. Cursor should be after the specified character.

f(char*p,int n){char*s=p+n;for(;s>=p&&isalnum(*s)+(*s==95);--s);for(p=s+1;*p&&isalnum(*p)+(*p==95);putchar(*p++));}
\$\endgroup\$
2
\$\begingroup\$

JavaScript (ES6), 57 bytes

f=(s,n)=>s.slice(0,n).match(/\w*$/)+s.slice(n).match(/\w*/)

Simply slices the string at the cursor point (which is before the 0-indexed character, which works out the same as after the 1-indexed character), then extracts and concatenates the adjacent word fragments. Even returns a sensible result when the cursor is at the start, end, or nowhere near a word.

\$\endgroup\$
  • \$\begingroup\$ Do you need the * in the last regex? \$\endgroup\$ – Charlie Wynn Jul 18 '16 at 17:31
  • \$\begingroup\$ @CharlieWynn Yes, otherwise the second testcase would only return de. \$\endgroup\$ – Neil Jul 18 '16 at 17:37
  • \$\begingroup\$ whoops, got unlucky with the tests I ran \$\endgroup\$ – Charlie Wynn Jul 18 '16 at 17:39
2
\$\begingroup\$

Java 8, 86 78 bytes

(s,p)->{for(String t:s.split("\\W"))if((p-=t.length()+1)<0)return t;return"";}

Ungolfed with test cases:

class Indexer {
    public static String f(String s, int p) {
        for(String t : s.split("\\W"))
            if((p -= t.length()+1) < 0)
                return t;
        return "";
    }

    public static void main(String[] args) {
        System.out.println(f("abc def",2));
        System.out.println(f("abc def",5));
        System.out.println(f("abc abc",2));
        System.out.println(f("ab cd ef",4));
        System.out.println(f("ab   cd",6));
        System.out.println(f("ab!cd",1));
    }
}

Splits the string by non-alphanumeric characters, then keeps subtracting the length of each substring, plus 1, from the specified position, until it becomes negative. Since any repeating non-alphanumerics get represented as empty string, the subtraction logic is significantly easier.

This code isn't extensively tested, so I'd like to see if someone can break this. Also, considering that this is Java code, how is this not the longest answer here? :P

\$\endgroup\$
  • \$\begingroup\$ I know it's been almost three years, but (s,p)-> can be s->p-> by using a currying lambda expression (i.e. java.util.function.Function<String, java.util.function.Function<String, String>> f). In addition, String could be var now if switched to Java 10, although that wasn't available at the time of course. Regardless, nice answer. I see I've already updated it somewhere in the past. :) \$\endgroup\$ – Kevin Cruijssen Mar 15 at 10:40
2
\$\begingroup\$

Pyth, 16 bytes

+e=:.<+QbE"\W"3h

       Q            first input (string)
      + b           plus newline
    .<   E          rotate left by second input (number)
   :      "\W"3     split on regex \W, non-word characters
  =                 assign to Q
 e                  last element
+              hQ   plus first element

Try it online

\$\endgroup\$
2
\$\begingroup\$

Ruby, 41 31 bytes

Try it online!

-10 bytes from @MartinEnder

->s,i{s[/\w*(?<=^.{#{i}})\w*/]}
\$\endgroup\$
2
\$\begingroup\$

Pyke, 19 bytes

#Q;cjmli<i+s)lttjR@

Try it here!

Uses Q; as a no-op to make sure the first input is placed correctly

#          )   -  first where
   c           -       input.split()
    ml         -      map(len, ^)
      i<       -     ^[:i]
        i+     -    ^+[i]
          s    -   sum(^)
            lt - len(^)-2
\$\endgroup\$
  • \$\begingroup\$ I'm having a 504 error when I click your link. \$\endgroup\$ – Leaky Nun Jul 18 '16 at 17:32
  • \$\begingroup\$ @LeakyNun Yeah, I killed it by accident whilst writing which is why I had the localhost link, it'll come back \$\endgroup\$ – Blue Jul 18 '16 at 17:36
  • \$\begingroup\$ Try it here! \$\endgroup\$ – Leaky Nun Jul 19 '16 at 7:56
  • 1
    \$\begingroup\$ Your program seems to be outputting N where the Nth word is the one selected, but we need the full word \$\endgroup\$ – Value Ink Jul 19 '16 at 9:33
2
\$\begingroup\$

Python 2, 70 66 bytes

import re
f=lambda x,y,r=re.split:r('\W',x[:y])[-1]+r('\W',x[y:])[0]

Splits the string by non-word separators, once on the original string up to the cursor index, then on the string beginning at the cursor index. Returns the last element of the left split plus the first element of the right split. Thanks to Leaky Nun for saving 4 bytes!

\$\endgroup\$
1
\$\begingroup\$

Clojure, 92 bytes

(fn[x k](let[[u i](map #(re-seq #"\w+"(apply str %))(split-at k x))](str(last u)(nth i 0))))

First, splits input string at position k into two strings. Then for these strings find occurrences of "\w+" and return them as list. Then concatenate the last element of first list and the first element of second list.

See it online: https://ideone.com/Dk2FIs

\$\endgroup\$
1
\$\begingroup\$

JavaScript (ES6), 52 bytes

(s,n)=>RegExp(`^.{0,${n}}(\\W+|^)(\\w+)`).exec(s)[2]

const F = (s,n) => RegExp(`^.{0,${n}}(\\W+|^)(\\w+)`).exec(s)[2]

class Test extends React.Component {
    constructor(props) {
        super(props);
        const input = props.input || '';
        const index = props.index || 0;
        this.state = {
            input,
            index,
            valid: /\w/.test(input),
        };
    }
    onInput = () => {
        const input = this.refs.input.value;
        const index = Math.min(+this.refs.index.value, input.length);
        this.setState({
            input,
            index,
            valid: /\w/.test(input),
        });
    }
    render() {
        const {input, index, valid} = this.state;
        return (
            <tr>
                <td>{ this.props.children }</td>
                <td>
                    <input ref="input" type="text" onInput={this.onInput} value={input} />
                    <input ref="index" type="number" onInput={this.onInput} min="1" max={input.length} value={index} />
                </td> 
                {valid && [
                    <td>{F(input, index)}</td>,
                    <td><pre>{input.slice(0, index)}|{input.slice(index)}</pre></td>,
                ]}
            </tr>
        );
    }
}

class TestList extends React.Component {
    constructor(props) {
        super(props);
        this.tid = 0;
        this.state = {
            tests: (props.tests || []).map(test => Object.assign({
                key: this.tid++
            }, test)),
        };
    }
    addTest = () => {
        this.setState({
            tests: [...this.state.tests, { key: this.tid++ }],
        });
    }
    removeTest = key => {
        this.setState({
            tests: this.state.tests.filter(test => test.key !== key),
        });
    }
    
    render() {
        return (
            <div>
                <table>
                    <thead>
                        <th/>
                        <th>Test</th>
                        <th>Output</th>
                        <th>Diagram</th>
                    </thead>
                    <tbody>
                        {
                            this.state.tests.map(test => (
                                <Test key={test.key} input={test.input} index={test.index}>
                                    <button onClick={() => this.removeTest(test.key)} style={{
                                        verticalAlign: 'middle',
                                    }}>-</button>
                                </Test>
                            ))
                        }
                    </tbody>
                    <tfoot>
                        <td/>
                        <td>
                            <button onClick={this.addTest} style={{
                                width: '100%',
                            }}>Add test case</button>
                        </td>
                    </tfoot>
                </table>
            </div>
        );
    }
}

ReactDOM.render(<TestList tests={[
    { input: 'abc def', index: 2 },
    { input: 'abc def', index: 5 },
    { input: 'abc abc', index: 2 },
    { input: 'ab cd ef', index: 4 },
    { input: 'ab   cd', index: 6 },
    { input: 'ab!cd', index: 1 },
]} />, document.body);
input[type="number"] {
  width: 3em;
}
table {
  border-spacing: 0.5em 0;
  border-collapse: separate;
  margin: 0 -0.5em ;
}
td, input {
    font-family: monospace;
}
th {
  text-align: left;
}
tbody {
  padding: 1em 0;
}
pre {
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

\$\endgroup\$
  • \$\begingroup\$ Why (\\W+|^) not (\\W|^) \$\endgroup\$ – l4m2 Dec 14 '17 at 14:42
1
\$\begingroup\$

Lua, 71 67 Bytes

Woohoo, Lua isn't the longest solution! Still one byte behind python, but don't know how to golf this down. Indexes are 1-based.

Thanks to @LeakyNun reminding me the existence of string.match, saved 4 bytes

g,h=...print(g:sub(1,h):match"[%a_]*$"..g:sub(h+1):match("[%a_]+"))

Old 71

Note: the explanations are still based on this one, because it also applies to the new one, but contains some extra informations on gmatch

g,h=...print(g:sub(1,h):gmatch"[%a_]*$"()..g:sub(h+1):gmatch"[%a_]*"())

Explanation

First, we unpack the arguments into g and h because they are shorter than arg[x]

g,h=...

Then, we construct our output, which is the concatanation of the part before the cursor and after it.

The first part of the string is

g:sub(1,h)

We want to find the word at the end of this one, so we use the function string.gmatch

:gmatch"[%a_]*$"

This pattern match 0..n times the character set of alphabet+underscore at the end of the string. gmatch returns an iterator on its list of match in the form of a function (using the principle of closure), so we execute it once to get the first part of our word

g:sub(1,h):gmatch"[%a_]*$"()

We get the second part of our word by the same way

g:sub(h+1):gmatch"[%a_]*"())

The only difference being we don't have to specify we want to match at the start of the string (using [^%a_]*), as it will be the match returned by the iterator when it's called the first time.

\$\endgroup\$
  • \$\begingroup\$ g:sub(h+1):match"^[%a_]*"? \$\endgroup\$ – Leaky Nun Jul 19 '16 at 7:50
  • \$\begingroup\$ @LeakyNun totally forgot the existence of match \o/ saves lot of bytes, thanks \$\endgroup\$ – Katenkyo Jul 19 '16 at 7:52
  • \$\begingroup\$ -1 for "indexes" \$\endgroup\$ – Leaky Nun Jul 19 '16 at 7:54
  • \$\begingroup\$ @LeakyNun both indices and indexes are correct ^^ \$\endgroup\$ – Katenkyo Jul 19 '16 at 7:57
  • \$\begingroup\$ I don't care, still -1 for "indexes". \$\endgroup\$ – Leaky Nun Jul 19 '16 at 8:01
1
\$\begingroup\$

Javascript (using external library) (168 bytes)

(n,w)=> _.From(w).Select((x,i)=>({i:i,x:x})).Split((i,v)=>v.x==" ").Where(a=>a.Min(i=>i.i)<=n-1&&a.Max(i=>i.i)>=n-2).First().Select(i=>i.x).Write("").match(/^\w*^\w*/)[0]

Link to lib:https://github.com/mvegh1/Enumerable/blob/master/linq.js

Explanation of code: Library accepts a string, which gets parsed into a char array. It gets mapped to an object storing the index and the char. The sequence is split into subsequences at every occurrence of " ". The subsequences are filtered by checking if the cursor index is contained within the min and max index of the subsequence. Then we take the first subsequence. Then we transform back to just a char array. Then we concatenate all characters with "" as the delimiter. Then we validate against word regex. Then we take the first match.

enter image description here

\$\endgroup\$
  • \$\begingroup\$ The word "word" is defined by the regex (?<!\w)\w+(?!\w), where \w is defined by [abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_], or "alphanumeric characters in ASCII including underscore". \$\endgroup\$ – Leaky Nun Jul 19 '16 at 16:06
  • \$\begingroup\$ When I run that regex against ab!cd on regex101.com I get this: No match groups were extracted. This means that your pattern matches but there were no (capturing (groups)) in it that matched anything in the subject string. Maybe I'm making a mistake somewhere... \$\endgroup\$ – applejacks01 Jul 19 '16 at 16:10
  • \$\begingroup\$ Why would I need to capture anything? \$\endgroup\$ – Leaky Nun Jul 19 '16 at 16:15
  • \$\begingroup\$ I know this isnt the place to learn, but I'm saying that when I run that regex against ab!cd I don't get anything. So why would 'ab' be the correct output? \$\endgroup\$ – applejacks01 Jul 19 '16 at 16:20
  • \$\begingroup\$ regex101.com/r/sC5oY0/1 \$\endgroup\$ – Leaky Nun Jul 19 '16 at 16:22
1
\$\begingroup\$

Perl 6, 34 bytes

->\b{&{first *.to>b,m:g/<<\w+>>/}}

Try it online!

Anonymous codeblock that takes input curried, like f(n)(string).

Explanation:

->\b{                            }   # Anonymous code block that takes a number
     &{                         }    # And returns another code block that
       first       ,m:g/<<\w+>>/     # Finds the first word in the input
             *.to>b                  # Where the start is after the number
\$\endgroup\$
1
\$\begingroup\$

Ruby, 30 bytes

->s,n{s[/.{#{n}}\w+/][/\w+$/]}

Try it online!

A different approach, only 1 byte shorter and 3 years later. Why not?

\$\endgroup\$
1
\$\begingroup\$

APL(NARS), 58 chars, 116 bytes

{m←⎕A,⎕a,⎕D,'_'⋄↑v⊂⍨m∊⍨v←⍵↓⍨¯1+⍵{⍵≤1:⍵⋄m∊⍨⍵⊃⍺:⍺∇⍵-1⋄⍵+1}⍺}

⍵{⍵≤1:⍵⋄m∊⍨⍵⊃⍺:⍺∇⍵-1⋄⍵+1}⍺ find where start the string...How to use and test:

  f←{m←⎕A,⎕a,⎕D,'_'⋄↑v⊂⍨m∊⍨v←⍵↓⍨¯1+⍵{⍵≤1:⍵⋄m∊⍨⍵⊃⍺:⍺∇⍵-1⋄⍵+1}⍺}
  2 f 'abc def'
abc
  5 f 'abc def'
def
  2 f 'abc abc'
abc
  4 f 'ab cd ef'
cd
  1 f 'ab!cd'
ab
  6 f 'ab   cd'
cd 
\$\endgroup\$
0
\$\begingroup\$

MATL, 16 15 bytes

'\w+'5B#XXi>)1)

Cursor is 1-indexed and after the character (as in the test cases).

Try it online! Or verify all test cases.

'\w+'    % Push string to be used as regex pattern
5B#XX    % Take input string implicitly. Apply regex. Push matches and ending indices
i>       % Take input number. Compare with obtained ending indices. Gives true for
         % ending indices that exceed the input number
)        % Use as logical index to select the corresponding matches
1)       % Select the first match. Implicitly display
\$\endgroup\$
0
\$\begingroup\$

PowerShell v3+, 103 101 bytes

param($a,$n)for(;$n[++$a]-match'\w'){}$i=$a--;for(;$n[--$a]-match'\w'-and$a-ge0){}-join$n[++$a..--$i]

Kind of a goofy solution, but a different approach than others.

Takes input $a as the 0-based index of the string $n. Then, we find the boundaries of our word. While we've not reached the end of the string and/or we're still matching word-characters, we ++$a. Then, because of fenceposting, we set $i=$a--. Next, we crawl backwards, decrementing $a until it's either 0 or we hit a non-word-character. We then slice the input string based on those two demarcations (with some increment/decrements to account for OBOE), and -join it together to produce the result.

Examples

PS C:\Tools\Scripts\golfing> .\select-the-word-around-the-index.ps1 2 'This!test'
This

PS C:\Tools\Scripts\golfing> .\select-the-word-around-the-index.ps1 5 'This!test'
test
\$\endgroup\$
  • \$\begingroup\$ select-the-word-around-the-index.ps1 \$\endgroup\$ – Leaky Nun Jul 19 '16 at 17:06
0
\$\begingroup\$

PHP, 98 bytes

function f($s,$p){foreach(preg_split('#\W+#',$s,-1,4)as$m)if($m[1]+strlen($m[0])>=$p)return$m[0];}
  • splits the string by non-word-characters, remembering their position (4 == PREG_SPLIT_OFFSET_CAPTURE), loops through the words until position is reached.
  • PHP strings are 0-indexed, cursor before character, but may be before or after the word
\$\endgroup\$
0
\$\begingroup\$

Python 3, 112 140 bytes

from string import*
p='_'+printable[:62]
def f(s,h,r=''):
 while s[h]in p and h>-1:h-=1
 while h+1<len(s)and s[h]in p:h+=1;r+=s[h]
 return r

0-indexed.

Seeks backward to the first alphanumeric character from the index, then goes forward to the last alphanumeric character after the index. There's probably a smarter way to do this.

Try it

\$\endgroup\$
  • \$\begingroup\$ @LeakyNun _ was added, I'm not sure why I'd get an error for f('abc',1) though. \$\endgroup\$ – atlasologist Jul 19 '16 at 13:30
0
\$\begingroup\$

JavaScript (ES 6), 4342 Bytes

s=>n=>s.replace(/\w*/g,(x,y)=>y<n?s=x:0)&&s

JavaScript (ES 3), 65 Bytes

function(s,n){s.replace(/\w*/g,function(x,y){y<n?s=x:0});alert(s)}
\$\endgroup\$
0
\$\begingroup\$

05AB1E, 14 bytes

ð«._DžjмS¡Á2£J

Port of @AndersKaseorg's Pyth answer.

1-indexed like the challenge test cases.

Try it online or verify all test cases.

Explanation:

ð«              # Append a space to the (implicit) input-String
  ._            # Rotate this string the (implicit) input-integer amount of times
                #  towards the left
     D          # Duplicate this string
      žjм       # Remove [a-zA-Z0-9_] from the string
         S¡     # Split the rotated string by each of the remaining characters
           Á    # Rotate the resulting list once towards the right
            2£J # And only leave the first two items, joined together
                # (which is output implicitly)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.