23
\$\begingroup\$

A digit word is a word where, after possibly removing some letters, you are left with one of the single digits: ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT or NINE (not ZERO).

For example, BOUNCE and ANNOUNCE are digit words, since they contain the digit one.

ENCODE is not a digit word, even though it contains an O, N and E, since they are not in order.

Write a program/function which takes a single (uppercase or lowercase -- you choose) word as input or a parameter and determines if it is a digit word. Make the code as short as possible.

If the word is not a digit word, you should return 'NO', 0 or any 'falsey' value (this may vary based on your language). If the word is a digit word, you should output the digit it contains, as a number.

You can assume that no words contain more than one digit, so you won't have anything like ONFIVE.

Test Cases

BOUNCE
1

ENCODE
NO

EIGHT
8

BLACKJACK
NO

FABULOUS
NO

EXERCISE
NO

DRIFTWOOD
2

SERVICEMAN
7

INSIGNIFICANCE
9

THROWDOWN
2

ZERO
NO

OZNERO
1

This challenge is taken from (and is a very slight modification of) Question 1 from BIO 2009. Most of the test cases are taken from the mark scheme.

\$\endgroup\$
7
  • 3
    \$\begingroup\$ Is it really important that we accept only uppercase words, or can we choose to accept lowercase words instead? \$\endgroup\$ May 18, 2017 at 22:01
  • 6
    \$\begingroup\$ NOFELINEVET contains both 5 and 9 ... what should I return? \$\endgroup\$
    – Titus
    May 18, 2017 at 22:39
  • 3
    \$\begingroup\$ Can we return 0 in the no-digit-found case even if it's not falsey in the language of choice? \$\endgroup\$
    – nimi
    May 18, 2017 at 22:49
  • \$\begingroup\$ @Titus: from the rules: "You can assume that no words contain more than one digit" \$\endgroup\$
    – nimi
    May 18, 2017 at 23:21
  • \$\begingroup\$ @GregMartin Well, I guess it doesn't add anything to the challenge, so yes, lowercase is allowed. I've changed it. \$\endgroup\$
    – 0WJYxW9FMN
    May 19, 2017 at 16:02

24 Answers 24

10
\$\begingroup\$

Javascript (ES6), 101 99 bytes

f=
s=>-~'ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split` `.findIndex(x=>s.match([...x].join`.*`))
<!-- snippet demo: -->
<input list=l oninput=console.log(f(this.value))>
<datalist id=l><option value=BOUNCE>
<option value=ENCODE>
<option value=EIGHT>
<option value=BLACKJACK>
<option value=FABULOUS>
<option value=EXERCISE>
<option value=DRIFTWOOD>
<option value=SERVICEMAN>
<option value=INSIGNIFICANCE>
<option value=THROWDOWN>
<option value=ZERO>
<option value=OZNERO>

\$\endgroup\$
8
\$\begingroup\$

PHP>=7.0, 87 Bytes

<?for(;$i++<9;)levenshtein(IntlChar::charName("$i"),"DIGIT $argn",0,1,1)?:die("$i")?>NO

If only insertions of chars from one digit as word to the input is done exit the program with the digit. Or change the order to levenshtein("DIGIT $argn",IntlChar::charName("$i"),1,1,0) to count not the deletions of chars

levenshtein

IntlChar::charName

PHP>=7.0, 112 Bytes

for(;$i<9;)$r+=++$i*preg_match("#".chunk_split(substr(IntlChar::charName("$i"),6),1,".*")."#",$argn);echo$r?:NO;

IntlChar::charName

PHP, 128 Bytes

foreach([ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE]as$v)$r+=++$k*preg_match("#".chunk_split($v,1,".*")."#",$argn);echo$r?:NO;

Try it online!

143 Bytes for more then 1 digit

foreach([ONE,TWO,THREE,FOUR,FIVE,SIX,SEVEN,EIGHT,NINE]as$v)$r.=++$k*preg_match("#".chunk_split($v,1,".*")."#",$argn);echo+$r?strtr($r,[""]):NO;

Try it online!

