Divisez un mot en parties avec des scores égaux


9

En supposant A = 1, B = 2 ... Z = 26, et la valeur d'un mot est la somme de ces valeurs de lettre, il est possible de diviser certains mots en deux morceaux de sorte qu'ils aient des valeurs égales.

Par exemple, "wordsplit" peut être divisé en deux morceaux comme ceci: ordsl wpit, car o + r + d + s + l = w + p + i + t.

C'était un défi que mon professeur d'informatique nous avait lancé - c'est apparemment un vieux défi de Lionhead Studios. Je l'ai résolu en Python et posterai ma réponse sous peu.

Défi: Le programme le plus court qui peut répertorier toutes les divisions possibles qui ont des scores égaux. Notez qu'il ne doit en lister qu'un pour chaque groupe de lettres - ordsl wpit est le même que rdosl wtip, par exemple. Il est plus facile de les énumérer dans l'ordre où ils apparaissent dans le mot.

Prime:

  • Si vous mettez en surbrillance des paires où les deux mots sont des mots anglais valides (ou une permutation des lettres), utilisez une liste de mots. (Cela pourrait être fait en plaçant un astérisque à côté de chaque méthode ou d'une autre, mais clarifiez-le.)
  • Ajout de l'option de suppression des doublons (ce ne devrait pas être la valeur par défaut.)
  • Prenant en charge plus de deux divisions, par exemple, trois, quatre ou même divisions à n.

Le programme doit-il prendre en charge la saisie de cas mixtes? Et si oui, peut-il rejeter le boîtier pour la sortie?
Nemo157

@ Nemo157 Il peut ignorer la casse et n'a pas à le conserver sur la sortie.
Thomas O

Le programme peut-il produire des éléments supplémentaires, tant que la partie demandée de la sortie est claire pour l'homme?
JB

@JB Oui, c'est possible.
Thomas O

ok, je vais améliorer ce Perl alors;) Merci
JB

Réponses:


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

Courez avec perl -nE '<code goes here>'. Ce «n» est compté dans la taille du code.

Repensé:

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

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

  say ""
}

Avec des commentaires et des noms de variables:

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

