Dividi una parola in parti con punteggi uguali


9

Supponendo A = 1, B = 2 ... Z = 26, e il valore di una parola è la somma di questi valori di lettere, è possibile dividere alcune parole in due pezzi in modo che abbiano valori uguali.

Ad esempio, "wordsplit" può essere suddiviso in due parti in questo modo: ordsl wpit, perché o + r + d + s + l = w + p + i + t.

Questa è stata una sfida che ci è stata data dal mio insegnante di informatica - a quanto pare è una vecchia sfida dei Lionhead Studios. L'ho risolto in Python e pubblicherò la mia risposta a breve.

Sfida: il programma più breve che può elencare tutte le possibili divisioni che hanno punteggi uguali. Nota che deve elencarne solo uno per ciascun gruppo di lettere - ordsl wpit è lo stesso di rdosl wtip, ad esempio. È più facile elencarli nell'ordine in cui arrivano nella parola.

Bonus:

  • Se si evidenziano le coppie in cui entrambe le parole sono parole inglesi valide (o una permutazione delle lettere è), utilizzare un elenco di parole di qualche tipo. (Questo potrebbe essere fatto posizionando un asterisco accanto a ciascuno o qualche altro metodo, ma chiariscilo.)
  • Aggiunta dell'opzione per la rimozione dei duplicati (questo non dovrebbe essere il valore predefinito).
  • Supporta più di due divisioni, ad esempio tre, quattro o addirittura divisioni n-way.

Il programma deve supportare input di casi misti? E se è così, può scartare il caso dell'output?
Nemo157,

@ Nemo157 Potrebbe ignorare il caso e non è necessario conservarlo sull'output.
Thomas O

Il programma può produrre materiale extra, a condizione che la parte richiesta dell'output sia chiara per un essere umano?
JB

@JB Sì, può.
Thomas O

ok, migliorerò quel Perl allora;) Grazie
JB

Risposte:


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""}

Corri con perl -nE '<code goes here>'. Quella 'n' viene contata nella dimensione del codice.

Respaced:

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

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

  say ""
}

Con commenti e nomi di variabili:

# 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 ""
}

Alcuni dei trucchi utilizzati:

  • 1..1<<@_copre lo stesso intervallo di bit di 0..(1<<@_)-1, ma è più breve. (si noti che considerando il problema da più lontano, inclusi i limiti di intervallo più volte, non si otterrebbe comunque un risultato errato)
  • $ left_range e $ right_range non vengono reimpostati allo zero numerico "0" reale: poiché li accumuliamo e confrontiamo alla fine, tutto ciò di cui abbiamo bisogno è che inizino con lo stesso valore.
  • sottraendo 64-ord$_[$_]invece di aggiungere ord$_[$_]-64vince un carattere invisibile: poiché termina con un delimitatore, rende lo spazio prima forinutile.
  • Perl consente di assegnare a una variabile determinata dalla operatore condizionale ternario: cond ? var1 : var2 = new_value.
  • espressioni booleane concatenate &&e ||utilizzate al posto di propri condizionali.
  • $l-$r è più corto di $l!=$r
  • produrrà una nuova riga anche su split che non si bilanciano. Le righe vuote sono ok per le regole! Ho chiesto!

Vuoi spiegare per quelli di noi che non parlano il rumore di linea? Sembra che tu stia usando un approccio di maschera binaria simile al mio, e vedo che 64 significa '@' = 'A' - 1, e dopo che mi sono praticamente perso.
dmckee --- ex gattino moderatore

Questa modifica è migliore?
JB

Bello. Devo pensare a trarre vantaggio dall'aggiunta di ogni conteggio a sinistra o con la somma giusta. Avrebbe dovuto essere ovvio, ma l'ho perso.
dmckee --- ex gattino moderatore

3

J (109)

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

Uscita per wordsplit:

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