\$\endgroup\$
9
  • 1
    \$\begingroup\$ foreach(...)$r+=++$k*preg_match(...);echo$r?:NO; (-1 byte). chunk_split($v,1,".*") instead of join(...) (-2 bytes). \$\endgroup\$
    – Titus
    May 18, 2017 at 23:31
  • \$\begingroup\$ @Titus very nice idea with the replacement withchunk_split I have never seen it before. You should make an entry in the tips section \$\endgroup\$ May 18, 2017 at 23:42
  • \$\begingroup\$ wicked idea! To answer your question: I´ll have a look into IntlChar::enumCharNames ... tomorrow. \$\endgroup\$
    – Titus
    May 19, 2017 at 1:32
  • 2
    \$\begingroup\$ levenshtein() seems to work: <?for(;$i++<9;)levenshtein(IntlChar::charName("$i"),"DIGIT $argn",0,1,1)?:die("$i")?>NO. \$\endgroup\$
    – user63956
    May 19, 2017 at 2:30
  • \$\begingroup\$ @user63956 Great idea Thank you. you should add it to the tips section to find subsequences \$\endgroup\$ May 19, 2017 at 11:15
6
\$\begingroup\$

Mathematica, 83 bytes (WindowsANSI encoding)

±w_:=Rest@Position[Subsets@w~Cases~#&/@Characters@*IntegerName~Array~9,Except@{},1]

Defines a unary function ± that takes a list of lowercase characters as input and returns either a digit, in a form like {{7}}, or else an empty list {}. I don't feel like I did a ton of golfy things here, except that Characters@*IntegerName~Array~9 generates the number-name matches to look for without hard-coding them.

Example usage:

±{"i", "n", "s", "i", "g", "n", "i", "f", "i", "c", "a", "n", "c", "e"}

yields {{9}}.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Edited to clarify usage \$\endgroup\$ May 19, 2017 at 7:41
5
\$\begingroup\$

Python 3, 150 bytes

from itertools import*;lambda x:([i for i in range(10)if(*' OTTFFSSENNWHOIIEIIEORUVXVGN  ERE EHE  E   NT '[i::9],)in[*combinations(x+'  ',5)]]+[0])[0]

combinations returns all the combinations of things in order. It would be simpler to have a set number for the second parameter of combinations, so spaces are added onto the end of the original string that is a parameter of my lambda. That's a simple description of how my entry works. Ask if you would like any more clarification.

\$\endgroup\$
0
5
\$\begingroup\$

Jelly, 31 28 bytes

-2 bytes now that lowercase input is acceptable

“¡¦ẇṆb~ṇjṚØ%ĖġṘḥḞṾṇJḌ»Ḳe€ŒPT

A full program that expects lowercase input and prints the result, using 0 for the falsey case.

As a monadic link taking a list of characters it actually returns a list of integers which contains a single 0 in the falsey case, a single integer between 1 and 9 inclusive in the expected use cases and multiple such entries in cases where more than one number exists in the word.

Try it online!

How?

“¡¦ẇṆb~ṇjṚØ%ĖġṘḥḞṾṇJḌ»Ḳe€ŒPT - Main link: list of characters, s
“¡¦ẇṆb~ṇjṚØ%ĖġṘḥḞṾṇJḌ»       - dictionary lookup of "one two three four five six seven eight nine"
                      Ḳ      - split on spaces
                         ŒP  - partitions of s
                       e€    - exists in? for €ach
                           T - truthy indexes
\$\endgroup\$
3
  • \$\begingroup\$ How do you use compressed strings??? >_> The compressor by Lynn doesn't work for me, any tips? \$\endgroup\$
    – hyper-neutrino
    May 18, 2017 at 22:15
  • \$\begingroup\$ Head into the Jelly chatroom and post the error / issue. \$\endgroup\$ May 18, 2017 at 22:17
  • 1
    \$\begingroup\$ Uppercase isn't required. \$\endgroup\$ May 19, 2017 at 16:24
5
\$\begingroup\$

Ruby + to_words: 49 48+12 = 61 60 bytes

Uses the flags -rto_words -n. Takes lowercase words. Returns nil if no "digit" found.

-1 byte now that lowercase input is allowed, allowing for the removal of the i flag on the regex.

p (1..9).find{|i|$_=~/#{i.to_words.chars*".*"}/}

For a more pure Ruby answer without external gems, 91+1 = 92 bytes:

p (1..9).find{|i|$_=~/#{%w"0 ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE"[i].chars*".*"}/}
\$\endgroup\$
4
\$\begingroup\$

Python, 148 bytes

from itertools import*
lambda s:[tuple(w)in combinations(s,len(w))for w in("x ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE "+s).split()].index(1)%10

An unnamed function taking an uppercase only word and returning the integer (1 to 9), or 0 for NO.

Try it online!

How?

For an input string s the function traverses through a list of the strings: "x", "ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT" , "NINE", and s itself looking for any matches.*

The comparison used is whether this string, w, is one that may be formed from a combination of letters in order from the input. The function combinations gets these for us (and only the ones of the required length using len(w)), but they are in the form of tuples, so the strings are cast to tuples for the comparison.

Of the eleven results the one for "x" will always be False, while the one for s itself will always be True. The "x" is there to ensure the index of a match with ONE through NINE are the values required (since Python lists are 0-indexed), the s is there to ensure the call to index(1) (synonymous with index(True)) wont fail when no digit word was found, whereupon the resulting 10 is converted to a 0 with a modulo of ten using %10.

* If s contains spaces for some reason, the list of ws will be longer, but the process will still work since the digit word matches will work the same way, and if none match the first space-split substring from s will match, once again giving 10 and returning 0.

If multiple digit words exist the function will return the minimal one.

\$\endgroup\$
3
\$\begingroup\$

05AB1E, 26 bytes

The falsy value here is 0.

æ‘€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‘#å1k>

Explanation:

æ                           # Compute the powerset of the input
 ‘€µ‚•„í†ìˆÈŒšï¿Ÿ¯¥Š‘       # Push "ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE"
                     #      # Break on spaces
                      å     # Check each for membership
                       1k   # Get the index of 1 in the array (-1 if not found)
                         >  # Increment by one

Uses the 05AB1E encoding. Try it online! or Verify all test cases!

\$\endgroup\$
1
  • \$\begingroup\$ 1k> can be ƶà for -1 byte. \$\endgroup\$ Sep 9, 2022 at 6:27
3
\$\begingroup\$

Japt, 52 bytes

1+`e two È(e fŒr five £x  v eight ͍`¸a@v fX¬q".*

Try it online!

\$\endgroup\$
3
\$\begingroup\$

Retina, 160 126 120 bytes

O.*N.*E
1
T.*W.*O
2
T.*H.*R.*E.*E
3
F.*O.*U.*R
4
F.*I.*V.*E
5
S.*I.*X
6
S.*E.*V.*E.*N
7
E.*I.*G.*H.*T
8
N.*I.*N.*E
9
\D

Try it online!

Returns an empty string if the input does not contain a digit.

-6 bytes thanks to @CalculatorFeline.

\$\endgroup\$
2
  • \$\begingroup\$ 1 byte save: use 0 instead of NO. \$\endgroup\$ May 26, 2017 at 21:21
  • \$\begingroup\$ @CalculatorFeline Or even an empty string which is a 6 char save. Thanks! \$\endgroup\$
    – eush77
    May 26, 2017 at 22:04
2
\$\begingroup\$

Haskell, 113 111 bytes

import Data.List
last.((`elemIndices`([]:words"ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE"))=<<).subsequences

Try it online!

Returns 0 if no digit is found.

Find all subsequences of the input word in the list of digits. Prepend an empty string [] at index 0 which is part of every subsequence. elemIndices returns a list of indices and =<< flattens them into a single list. Pick the last index.

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

JavaScript (ES6), 121 bytes

f=
s=>[...s].map(c=>a=a.map(s=>s.slice(s[0]==c)),a=`ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE`.split` `)&&1+a.indexOf(``)
<input oninput=o.textContent=f(this.value)><pre id=o>0

Returns the lowest detected digit or 0 if no digit was detected (+6 if NO is required).

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

Bash, 163 bytes

a=NO;case $1 in *O*N*E*)a=1;;*T*W*O*)a=2;;*T*H*R*E*E*)a=3;;*F*O*U*R*)a=4;;*F*I*V*E*)a=5;;*S*I*X*)a=6;;*S*E*V*E*N*)a=7;;*E*I*G*H*T*)a=8;;*N*I*N*E*)a=9;;esac;echo $a

