x86 Machine Code (32-bit, requires built-in FPU), 35 bytes
C8 10 00 00 D9 EE DD 14 24 DD 5C 24 08 8B 01 FE 04 04
83 C1 04 4A 75 F5 6A 0A 58 48 FE 0C 04 75 FA C9 C3
The above code defines a function in 32-bit x86 machine code that finds the unique highest digit in an array of integer values. (The question says we can take the input as a "list of digits", and assembly language is like C in that it lacks a built-in list/array type, but rather represents them as a pointer and length. Therefore, the function is prototyped in C as follows:
int __fastcall FindHighestUniqueDigit(const int * ptrDigits, int count);
As the prototype suggests, this function is written to follow Microsoft's fastcall calling convention, which passes arguments in registers. The first parameter (ptrDigits
) is passed in ECX
, and the second argument (count
) is passed in EDX
. The return value is, as always, returned in EAX
.
Try it online!
Here are the ungolfed assembly mnemonics:
FindHighestUniqueDigit:
enter 16, 0 ; reserve 16 bytes of space on the stack for a table
fldz ; load 0 in the first x87 FPU register (st0)
fst QWORD PTR [esp+0] ; store value in st0 on the stack, at offset 0
fstp QWORD PTR [esp+8] ; store value in st0 on the stack, at offset 0, and pop st0
ReadDigits:
mov eax, DWORD PTR [ecx] ; read next digit from array
add ecx, 4 ; increment pointer to reference next digit (ints are 4-bytes long)
inc BYTE PTR [esp + eax] ; increment value in table for this digit
dec edx ; decrement counter (length of array)
jne ReadDigits ; keep looping as long as there are more digits to read
push 10
pop eax ; space-efficient way to put '10' in EAX so we start with highest digit
ProcessDigits:
dec eax ; decrement EAX
dec BYTE PTR [esp + eax] ; decrement value in table for this digit
jnz ProcessDigits ; if that table value is now zero, we found a match; otherwise, keep looping
leave ; undo effects of ENTER instruction
ret ; return, with result in EAX (digit ID)
I suppose the code requires a bit of explanation…
Basically, what we do is create a table in memory, with enough space to store a unique count for each of the 9 possible digits (0 through 9).
| esp+0 | esp+1 | esp+2 | esp+3 | esp+4 | esp+5 | esp+6 | esp+7 | esp+8 | esp+9 | unused |
|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|--------|
| 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ?? |
The first ENTER
instruction is responsible for allocating this space on the stack. We allocate 16 bytes because it's good practice to keep this a power of 2 for alignment purposes, 8 bytes isn't enough, and it doesn't hurt or cost anymore code bytes to allocate more than we need. The next three instructions are responsible for zeroing out all entries in this array. It's basically equivalent to memset
in C, but shorter. We could have used the x86's REP STOSD
string instruction, but that requires some setup (getting the right values in the right registers), and it turned out that (ab)using the x87 FPU to do 8-byte stores was shorter (required fewer bytes of code). All we do is load 0 at the top of the x87 FPU stack, which is pseudo-register st0
. Then, we store that value across the first 8 bytes of the table, and finally, we store that value across the next 8 bytes of the table, popping it off of the x87 FPU stack at the same time.
The next section of the code is ReadDigits
. This just iterates through the array of digits that we were passed, one digit at a time, and increments the unique counter for that digit in our table. You should be able to figure out exactly how this works by reading the comments for each of the instructions.
Finally, we come to the final phase, ProcessDigits
. Actually, the PUSH
+POP
instructions right above this label are conceptually part of this phase, too. They just initialize EAX
with 10 so that we'll start processing the table values for the highest possible digit, 9. Inside of the ProcessDigits
loop, we eagerly decrement EAX
, and then decrement the value in the table for the corresponding digit. If the resulting table value/counter is 0, then we've found our match, and we exit the loop. Otherwise, we keep looping and trying smaller digits. (Note that we could have done a comparison here—e.g., cmp BYTE PTR [esp + eax], 1
+jnz
—and in fact that's what we're logically doing, but the decrement was shorter to encode.)
Notice that if we never find a matching digit, then the code exhibits undefined behavior: it just keeps looping through memory, decrementing values and waiting until one is non-zero. When it finally finds one, it will terminate and return the ID, but the ID will be meaningless, and more importantly, it will have stomped all over the stack, decrementing values aimlessly!
The very last two instructions just clean up the stack (LEAVE
) and return, with the return value (the highest unique digit that we found) left in EAX
.
2Can we take input as a string instead? – user41805 – 2017-06-28T08:13:05.043
3Given the last test case, I think we are forced to take input as a string... (leading zeroes can't be represented in integers) – Leo – 2017-06-28T08:14:27.247
@Leo that was my bad actually, basically mashed the numbers on my keyboard, didn't notice the leading zero. But yes, input can be taken as a string – Skidsdev – 2017-06-28T08:17:14.040
Then why can't we just take a list of digits (or numbers for that sake)? – Adám – 2017-06-28T08:19:09.787
Can we throw an error if there are no unique digits? – Adám – 2017-06-28T08:21:51.247
25@Adám "undefined behaviour" generally means you can do anything, including summoning nameless horrors from the void if that saves bytes. – Martin Ender – 2017-06-28T08:22:26.807
22@MartinEnder in fact I'll happily knock off 50% of your bytes if your code successfully summons cthulhu upon there being no unique digits ;) – Skidsdev – 2017-06-28T08:31:18.500
@Mayube Can we take a list of digits? – Adám – 2017-06-28T08:43:17.593
@Adám sure, most people are only splitting it into a list of some form anyway, I'll explicitly state accepted input formats on the post – Skidsdev – 2017-06-28T08:47:21.903
Regarding the list of digits: can it be either a list of integers or a list of characters? (
[1,2,3]
vs['1','2','3']
) – Arnauld – 2017-06-28T09:55:21.240@Arnauld no. List of integer digits only. – Skidsdev – 2017-06-28T09:57:36.937