48
\$\begingroup\$

Given a string consisting of printable ASCII chars, produce an output consisting of its unique chars in the original order. In other words, the output is the same as the input except that a char is removed if it has appeared previously.

No built-ins for finding unique elements in an array can be used (for example, MATLAB has a unique function that does that). The idea is to do it manually.

Further details:

  • Either functions or programs are allowed.
  • Input and output can be in the form of function arguments, stdin/stdout (even for functions), or a mix of those.
  • If stdin or stdout are used, a string is understood as just the sequence of chars. If function arguments are used, the sequence of chars may need to be enclosed in quotation marks or equivalent symbols that the programming language of choice uses for defining strings.
  • The output should be a string containing only the unique characters of the input. So no extra linebreaks, spaces etc. The only exception is: if the output is displayed in stdout, most displaying functions add a trailing \n (to separate the string from what will come next). So one trailing \n is acceptable in stdout.
  • If possible, post a link to an online interpreter/compiler so that others can try your code.

This is code golf, so shortest code in bytes wins.

Some examples, assuming stdin and stdout:

  1. Input string:

    Type unique chars!
    

    Output string:

    Type uniqchars!
    
  2. Input string

    "I think it's dark and it looks like rain", you said
    

    Output string

    "I think'sdarloe,yu
    
  3. Input string

    3.1415926535897932384626433832795
    

    Output string

    3.14592687
    
\$\endgroup\$
16
  • 8
    \$\begingroup\$ Just to double check: Does the no builtins rule mean that set objects are disallowed? \$\endgroup\$
    – Sp3000
    Oct 4, 2015 at 14:51
  • \$\begingroup\$ @Sp3000 Set objects are allowed. Just don't use a function or method (if it exists) that gives you its unique elements. And input/output should be strings, not set tobjects \$\endgroup\$
    – Luis Mendo
    Oct 4, 2015 at 14:54
  • 7
    \$\begingroup\$ I think only my answer uses sets currently, and I wouldn't mind if you changed it. However, I'm not really sure a bonus like that would change much, e.g. I doubt CJam would be doable in < 6 bytes without sets. Also, I'm not sure where the line is between a builtin which finds unique elements, and constructing a set from a number of elements... \$\endgroup\$
    – Sp3000
    Oct 4, 2015 at 15:13
  • 2
    \$\begingroup\$ @Sp3000 Yes, it's a blurred border. I hadn't anticipated set functions. I think I'll leave the challenge as it is now \$\endgroup\$
    – Luis Mendo
    Oct 4, 2015 at 15:16
  • 1
    \$\begingroup\$ -1 Disallowing built-ins serves no purpose other than making interesting answers. This is code-golf, not popularity contests. \$\endgroup\$
    – MilkyWay90
    Apr 6, 2019 at 2:26

83 Answers 83

1 2
3
1
\$\begingroup\$

Rust, 318 Bytes

not really a competitor, but rust needs a bit more love. As always, comments and advice welcome.

use std::io;fn main(){let mut s=io::stdin();let mut b=String::new();s.read_line(&mut b).unwrap();let mut n: Vec<char>=b.chars().collect();for i in 0..n.len()-2{let mut j=i+1;let mut p = true;while j<n.len(){if j<n.len(){if n[i]==n[j]{n.remove(j);p=false;}}if p{j+=1;}p=true;}}for i in 0..n.len()-2{print!("{}",n[i]);}}

Try it online here (first compile, then execute, then click into the proper area to enter user input)

ungolfed:

use std::io;
fn main() {
let mut s = io::stdin();
let mut b=String::new();
s.read_line(&mut b).unwrap();
let mut chars: Vec<char> = b.chars().collect();
for i in 0..chars.len()-2 {
    let mut j=i+1;
    let mut inc = true;
    while j<chars.len() {
        if j<chars.len() 
        {
            if chars[i]==chars[j] {
                chars.remove(j);
                inc = false;
            }
        }
        if inc {
            j+=1;
        }
        inc = true;
    }
}
for i in 0..chars.len()-2 {
    print!("{}",chars[i]);
}
}
\$\endgroup\$
1
\$\begingroup\$

Python 2, 62 bytes

s=set();print filter(lambda i:not(i in s or s.add(i)),input())
\$\endgroup\$
1
\$\begingroup\$

Python 3, 132 109 105 bytes

u=[]
def t(l):
 if l in u:return''
 else:
  u.append(l);return l
print(''.join([t(l)for l in input()]))

Ungolfed:

used_chars = []
def test(letter):
    if letter in used_chars:
        return '' # skip
    else:
        used_chars.append(letter)
        return letter
