Divida uma palavra em partes com pontuações iguais

9

Assumindo A = 1, B = 2 ... Z = 26, e o valor de uma palavra é a soma desses valores de letras, é possível dividir algumas palavras em duas partes, para que elas tenham valores iguais.

Por exemplo, "wordsplit" pode ser dividido em duas partes: ordsl wpit, porque o + r + d + s + l = w + p + i + t.

Esse foi um desafio que o professor de computação nos deu - é um antigo desafio da Lionhead Studios, aparentemente. Eu o resolvi em Python e postarei minha resposta em breve.

Desafio: O programa mais curto que pode listar todos as divisões possíveis com pontuações iguais. Observe que ele só precisa listar uma para cada grupo de letras - ordsl wpit é o mesmo que rdosl wtip, por exemplo. É mais fácil listá-los na ordem em que aparecem na palavra.

Bônus:

  • Se você destacar pares em que ambas as palavras são válidas em inglês (ou alguma permutação das letras), use algum tipo de lista de palavras. (Isso pode ser feito colocando um asterisco ao lado de cada um ou outro método, mas deixe claro.)
  • Adicionando a opção para remover duplicatas (esse não deve ser o padrão).
  • Suportando mais de duas divisões, por exemplo, três, quatro ou mesmo divisões de sentido n.
Thomas O
fonte
O programa deve oferecer suporte a entrada de casos mistos? E se sim, pode descartar o argumento para a saída?
Nemo157
@ Nemo157 Pode ignorar maiúsculas e minúsculas e não tem que preservá-lo na saída.
Thomas O
O programa pode produzir coisas extras, desde que a parte solicitada da saída seja clara para um ser humano?
JB
@JB Sim, pode.
Thomas O
ok, vou melhorar esse Perl então;) Obrigado
JB

Respostas:

4

Perl, 115 118 123

