20
\$\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\$
  • 3
    \$\begingroup\$ Is it really important that we accept only uppercase words, or can we choose to accept lowercase words instead? \$\endgroup\$ – Greg Martin May 18 '17 at 22:01
  • 6
    \$\begingroup\$ NOFELINEVET contains both 5 and 9 ... what should I return? \$\endgroup\$ – Titus May 18 '17 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 '17 at 22:49
  • \$\begingroup\$ @Titus: from the rules: "You can assume that no words contain more than one digit" \$\endgroup\$ – nimi May 18 '17 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 '17 at 16:02

20 Answers 20

9
\$\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\$
7
\$\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\$
  • 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 '17 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\$ – Jörg Hülsermann May 18 '17 at 23:42
  • \$\begingroup\$ wicked idea! To answer your question: I´ll have a look into IntlChar::enumCharNames ... tomorrow. \$\endgroup\$ – Titus May 19 '17 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 '17 at 2:30
  • \$\begingroup\$ @user63956 Great idea Thank you. you should add it to the tips section to find subsequences \$\endgroup\$ – Jörg Hülsermann May 19 '17 at 11:15
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\$
5
\$\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
    \$\begingroup\$ Edited to clarify usage \$\endgroup\$ – Greg Martin May 19 '17 at 7:41
4
\$\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\$
  • \$\begingroup\$ How do you use compressed strings??? >_> The compressor by Lynn doesn't work for me, any tips? \$\endgroup\$ – HyperNeutrino May 18 '17 at 22:15
  • \$\begingroup\$ Head into the Jelly chatroom and post the error / issue. \$\endgroup\$ – Jonathan Allan May 18 '17 at 22:17
  • 1
    \$\begingroup\$ Uppercase isn't required. \$\endgroup\$ – Erik the Outgolfer May 19 '17 at 16:24
3
\$\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\$
2
\$\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\$
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\$

Japt, 52 bytes

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

Try it online!

\$\endgroup\$
2
\$\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\$
  • \$\begingroup\$ 1 byte save: use 0 instead of NO. \$\endgroup\$ – CalculatorFeline May 26 '17 at 21:21
  • \$\begingroup\$ @CalculatorFeline Or even an empty string which is a 6 char save. Thanks! \$\endgroup\$ – eush77 May 26 '17 at 22:04
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\$
  • \$\begingroup\$ Great I love it \$\endgroup\$ – Jörg Hülsermann May 18 '17 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\$ – Jörg Hülsermann May 19 '17 at 0:37
  • \$\begingroup\$ Can you use my new approach? \$\endgroup\$ – Jörg Hülsermann May 19 '17 at 1:18
1
\$\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\$
1
\$\begingroup\$

Java, 167 153 bytes

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

A rather naive implementation, matching on regex.

Test and ungolfed

import java.util.function.Function;

public class Main {

  public static void main(String[] args) {
    Function<String, Object> f = s -> {
      for (int i = 0; i < 9;) {
        if (s.matches(".*" + "".join(".*", "ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE".split(" ")[i++].split("")) + ".*")) {
          return i;
        }
      }
      return "NO";
    };

    String[] tests = {
      "BOUNCE",
      "ENCODE",
      "EIGHT",
      "BLACKJACK",
      "FABULOUS",
      "EXERCISE",
      "DRIFTWOOD",
      "SERVICEMAN",
      "INSIGNIFICANCE",
      "THROWDOWN",
      "ZERO",
      "OZNERO"
    };

    for (String test : tests) {
      System.out.printf("%s%n%s%n%n", test, f.apply(test));
    }
  }
}

Saves

  • 167 -> 153: various optimization thanks to @KevinCruijssen
\$\endgroup\$
  • 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\$ – Kevin Cruijssen May 19 '17 at 11:09
  • \$\begingroup\$ You can save another byte by changing "".join to s.join. \$\endgroup\$ – Kevin Cruijssen May 23 '17 at 8:21
1
\$\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\$
  • \$\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 '17 at 13:04
0
\$\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
\$\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\$
0
\$\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\$
0
\$\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\$
0
\$\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\$

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.