print(''.join([test(letter) for letter in input()]))
\$\endgroup\$
3
  • \$\begingroup\$ Shouldn't the r(l) in the last line be t(l)? \$\endgroup\$
    – user4768
    Oct 5, 2015 at 9:16
  • \$\begingroup\$ Also, you can use print if you end the line with a comma: print ('%s') % (''.join([r(l)for l in i])), then import sys won't be necessary. \$\endgroup\$
    – user4768
    Oct 5, 2015 at 9:19
  • \$\begingroup\$ Following Thomas Kwa's suggestion, one trailing \n is acceptable in stdout (in fact it's implicit in many functions used for displaying into stdout). I've made this explicit in the challenge rules. You may perhaps use that to reduce your code length \$\endgroup\$
    – Luis Mendo
    Oct 5, 2015 at 9:54
1
\$\begingroup\$

Scala, 79, 66 bytes

Inspired by the Haskell version using foldLeft

def f(s:String)=s.foldLeft("")((a,c)=>if(a.contains(c))a else a+c)

Previous version using zipWithIndex and collect

def f(s:String)=s.zipWithIndex.collect{case(c,i)if s.indexOf(c)==i=>c}.mkString
\$\endgroup\$
1
\$\begingroup\$

Perl 6, 32 bytes

The way you would normally write this would be:

$*IN.comb.unique.print # it would have been 22

Which would have had the benefit of being obviously correct.


Instead I have to write something like the following

print $*IN.comb.grep:{!.{$^a}++} # 32
\$\endgroup\$
1
\$\begingroup\$

Gema, 18 characters

?=${?;$0}@set{$0;}

Sample run:

bash-4.3$ gema '?=${?;$0}@set{$0;}' <<< 'hello world'
helo wrd
\$\endgroup\$
1
\$\begingroup\$

R, 52 bytes

cat(union(a<-strsplit(readline(),"")[[1]],a),sep="")

Test examples:

Type uniqchars!
"I think'sdarloe,yu
3.14592687
\$\endgroup\$
1
\$\begingroup\$

REXX, 79 bytes

a=arg(1)
o=
do while a>''
  parse var a b+1 a
  if pos(b,o)=0 then o=o||b
  end
say o
\$\endgroup\$
1
\$\begingroup\$

Python, 62 bytes

lambda s:''.join([c for i,c in enumerate(s)if c not in s[:i]])
\$\endgroup\$
2
  • \$\begingroup\$ It's scored by size in bytes, I'll just edit it so its right... Also, you can move the space between the : ''.join so it's like :''.join \$\endgroup\$
    – Blue
    Oct 6, 2015 at 16:27
  • \$\begingroup\$ I don't think you need the outer square brackets. \$\endgroup\$
    – hyper-neutrino
    Mar 23, 2017 at 12:58
1
\$\begingroup\$

Python, 61 59 bytes

lambda s:''.join(i for c,i in enumerate(s)if s.index(i)==c)

str.index always returns the first occurrence, so only output the character if the current occurrence is the first occurrence.

Edit: The square brackets were unnecessary.

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

PHP, 47 Bytes

for(;a&$c=$argn[$i++];)$r[$c]=$c;echo join($r);

Try it online!

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

Ceylon, 98 bytes (stdio) / 69 bytes (function)

This version is using stdio and a HashSet, 98 bytes:

import ceylon.collection{H=HashSet}shared void u(){print(String(H{*(process.readLine()else"")}));}

This is just a function String → String (also using a HashSet), 69 bytes:

import ceylon.collection{H=HashSet}String n(String i)=>String(H{*i});

Ungolfed version:

import ceylon.collection {
    HashSet
}
void uniq() {
    value input = process.readLine() else "";
    value set = HashSet { elements = input; };
    print(String(set));
}

A Ceylon HashSet by default is linked (i.e. conserves the insertion order as iteration order). We use here the named-argument syntax to pass the elements argument to the HashSet constructor.

A string is also an iterable of characters, and as such can be passed to the elements parameter. A string can also be constructed from such a sequence, which is what we do in the last command.

The golfed version again: import ceylon.collection{H=HashSet}shared void u(){print(String(H{*(process.readLine()else"")}));}

Some used tricks:

  • Variable inlining
  • If we have to import HashSet anyways, give it a shorter name.
  • The named-argument syntax accepts also a "normal" argument list. Any arguments in that list will be, if they don't fit the other arguments, be wrapped in an Iterable and passed to an Iterable argument (if there is one). To spread our existing iterable (the input string) into such an argument list, we use the "spread operator *".

If we don't have to handle standard-input and output and just need a function, this is shorter:

import ceylon.collection{HashSet}
String n(String i) => String(HashSet{*i});

The explanations from above are still valid.

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

Ceylon, 98 bytes (stdio) / 68 bytes (function)

Here is a version without using Set or similar stuff, as a function (68 bytes):

String i(String s)=>String{for(x->c in s.indexed)if(!c in s[0:x])c};