Try it online!

a=NO;
case $1 in
 *O*N*E*)a=1;;
 *T*W*O*)a=2;;
 *T*H*R*E*E*)a=3;;
 *F*O*U*R*)a=4;;
 *F*I*V*E*)a=5;;
 *S*I*X*)a=6;;
 *S*E*V*E*N*)a=7;;
 *E*I*G*H*T*)a=8;;
 *N*I*N*E*)a=9;;
esac;
echo $a

Don't know RETINA but seems a straight port of that answer.

\$\endgroup\$
1
  • \$\begingroup\$ tried to reduce *'s with j=0;for i in ONE TWO .. ; do ((j++)); printf "%s)a=%s;;" $(sed 's/./\*&\*/g' <<<$i) $j ; done but was longer \$\endgroup\$
    – marcosm
    May 19, 2017 at 13:04
2
\$\begingroup\$

Java (JDK), 150 bytes

s->{for(var i=0;i<9;)if(s.matches("."+"ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE".split(" ")[i++].replaceAll(".","*$0.")+"*"))return i;return"NO";}

Try it online!

Saves

  • 167 -> 153: various optimization thanks to @KevinCruijssen
\$\endgroup\$
2
  • 1
    \$\begingroup\$ You only use the array once, so you can use "ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE".split(" ")[i].split("") directly in the for-loop, and get rid of String[]N= and ;. And you can save an additional 2 bytes by changing the order of the for-loop: for(int i=0;i<9;), use [i++] instead of [i] in the if-check, and get rid of the +1 in the return. \$\endgroup\$ May 19, 2017 at 11:09
  • \$\begingroup\$ You can save another byte by changing "".join to s.join. \$\endgroup\$ May 23, 2017 at 8:21