@_=~/./g;for$i(1..1<<@_){$l=$
r;$i&1<<$_?$l:$r+=64-ord$_[$_
]for 0..$#_;$l-$r||$i&1<<$_&&
print$_[$_]for 0..$#_;say""}

Corra com perl -nE '<code goes here>'. Esse 'n' é contado no tamanho do código.

Substituído:

@_ = /./g;
for $i (1 .. 1<<@_) {
  $l = $r;
  $i & 1<<$_ ? $l : $r -= 64 - ord $_[$_] for 0 .. $#_;

  $l - $r      ||
  $i & 1<<$_   &&
  print $_[$_]
    for 0 .. $#_;

  say ""
}

Com comentários e nomes de variáveis:

# split a line of input by character
@chars = /./g;

# generate all binary masks of same length
for $mask (1 .. 1<<@_) {

  # start at "zero"
  $left_sum = $right_sum;

  # depending on mask, choose left or right count
  # 1 -> char goes left; 0 -> char goes right
  $mask & 1<<$_ ? $left_sum : $right_sum
    -= 64 - ord $chars[$_]   # add letter value
      for 0 .. $#chars;      # for all bits in mask

  # if left = right
  $left_sum - $right_sum ||

  # if character was counted left (mask[i] = 1)
  $mask & 1<<$_          &&

  # print it
  print $chars[$_]

  # ...iterating on all bits in mask
    for 0 .. $#chars;

  # newline
  say ""
}

Alguns dos truques usados:

  • 1..1<<@_ cobre o mesmo intervalo de bits que 0..(1<<@_)-1 , mas é mais curto. (observe que, considerando o problema de mais longe, incluindo os limites do intervalo várias vezes, isso não resultaria em uma saída errada)
  • $ left_range e $ right_range não são redefinidos para o zero numérico real "0": como apenas acumulamos e comparamos no final, tudo o que precisamos é que eles comecem com o mesmo valor.
  • subtrair em 64-ord$_[$_]vez de adicionar ord$_[$_]-64ganha um caractere invisível: como termina com um delimitador, torna fordesnecessário o espaço antes .
  • Perl permite atribuir a uma variável determinada pelo operador condicional ternário: cond ? var1 : var2 = new_value.
  • expressões booleanas encadeadas com &&e ||são usadas em vez de condicionais apropriadas.
  • $l-$r é mais curto que $l!=$r
  • produzirá uma nova linha mesmo em divisões que não são equilibradas. As linhas vazias estão ok pelas regras! Eu perguntei!
JB
fonte
Gostaria de explicar para aqueles de nós que não falam barulho na linha? Parece que você está usando uma abordagem de máscara binária semelhante à minha, e vejo 64 significa '@' = 'A' - 1, e depois disso estou praticamente perdida.
dmckee --- ex-moderador gatinho
Esta edição é melhor?
JB
Agradável. Preciso pensar em aproveitar a soma de cada contagem à esquerda ou à soma certa. Deveria ter sido óbvio, mas eu perdi.
dmckee --- ex-moderador gatinho
3

J (109)

~.(/:{[)@:{&a.@(96&+)&.>>(>@(=/@:(+/"1&>)&.>)#[),}.@(split~&.>i.@#@>)@<@(96-~a.&i.)"1([{~(i.@!A.i.)@#)1!:1[1

Saída para wordsplit:

┌─────┬─────┐
│lorw │dipst│
├─────┼─────┤
│diltw│oprs │
├─────┼─────┤
│iptw │dlors│
├─────┼─────┤
│dlors│iptw │
├─────┼─────┤
│oprs │diltw│
├─────┼─────┤
Ipdipst│lorw │
└─────┴─────┘

Explicação:

  • 1!:1[1: leia uma linha de stdin
  • ([{~(i.@!A.i.)@#): obter todas as permutações
  • "1: para cada permutação:
  • (96-~a.&i.): obter pontuações de letras
  • }.@(split~&.>i.@#@>)@<: divida cada permutação das pontuações em cada espaço possível, exceto antes do primeiro e após o último número
  • >(>@(=/@:(+/"1&>)&.>)#[): veja quais permutações têm metades correspondentes e selecione estas
  • {&a.@(96&+)&.>: transformar as pontuações novamente em letras
  • ~.(/:{[): remover variações triviais (por exemplo, ordsl wpite ordsl wpti)
marinus
fonte
Algumas de suas respostas são duplicadas.
DavidC
@DavidCarraher: Bem, ou sou cego ou este não é, nem minhas respostas recentes. Eu nunca copiei as respostas de outras pessoas de propósito, embora você possa estar certo, eu postei aqui enquanto estava bêbado às vezes e não me lembro até que recebi muitos votos negativos e acabou que eu enviei algo que nem sequer era perto de corrigir. Se você me viu se comportar mal, talvez deixe o comentário em que estou me comportando mal, excluirei as respostas ofensivas; ou talvez diminua o voto para eles, pois é para isso que servem os votos negativos.
marinus
Nenhuma ligeira foi planejada. Simplesmente quis dizer, por exemplo, que sua primeira resposta, {"lorw", "dipst"} é uma duplicata da sua resposta final, {"dipst", "lorw"}. Somente a ordem das palavras é diferente.
DavidC
@DavidCarraher: whoops: PI pensou que você quis dizer que eu tinha copiado a resposta de alguém ... de qualquer forma, esta pergunta diz (se eu a interpretei corretamente) para remover as duplicatas em que as partes individuais são apenas permutações uma da outra, mas não para remover aqueles em que a ordem das peças é diferente, ou seja, se {a,bc}já for encontrada, para remover, {a,cb}mas não para remover {bc,a}. (E, claro, eu não estou ofendido, se eu realmente / HAD / duplicada a resposta de alguém que eu prefiro que se alguém apontou.)
marinus
Você parece estar certo. As instruções dizem que não há problema em ignorar a ordem das palavras ("Observe que ele só precisa listar uma para cada grupo de letras"), mas não é necessário. Isso pode me salvar alguns caracteres. Obrigado.
DavidC
2

c99 - 379 caracteres necessários

#include <stdio.h>
#include <string.h>
#include <ctype.h>
int s(char*w,int l,int m){int b,t=0;for(b=0;b<l;++b){t+=(m&1<<b)?toupper(w[b])-64:0;}return t;}
void p(char*w,int l,int m){for(int b=0;b<l;++b){putchar((m&1<<b)?w[b]:32);}}
int main(){char w[99];gets(w);int i,l=strlen(w),m=(1<<l),t=s(w,l,m-1);
for(i=0;i<m;i++){if(s(w,l,i)==t/2){p(w,l,i);putchar(9);p(w,l,~i);putchar(10);}}}

A abordagem é bastante óbvia. Existe uma função que soma as palavras de acordo com uma máscara e uma função que as imprime também de acordo com uma máscara. Entrada da entrada padrão. Uma peculiaridade é que a rotina de impressão insere espaços para letras que não estão na máscara. Uma guia é usada para separar os grupos.

Não faço nenhum dos itens de bônus, nem é facilmente convertido para apoiá-los.

Legível e comentado:

#include <stdio.h>
#include <string.h>
#include <ctype.h>
int s(char *w, int l, int m){ /* word, length, mask */
  int b,t=0;                  /* bit and total */
  for (b=0; b<l; ++b){        
/*     printf("Summing %d %d %c %d\n",b,m&(1<<b),w[b],toupper(w[b])-'A'-1); */
    t+=(m&1<<b)?toupper(w[b])-64:0; /* Add to toal if masked (A-1 = @ = 64) */
  }
  return t;
}
void p(char *w, int l, int m){
  for (int b=0; b<l; ++b){ 
    putchar((m&1<<b)?w[b]:32);  /* print if masked (space = 32) */
  }
}
int main(){
  char w[99];
  gets(w);
  int i,l=strlen(w),m=(1<<l),t=s(w,l,m-1);
/*   printf("Word is '%s'\n",w); */
/*   printf("...length %d\n",l); */
/*   printf("...mask   0x%x\n",m-1); */
/*   printf("...total  %d\n",t); */
  for (i=0; i<m; i++){
/*     printf("testing with mask 0x%x...\n",i); */
    if (s(w,l,i)==t/2) {p(w,l,i); putchar(9); p(w,l,~i); putchar(10);}
    /* (tab = 9; newline = 10) */
  }
}

Validação

 $ wc wordsplit_golf.c
  7  24 385 wordsplit_golf.c
 $ gcc -std=c99 wordsplit_golf.c
 $ echo wordsplit | ./a.out
warning: this program uses gets(), which is unsafe.
 or sp          w  d  lit
wor   l            dsp it
 ords l         w    p it
w    p it        ords l  
   dsp it       wor   l  
w  d  lit        or sp   
dmckee --- gatinho ex-moderador
fonte
1

Ruby: 125 caracteres

r=->a{a.reduce(0){|t,c|t+=c.ord-96}}
f=r[w=gets.chomp.chars]
w.size.times{|n|w.combination(n).map{|s|p([s,w-s])if r[s]*2==f}}

Exemplo de execução:

bash-4.2$ ruby -e 'r=->a{a.reduce(0){|t,c|t+=c.ord-96}};f=r[w=gets.chomp.chars.to_a];w.size.times{|p|w.combination(p).map{|q|p([q,w-q])if r[q]*2==f}}' <<< 'wordsplit'
[["w", "o", "r", "l"], ["d", "s", "p", "i", "t"]]
[["w", "p", "i", "t"], ["o", "r", "d", "s", "l"]]
[["o", "r", "s", "p"], ["w", "d", "l", "i", "t"]]
[["w", "d", "l", "i", "t"], ["o", "r", "s", "p"]]
[["o", "r", "d", "s", "l"], ["w", "p", "i", "t"]]
[["d", "s", "p", "i", "t"], ["w", "o", "r", "l"]]
homem a trabalhar
fonte
1

Mathematica 123 111

Localiza todos os subconjuntos de palavras que possuem 1/2 do "total ASCII" da palavra d,. Em seguida, encontra os complementos desses subconjuntos.

d = "WORDSPLIT"

{#, Complement[w, #]}&/@Cases[Subsets@#,x_/;Tr@x==Tr@#/2]&[Sort[ToCharacterCode@d - 64]];
FromCharacterCode[# + 64] & /@ %

{{"IPTW", "DLORS"}, {"LORW", "DIPST"}, {"OPRS", "DILTW"}, {"DILTW", "OPRS"}, {"DIPST", "LORW"} , {"DLORS", "IPTW"}}

DavidC
fonte
1

J, 66 caracteres

Usando dígitos dos números base2 para selecionar todos os subconjuntos possíveis.

   f=.3 :'(;~y&-.)"{y#~a#~(=|.)+/"1((+32*0&>)96-~a.i.y)#~a=.#:i.2^#y'
   f 'WordSplit'
┌─────┬─────┐
│Worl │dSpit│
├─────┼─────┤
│Wdlit│orSp │
├─────┼─────┤
│Wpit │ordSl│
├─────┼─────┤
│ordSl│Wpit │
├─────┼─────┤
│orSp │Wdlit│
├─────┼─────┤
│dSpit│Worl │
└─────┴─────┘
randomra
fonte
0

Minha solução está abaixo. É um tamanho quase anti-golfe, mas funciona muito bem. Ele suporta divisões n-way (embora o tempo computacional se torne muito longo para mais de três divisões) e suporta a remoção de duplicatas.

class WordSplitChecker(object):
    def __init__(self, word, splits=2):
        if len(word) == 0:
            raise ValueError, "word too short!"
        if splits == 0:
            raise ValueError, "splits must be > 1; it is impossible to split a word into zero groups"
        self.word = word
        self.splits = splits

    def solve(self, uniq_solutions=False, progress_notifier=True):
        """To solve this problem, we first need to consider all the possible
        rearrangements of a string into two (or more) groups.

        It turns out that this reduces simply to a base-N counting algorithm,
        each digit coding for which group the letter goes into. Obviously
        the longer the word the more digits needed to count up to, so 
        computation time is very long for larger bases and longer words. It 
        could be sped up by using a precalculated array of numbers in the
        required base, but this requires more memory. (Space-time tradeoff.)

        A progress notifier may be set. If True, the default notifier is used,
        if None, no notifier is used, and if it points to another callable,
        that is used. The callable must take the arguments as (n, count, 
        solutions) where n is the number of iterations, count is the total 
        iteration count and solutions is the length of the solutions list. The
        progress notifier is called at the beginning, on every 1000th iteration, 
        and at the end.

        Returns a list of possible splits. If there are no solutions, returns
        an empty list. Duplicate solutions are removed if the uniq_solutions
        parameter is True."""
        if progress_notifier == True:
           progress_notifier = self.progress 
        solutions = []
        bucket = [0] * len(self.word)
        base_tuple = (self.splits,) * len(self.word)
        # The number of counts we need to do is given by: S^N,
        # where S = number of splits,
        #       N = length of word.
        counts = pow(self.splits, len(self.word))
        # xrange does not create a list in memory, so this will work with very
        # little additional memory.
        for i in xrange(counts):
            groups = self.split_word(self.word, self.splits, bucket)
            group_sums = map(self.score_string, groups)
            if len(set(group_sums)) == 1:
                solutions.append(tuple(groups))
            if callable(progress_notifier) and i % 1000 == 0:
                progress_notifier(i, counts, len(solutions))
            # Increment bucket after doing each group; we want to include the
            # null set (all zeroes.)
            bucket = self.bucket_counter(bucket, base_tuple)
        progress_notifier(i, counts, len(solutions))
        # Now we have computed our results we need to remove the results that
        # are symmetrical if uniq_solutions is True.
        if uniq_solutions:
            uniques = []
            # Sort each of the solutions and turn them into tuples.  Then we can 
            # remove duplicates because they will all be in the same order.
            for sol in solutions:
                uniques.append(tuple(sorted(sol)))
            # Use sets to unique the solutions quickly instead of using our
            # own algorithm.
            uniques = list(set(uniques))
            return sorted(uniques)
        return sorted(solutions)

    def split_word(self, word, splits, bucket):
        """Split the word into groups. The digits in the bucket code for the
        groups in which each character goes in to. For example,

        LIONHEAD with a base of 2 and bucket of 00110100 gives two groups, 
        "LIHAD" and "ONE"."""
        groups = [""] * splits
        for n in range(len(word)):
            groups[bucket[n]] += word[n]
        return groups

    def score_string(self, st):
        """Score and sum the letters in the string, A = 1, B = 2, ... Z = 26."""
        return sum(map(lambda x: ord(x) - 64, st.upper()))

    def bucket_counter(self, bucket, carry):
        """Simple bucket counting. Ex.: When passed a tuple (512, 512, 512)
        and a list [0, 0, 0] it increments each column in the list until
        it overflows, carrying the result over to the next column. This could
        be done with fancy bit shifting, but that wouldn't work with very
        large numbers. This should be fine up to huge numbers. Returns a new
        bucket and assigns the result to the passed list. Similar to most
        counting systems the MSB is on the right, however this is an 
        implementation detail and may change in the future.

        Effectively, for a carry tuple of identical values, this implements a 
        base-N numeral system, where N+1 is the value in the tuple."""
        if len(bucket) != len(carry):
            raise ValueError("bucket and carry lists must be the same size")
        # Increase the last column.
        bucket[-1] += 1 
        # Carry numbers. Carry must be propagated by at least the size of the
        # carry list.
        for i in range(len(carry)):
            for coln, col in enumerate(bucket[:]):
                if col >= carry[coln]:
                    # Reset this column, carry the result over to the next.
                    bucket[coln] = 0
                    bucket[coln - 1] += 1
        return bucket

    def progress(self, n, counts, solutions):
        """Display the progress of the solve operation."""
        print "%d / %d (%.2f%%): %d solutions (non-unique)" % (n + 1, counts, (float(n + 1) / counts) * 100, solutions) 

if __name__ == '__main__':
    word = raw_input('Enter word: ')
    groups = int(raw_input('Enter number of required groups: '))
    unique = raw_input('Unique results only? (enter Y or N): ').upper()
    if unique == 'Y':
        unique = True
    else:
        unique = False
    # Start solving.
    print "Start solving"
    ws = WordSplitChecker(word, groups)
    solutions = ws.solve(unique)
    if len(solutions) == 0:
        print "No solutions could be found."
    for solution in solutions:
        for group in solution:
            print group,
        print

Saída de amostra:

Enter word: wordsplit
Enter number of required groups: 2
Unique results only? (enter Y or N): y
Start solving
1 / 512 (0.20%): 0 solutions (non-unique)
512 / 512 (100.00%): 6 solutions (non-unique)
dspit worl
ordsl wpit
orsp wdlit
Thomas O
fonte
11
Na minha opinião, respostas que reconhecidamente não fazem nenhuma tentativa de atingir o objetivo da pergunta original (brevidade do código) são efetivamente fora de tópico. Você admite que esta resposta não tem relação com o código-golfe, em vez de publicá-la como resposta, sugiro publicá-la em outro lugar e colocar um link para ela em um comentário sobre a pergunta.
Jeff Swensen
2
@Sugerman: É uma implementação de referência, não uma tentativa de ganhar o jogo. Prefiro implementações de referência como respostas, em vez de ocupar espaço na parte superior da página, e prefiro-as no local para remover o risco de podridão do link.
dmckee --- ex-moderador gatinho
@Sugerman: Por que não enviá-lo? É melhor que nada. Pode ser minimizado, mas por que se preocupar - eu realmente não posso aceitar a minha própria pergunta (bem, eu posso , mas não é no espírito da coisa.)
Thomas O
Porque, na base, este é um site de perguntas e respostas. Algo que não pretende ser uma resposta não deve ser postado como tal.
Jeff Swensen
11
Como eu disse no meu primeiro comentário, eu teria vinculado a ele em um comentário sobre a pergunta ou, como você é o proprietário da pergunta, edite o link lá. Também não há nada de errado em enviar uma resposta para sua própria pergunta, desde que você não aceite automaticamente sua própria resposta sem considerar todas as outras (e resultados da votação).
Jeff Swensen
0

Lua - 195

a=io.read"*l"for i=0,2^#a/2-1 do z,l=0,""r=l for j=1,#a do b=math.floor(i/2^j*2)%2 z=(b*2-1)*(a:byte(j)-64)+z if b>0 then r=r..a:sub(j,j)else l=l..a:sub(j,j)end end if z==0 then print(l,r)end end

a entrada deve estar em maiúsculas:

~$ lua wordsplit.lua 
>WORDSPLIT
WDLIT   ORSP
DSPIT   WORL
WPIT    ORDSL
mniip
fonte
0

Python - 127

w=rawinput()
for q in range(2**len(w)/2):
 a=[0]*2;b=['']*2
 for c in w:a[q%2]+=ord(c)-96;b[q%2]+=c;q/=2
 if a[0]==a[1]:print b

e aqui uma versão n-split com 182 bytes sem duplicatas:

n,w=input()
def b(q):
 a=[0]*n;b=['']*n
 for c in w:a[q%n]+=ord(c)-96;b[q%n]+=c;q/=n
 return a[0]==a[1] and all(b) and frozenset(b)
print set(filter(None,map(b,range(n**len(w)/n))))

A entrada é por exemplo:

3, 'wordsplit'
Daniel
fonte