The same with stdio (reading just one line) (98 bytes):

void q(){value s=process.readLine()else"";print(String{for(x->c in s.indexed)if(!c in s[0:x])c});}

The first one ungolfed (117 bytes):

String i(String s) => String{
        for (x->c in s.indexed)
            if (!c in s[0:x])
                c
    };

This uses the feature to build a string from an iterable of characters, but this time the iterable is build from a comprehension, which is passed to the string-constructor using the named-argument-list syntax. (This could also have been written as String({...});, using a positional argument list and an iterable-constructor, but that would have been two bytes more).

The comprehension consists of a for-clause (which iterates over an indexed version of the input string, i.e. x is the index corresponding to the character c) and an if-clause (which filters the character by checking if it appears in the substring up to the index).

We see here two different uses of the keyword in – once as part of the syntax of the for clause of comprehensions, and once as the in operator, which maps to s[0:x].contains(c).

The "thin arrow" in x->c is the syntax for an Entry made of x and c (as key and value). As an expression one can build an entry this way from its constituents, but here it is a part of the comprehension syntax (and also the syntax of the for-loop), and deconstructs the entry which is returned by the iterator of the iterable s.indexed.

The "fat arrow" => is a shorthand for defining a function whose body consists of just one expression (which is evaluated and returned) – that saves us the return and a pair of braces.

The variant which uses stdio:

void q() {
    value s = process.readLine() else "";
    print(String { for (x->c in s.indexed)
                if (!c in s[0:x])
                    c });
}

Unfortunately we need a variable here to store the string, because we need to access it twice (in the loop and in the filter).


Instead of using an Iterable comprehension, we can also use the stream-manipulating method filter together with the spread attribute operator *. and a lambda expression (e)=>!e.item in s[0:e.key] inbetween:

String j(String s)=>String(s.indexed.filter((e)=>!e.item in s[0:e.key])*.item);

This is even longer with 79 bytes.

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

sed -E, 21 bytes

:L;s/((.).*)\2/\1/;tL

Try it online!

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

Stax, 5 bytes

╤•╗jP

Run and debug it

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

Red, 23 bytes

func[s][union/case s s]

Try it online!

Using a map!, 58 bytes