Spiegazione:

  • 1!:1[1: leggi una riga da stdin
  • ([{~(i.@!A.i.)@#): ottieni tutte le permutazioni
  • "1: per ogni permutazione:
  • (96-~a.&i.): ottieni i punteggi delle lettere
  • }.@(split~&.>i.@#@>)@<: dividere ogni permutazione dei punteggi in ogni spazio possibile, tranne prima del primo e dopo l'ultimo numero
  • >(>@(=/@:(+/"1&>)&.>)#[): vedi quali permutazioni hanno le metà corrispondenti e selezionale
  • {&a.@(96&+)&.>: trasforma i punteggi in lettere
  • ~.(/:{[): rimuovi banali variazioni (es. ordsl wpite ordsl wpti)

Alcune delle tue risposte sono duplicate.
DavidC,

@DavidCarraher: Beh, o sono cieco o questo non lo è, né le mie risposte recenti. Non ho mai copiato le risposte di altre persone di proposito, anche se ovviamente potresti avere ragione, ho pubblicato qui mentre ero ubriaco a volte e non ricordato fino a quando non ho ricevuto un sacco di voti negativi e si è scoperto che avevo inviato qualcosa che non era nemmeno vicino a correggere. Se mi hai visto comportarsi male, forse lascia il commento in cui mi sto comportando male, quindi eliminerò tutte le risposte offensive; o forse li ha declassati poiché è a questo che servono i downvotes.
Marin

Non era previsto alcun leggero. Intendevo semplicemente, ad esempio, che la tua prima risposta, {"lorw", "dipst"} è un duplicato della tua risposta finale, {"dipst", "lorw"}. Solo l'ordine delle parole è diverso.
DavidC,

@DavidCarraher: whoops: PI pensavo intendessi che avrei copiato la risposta di qualcuno ... comunque questa domanda dice (se l'ho interpretata bene) per rimuovere i duplicati in cui le singole parti sono solo permutazioni l'una dell'altra, ma non per rimuovere quelli in cui l'ordine delle parti è diverso, ovvero se {a,bc}è già stato trovato, da rimuovere {a,cb}ma non da rimuovere {bc,a}. (E ovviamente non mi offendo, se davvero / avessi / duplicato la risposta di qualcuno, lo preferirei se qualcuno lo segnalasse.)
marinus

Sembra che tu abbia ragione. Le istruzioni dicono che va bene ignorare l'ordine delle parole ("Nota che deve elencarne solo una per ogni gruppo di lettere"), ma non lo richiedono. Questo potrebbe salvarmi alcuni personaggi. Grazie.
DavidC,

2

c99 - 379 caratteri necessari

#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);}}}

L'approccio è abbastanza ovvio. C'è una funzione che somma una parola secondo una maschera e una che la stampa anche secondo una maschera. Input dall'input standard. Una stranezza è che la routine di stampa inserisce spazi per la lettera non nella maschera. Una scheda viene utilizzata per separare i gruppi.

Non faccio nessuno degli oggetti bonus, né viene facilmente convertito per supportarli.

Leggibile e commentato:

#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) */
  }
}

Validazione

 $ 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   

1

Rubino: 125 caratteri

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}}

Esecuzione di esempio:

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"]]

1

Mathematica 123 111

Trova tutti i sottogruppi della parola che hanno 1/2 del "ascii totale" della parola, d. Quindi trova i complementi di tali sottoinsiemi.

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"}}


1

J, 66 caratteri

Usando le cifre dei numeri base2 per selezionare ogni possibile sottoinsieme.

   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 │
└─────┴─────┘

0

La mia soluzione è sotto. È quasi un anti-golf nelle sue dimensioni, ma funziona molto bene. Supporta divisioni n-way (sebbene il tempo di calcolo diventi molto lungo per non più di circa 3 divisioni) e supporta la rimozione di duplicati.

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

Uscita campione:

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

1
A mio avviso, le risposte che ammettono senza alcun tentativo di raggiungere l'obiettivo della domanda originale (brevità del codice) sono effettivamente fuori tema. Ammetti che questa risposta non ha alcuna relazione con il code-golf, quindi piuttosto che pubblicarla come risposta, suggerirei di pubblicarla da qualche altra parte e di inserire un link ad essa in un commento sulla domanda.
Jeff Swensen,

2
@Sugerman: è un'implementazione di riferimento, non un tentativo di vincere la partita. Preferisco le implementazioni di riferimento come risposte piuttosto che occupare spazio nella parte superiore della pagina e le preferisco sul posto per rimuovere il rischio di marciume dei link.
dmckee --- ex gattino moderatore

@Sugerman: Perché non inviarlo? Meglio di niente. Potrebbe essere ridotto al minimo, ma perché preoccuparsi - non posso davvero accettare la mia domanda (bene, posso , ma non è nello spirito di esso.)
Thomas O

Perché alla base di questo, questo è un sito di domande e risposte. Qualcosa che non è inteso come una risposta non dovrebbe essere pubblicato come tale.
Jeff Swensen,

1
Come ho detto nel mio primo commento, mi sarei collegato ad esso in un commento sulla domanda o poiché possiedi la domanda modifica il link lì dentro. Inoltre, non c'è nulla di sbagliato nell'inviare una risposta alla propria domanda, purché non si accetti automaticamente la propria risposta senza considerare tutti gli altri (e i risultati della votazione).
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

l'input deve essere in maiuscolo:

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

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 qui una versione n-split con 182 byte senza duplicati:

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))))

L'input è ad es .:

3, 'wordsplit'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.