Certaines des astuces utilisées:

  • 1..1<<@_ couvre la même plage de bits que 0..(1<<@_)-1 , mais est plus court. (notez que le fait de considérer le problème de plus loin, y compris les limites de plage plusieurs fois, n'entraînerait pas une mauvaise sortie de toute façon)
  • $ left_range et $ right_range ne sont pas réinitialisés au zéro numérique "0" réel: puisque nous les accumulons et les comparons à la fin, tout ce dont nous avons besoin est qu'ils commencent à la même valeur.
  • soustraire 64-ord$_[$_]au lieu d'ajouter ajoute ord$_[$_]-64un caractère invisible: puisqu'il se termine par un délimiteur, il rend l'espace avant forinutile.
  • Perl vous permet d' assigner à une variable déterminée par l'opérateur conditionnel ternaire: cond ? var1 : var2 = new_value.
  • les expressions booléennes sont chaînées avec &&et ||sont utilisées à la place des conditions appropriées.
  • $l-$r est plus court que $l!=$r
  • affichera une nouvelle ligne même sur des divisions qui ne s'équilibrent pas. Les lignes vides sont ok selon les règles! J'ai demandé!

Voulez-vous expliquer à ceux d'entre nous qui ne parlent pas le bruit de ligne? Il semble que vous utilisiez une approche de masque binaire similaire à la mienne, et je vois les 64 moyens '@' = 'A' - 1, et après cela, je suis à peu près perdu.
dmckee --- chaton ex-modérateur

Cette modification est-elle meilleure?
JB

Agréable. Je dois penser à profiter de l'ajout de chaque compte à gauche ou à droite. Cela aurait dû être évident, mais je l'ai raté.
dmckee --- chaton ex-modérateur

3

J (109)

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

Sortie pour wordsplit:

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

Explication:

  • 1!:1[1: lire une ligne de stdin
  • ([{~(i.@!A.i.)@#): obtenez toutes les permutations
  • "1: pour chaque permutation:
  • (96-~a.&i.): obtenir les scores des lettres
  • }.@(split~&.>i.@#@>)@<: divise chaque permutation des scores à chaque espace possible, sauf avant le premier et après le dernier nombre
  • >(>@(=/@:(+/"1&>)&.>)#[): voyez quelles permutations ont des moitiés correspondantes et sélectionnez-les
  • {&a.@(96&+)&.>: retransformez les partitions en lettres
  • ~.(/:{[): supprimer les variations triviales (par exemple ordsl wpitet ordsl wpti)

Certaines de vos réponses sont des doublons.
DavidC

@DavidCarraher: Eh bien, soit je suis aveugle, soit celui-ci ne l'est pas, ni mes réponses récentes. Je n'ai jamais copié les réponses d'autres personnes exprès, bien que vous puissiez bien sûr avoir raison, j'ai posté ici en état d'ivresse parfois et je ne me souviens pas tant que je n'ai pas reçu beaucoup de votes négatifs et il s'est avéré que j'avais soumis quelque chose qui n'était même pas près de corriger. Si vous m'avez vu mal se comporter, laissez peut-être le commentaire là où je me conduis mal, alors je supprimerai toutes les réponses offensantes; ou peut-être les downvote car c'est à cela que servent les downvotes.
marinus

Aucun léger n'était prévu. Je voulais simplement dire, par exemple, que votre première réponse, {"lorw", "dipst"} est un double de votre réponse finale, {"dipst", "lorw"}. Seul l'ordre des mots est différent.
DavidC

@DavidCarraher: whoops: PI pensait que vous vouliez dire que j'avais copié la réponse de quelqu'un ... de toute façon cette question dit (si j'ai bien interprété) de supprimer les doublons où les parties individuelles ne sont que des permutations les unes des autres, mais pas de supprimer celles où l'ordre des pièces est différent, c'est-à-dire si {a,bc}est déjà trouvé, à supprimer {a,cb}mais pas à supprimer {bc,a}. (Et bien sûr, je ne suis pas offensé, si j'ai réellement / eu / dupliqué la réponse de quelqu'un, je le préférerais si quelqu'un le signalait.)
marinus

Vous semblez avoir raison. Les instructions indiquent qu'il est acceptable d'ignorer l'ordre des mots ("Notez qu'il ne doit en répertorier qu'un pour chaque groupe de lettres"), mais ils ne l'exigent pas. Cela peut me faire économiser quelques caractères. Merci.
DavidC

2

c99 - 379 caractères nécessaires

#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'approche est assez évidente. Il existe une fonction qui résume un mot selon un masque et une qui l'imprime également selon un masque. Entrée depuis l'entrée standard. Une particularité est que la routine d'impression insère des espaces pour la lettre non dans le masque. Un onglet est utilisé pour séparer les groupes.

Je ne fais aucun des objets bonus, et il n'est pas facilement converti pour les prendre en charge.

Lisible et commenté:

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

Validation

 $ 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

Ruby: 125 caractères

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

Exemple d'exécution:

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

Trouve tous les sous - ensembles de mots qui ont la moitié « total ascii » du mot, d. Trouve ensuite les compléments de ces sous-ensembles.

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 caractères

Utilisation des chiffres des nombres base2 pour sélectionner tous les sous-ensembles possibles.

   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

Ma solution est ci-dessous. C'est un anti-golf presque dans sa taille, mais cela fonctionne très bien. Il prend en charge les divisions à n voies (bien que le temps de calcul devienne très long pour plus de 3 divisions environ) et il prend en charge la suppression des doublons.

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

Exemple de sortie:

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
À mon avis, les réponses qui, certes, n'effectuent aucun effort pour atteindre l'objectif de la question d'origine (brièveté du code) sont effectivement hors sujet. Vous admettez que cette réponse n'a aucun rapport avec le code-golf, donc plutôt que de la poster comme réponse, je vous suggère de la poster ailleurs et de mettre un lien vers elle dans un commentaire sur la question.
Jeff Swensen

2
@Sugerman: C'est une implémentation de référence, pas une tentative de gagner le match. Je préfère les implémentations de référence comme réponses plutôt que de prendre de la place en haut de la page, et je les préfère sur place pour éliminer le risque de pourriture des liens.
dmckee --- chaton ex-modérateur

@Sugerman: Pourquoi ne pas le soumettre? C'est mieux que rien. Cela pourrait être minimisé, mais pourquoi s'embêter - je ne peux pas vraiment accepter ma propre question (enfin, je peux , mais ce n'est pas dans son esprit.)
Thomas O

Parce qu'à la base, c'est un site de questions et réponses. Quelque chose qui n'est pas destiné à répondre ne doit pas être affiché comme tel.
Jeff Swensen

1
Comme je l'ai dit dans mon premier commentaire, je l'aurais lié à un commentaire sur la question ou puisque vous êtes propriétaire de la question, modifiez le lien là-dedans. Il n'y a également rien de mal à soumettre une réponse à votre propre question tant que vous n'acceptez pas automatiquement votre propre réponse sans prendre en compte toutes les autres (et les résultats du vote).
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'entrée doit être en majuscules:

~$ 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

et ici une version n-split avec 182 octets sans doublons:

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'entrée est par exemple:

3, 'wordsplit'
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.