func[s][m: copy #()foreach c s[put m c c]rejoin keys-of m]

Try it online!

A naive implementation, 69 bytes

func[s][rejoin collect[foreach c s[unless find collected c[keep c]]]]

Try it online!

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

Lua, 70 bytes

l={}print(((...):gsub('.',function(c)c=l[c]or c;l[c]=''return c end)))

Attempt This Online! Stores the characters in the table after gsub walks through the whole string. Strings are truthy values in Lua so when list[character] is nil, the character is returned the first time then it is replaced with an empty string after the assignment.

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

Lexurgy, 30 bytes

Due to how Lexurgy handles whitespace, it expects whitespace to be replaced with some other character, such as _.

a propagate:
[]$1=>*/$1 []* _

Explanation

a propagate:        # rule a: while the current string changed last run...
[]$1                # match anything...
    =>              # and replace with...
      *             # empty string
       /            # when the input...
        $1          # starts with the captured char
          []*       # followed by any number of anything
             _      # then a captured char
\$\endgroup\$
1
\$\begingroup\$

RProgN, 85 Bytes

S i 'a' = s 'n' = a L while a pop 'b' = n b hasvalue ! if n b push end a L end n i ++

Explanation

S i                     # Take the implicit string input, convert it to a stack such that the first letter is at the bottom, invert the stack such that the first letter is at the top.
'a' =                   # Associate it with 'a'
s 'n' =                 # Create a new stack, associate it with 'n'
a L                     # Push the size of a
while                   # While the top of the stack contains a truthy value
    a pop 'b' =         # Pop the top of a, associate it with 'b'
    n b hasvalue !      # Push if the stack 'n' does not contain b
    if                  # If the top of the stack is truthy
        n b push        # Push b to the stack n
    end                 #
    a L                 # Push the length of a
end                     # loop
n i ++                  # Push the n, inverts it, sums it, which acts to concatenate it, implicitly print the result.
\$\endgroup\$
1
\$\begingroup\$

nanoMIPS machine code, 34 bytes

00000000: 80 1c a0 d3 fc 90 a7 23 87 04 80 bb c0 5f 89 90  .......#....._..
00000010: a7 23 07 30 00 bb a7 23 87 38 d4 5f a9 90 80 bb  .#.0...#.8._....
00000020: 80 1d                                            ..

Commented assembly:

    .globl uniqchars
    # Type uniqchars! for nanoMIPS, Yet Another Weird MIPS Extension
    # Follows C calling convention
    #    void uniqchars(const char *input, char *output);
    # Input:
    #    a0: pointer to a null terminated ASCII string
    #    a1: pointer to an adequately sized buffer
    # Output:
    #    Null terminated ASCII string stored to a1
uniqchars:
    # Create a 128 byte stack buffer for a lookup table
    # Equivalent normieMIPS:
    #  addiu $sp, -128
    save     128
    # Clear the buffer
    li       $a3, 128 / 4
.Lclear:
    # Decrement index
    addiu    $a3, -4
    # Behold! MIPS finally added register-register addressing!
    # And only like 31 years late!
    #  addiu $at, $sp, $a3
    #  sw    $zero, ($at)
    swx      $zero, $a3($sp)
    # Loop if not zero
    bnez     $a3, .Lclear
    # Loop through the string
.Lloop:
    # Load byte from a0
    lb       $a3, ($a0)
    # Increment pointer
    addiu    $a0, 1
    # Load byte from lookup table
    lbx      $a2, $a3($sp)
    # If it is nonzero, this char is not unique. Try the next one.
    bnez     $a2, .Lloop
    # Store the char itself to the lookup table
    sbx      $a3, $a3($sp)
    # Store the char to the output string
    sb       $a3, ($a1)
    addiu    $a1, 1
    # Loop if it was not a null terminator
    bnez     $a3, .Lloop
    # Clean up the stack and return
    #  addiu $sp, 128
    #  jr    $ra
    restore.jrc 128

This uses the new nanoMIPS calling convention, and can be called from C with the following prototype:

void uniqchars(const char *input, char *output);

Yes. This is the third compressed variant of the MIPS instruction set. All three of which are incompatible.

At least it golfs decently. thumb > all

Aside from a few "hybrid instructions" added, it is just normal MIPS in shorthand.

A GCC toolchain, QEMU port, and documentation links are in the link above. Note that for some reason the Windows port lacks qemu-nanomips and both Linux and Windows versions crash on my system unless I run it in qemu-x86_64 itself. 🤔

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

Thunno 2 J, 1 byte

ċ

Attempt This Online!

Takes the set union with itself (set operations were allowed by OP).

Thunno 2 J, 11 bytes

{DȤsƇ¬?ȥ;}Ȥ

Attempt This Online!

A different answer that doesn't use sets.

Explanation

{DȤsƇ¬?ȥ;}Ȥ  # Implicit input
{        }   # For each character in the input:
 D           #  Duplicate it
  Ȥs         #  Push the global array and swap
    Ƈ¬?      #  If it's not in the global array:
       ȥ;    #   Add it to the global array
          Ȥ  # After the loop, push the global array
             # Implicit output, joined
\$\endgroup\$
1
\$\begingroup\$

Rockstar, 126 bytes

Listen to I
Cut I
O's ""
While I
Roll I in C
X's 0
P's 1
While O at X
If C's O at X
P's 0

Build X up

If P
Let O be+C


Say O

Try it (Code will need to be pasted in)

Or, if we can output each character individually, on a separate line ...

124 bytes

Listen to I
Cut I
O's ""
While I
Roll I in C
X's 0
P's 1
While O at X
If C's O at X
P's 0

Build X up

If P
Let O be+C
Say C
\$\endgroup\$
0
\$\begingroup\$

Z80Golf, 13 bytes

00000000: cd 03 80 38 04 26 ff 6f cb 76 74 c5 c0           ...8.&.o.vt..

Try it online!

Commented assembly:

start:
.loop:
    call  0x8003         ; CD 0380  Read from stdin to A, C is success
    jr    c, .end + 1    ; 38 04    Jump to the 76 in the middle of the bit opcode to halt
    ld    h, 0xFF        ; 26 FF    HL = 0xFFxx
    ld    l, a           ; 6F       HL = 0xFF00+A, address + offset of LUT
.end:
    bit   6, (hl)        ; CB 76    Test bit 6 of the table
    ; halt               ;   (76)      Exit the program with halt (overlaps)
    ld    (hl), h        ; 74       Mark the LUT to 0xFF
    push  bc             ; C5       Push address of .loop for ret (BC is 0x0000)
    ret   nz             ; C0       If bit 6 was set (not unique), ret to .loop
                         ;          otherwise fall through to putchar

This uses the LUT method in my other answers. However it is much easier on Z80Golf because I know the full state of the machine and can optimize for it.

Specifically, I know that the 128 bytes at 0xFF00-0xFF7F will be 0, so I can use that by setting the high byte of HL and the low byte of A.

There are some weird optimizations to make this smaller. I could get 0xFF in h in one byte by dec h, but that introduces a state I have to check. Instead, I suck up the byte for ld h, 0xFF in favor of the entire program being stateless outside of the table. Then, I can use push bc followed by ret nz to loop if nonzero or fall through the nop sled to putchar otherwise.

\$\endgroup\$
1 2
3

Your Answer

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

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