2
\$\begingroup\$

Brachylog, 57 bytes

∧"ONE
TWO
THREE
FOUR
FIVE
SIX
SEVEN
EIGHT
NINE"ṇi₁Ph⊆?∧Pt

Try it online!

Explanation

I think this could be shorter if i were reversible, but it doesn't appear to be.

∧"..."ṇi₁Ph⊆?∧Pt
∧                  Break unification with input
 "..."             String containing words ONE through NINE, newline-separated
      ṇ            Split on newlines
       i₁          [item, index] pair for some item in that list (1-based index)
         P         Call that pair P
          h        Its first element (the word)
           ⊆       is a non-contiguous substring of
            ?      the input
             ∧     And
               Pt  P's last element (the index) is the output
\$\endgroup\$
2
\$\begingroup\$

Python, 125 123 bytes

f=lambda b,x=9,i=0:x and f(b,x-1)+x*all(i:=b.find(c,i)+1for c in"NINE EIGHT SEVEN SIX FIVE FOUR THREE TWO ONE".split()[-x])

Attempt This Online!

-2 bytes from @xnor, also fixing a misalignment with the spec

\$\endgroup\$
4
  • \$\begingroup\$ Unfortunately, this says. i.e., that THREW contains THREE due to how it handles repeated letters, but it can be fixed for 2 bytes. \$\endgroup\$
    – xnor
    Sep 10, 2022 at 3:48
  • \$\begingroup\$ You also need to count the f=. But here's a byte back for 126. \$\endgroup\$
    – xnor
    Sep 10, 2022 at 3:57
  • 1
    \$\begingroup\$ Back to 123 bytes \$\endgroup\$
    – xnor
    Sep 10, 2022 at 4:08
  • \$\begingroup\$ @xnor Somehow, every time I read your solutions, I think both "Thats a very clever solution" and "Why didn't I think of that?" in equal measure :P \$\endgroup\$ Sep 10, 2022 at 5:23
2
\$\begingroup\$

Factor + math.combinatorics math.text.english, 53 bytes

[ all-subsets 9 [1,b] [ number>text ] map disjoint? ]

Try it online!

Returns f for 'is a digit word' and t for 'is not a digit word.'

  • all-subsets Get all the subsets of the input.
  • 9 [1,b] [ number>text ] map A shorter way to write { "one" "two" "three" "four" "five" "six" "seven" "eight" "nine" }.
  • disjoint? Do the sets not overlap? (This is 2 bytes shorter than saying intersects?.)
\$\endgroup\$
1
\$\begingroup\$

PHP, 134 132 128 bytes

<?=preg_match(strtr(chunk_split("#(ONE0TWO0THREE0FOUR0FIVE0SIX0SEVEN0EIGHT0NINE",1,".*"),[")|("]).")#",$argn,$m)?count($m)-1:NO;

run as pipe with -nF or try it online.

Creates a regex with the words in parentheses; i.e. each word N is in the Nth sub-expression.
If a word is found, the matched string will be in $m[0] and in the the Nth element, with the elements between them empty and no empty string behind; i.e. $m has N+1 elements.

\$\endgroup\$
3
  • \$\begingroup\$ Great I love it \$\endgroup\$ May 18, 2017 at 23:55
  • 1
    \$\begingroup\$ You can save 3 Bytes with your own idea a little improve <?=preg_match("#(".strtr(chunk_split(ONE0TWO0THREE0FOUR0FIVE0SIX0SEVEN0EIGHT0NINE,1,".*"),[")|("]).")#",$argn,$m)?count($m)-1:NO; Try it online! \$\endgroup\$ May 19, 2017 at 0:37
  • \$\begingroup\$ Can you use my new approach? \$\endgroup\$ May 19, 2017 at 1:18
1
\$\begingroup\$

Javascript, 121 bytes

(s,a=0)=>'ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split``.map((p,i)=>RegExp(p.split``.join`.*`).exec(s)?a=i+1:0)&&a

or 116

(s,a=0)=>' ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split` `.map((p,i)=>a|=~s.search(p.split``.join`.*`)&&i)&&a

But just recycling material at this point.

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

Pyth, -44- 41 bytes

+1xm}dyQc."EX%~)Û#lº,îQÓCe¯4Aô4"\P1

Takes a quoted string, outputs 0 for NO.

Try it!

explanation

+1xm}dyQc."EX%~)Û#lº,îQÓCe¯4Aô4"\P1
         ."EX%~)Û#lº,îQÓCe¯4Aô4"      # Compressed string: "ONEPTWOPTHREEPFOURPFIVEPSIXPSEVENPEIGHTPNINE" (P is close to the other letters, makes the compression better)
        c                       \P    # split on P: ['ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE']
   m                                  # map over this list (variable: d)
    }dyQ                              # is d a (ordered) subset of the input (Q)? (= element of the powerset)
  x                                1  # get the index of the first true
+1                                    # add one, because the list was indexed at 0 and conveniently -1 (not found) becomes 0 
\$\endgroup\$
1
\$\begingroup\$

Java, 254 bytes

int g(String a,String b){int i=0,p=0;for(char c:a.toCharArray()){p=i;i=b.indexOf(c);if(i<=p)return 0;}return 1;}
String f(String a){String[]T={"ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"};for(String t:T)if(g(t,a)>0)return t;return"NO";}

Try Online

boolean g(String a,String b)
{
    int i = 0, p = 0;
    for(char c:a.toCharArray())
    {
        p = i;
        i = b.indexOf(c);
        if(i <= p) return false;
    }
    return true;
}

String f(String a)
{
    String[]T={"ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"};
    for(String t:T)if(g(t,a))return t;
    return"NO";
}
\$\endgroup\$
1
\$\begingroup\$

C, 198 bytes

Try Online

char*T[]={"ONE","TWO","THREE","FOUR","FIVE","SIX","SEVEN","EIGHT","NINE"};
g(char*a,char*b){while(*a&*b)a+=(*b++==*a);return!*a;}
i;char*f(char*b){for(i=0;i<9;i++)if(g(T[i],b))return T[i];return"NO";}
\$\endgroup\$
1
\$\begingroup\$

Python 2, 155 bytes

import re;lambda k:next((i for i,j in enumerate([re.search('.*'.join(list(s)),k)for s in'ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE'.split()])if j),-1)+1

An anonymous function searching for regex group. Not the best solution here in Python but an alternative way.

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

Vyxal, 9 8 bytes

9'∆ċ?ṗṠc

Try it Online!

-1 thanks to EmanresuA

9 bytes but faster:

9'∆ċ:?Þ∩=

Try it Online!

The joys of having number to word built-ins!

Explained

9'∆ċ:?Þ∩=
9'        # From the range [1, 9], keep only items n where:
  ∆ċ      #   n as a word
     ?Þ∩  #   under multiset intersection with the input
    :   = #   is the same. 
\$\endgroup\$
1
  • \$\begingroup\$ 8 (slow) \$\endgroup\$
    – emanresu A
    Sep 9, 2022 at 4:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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