2010-04-05 12 views
11

In Python, posso fare questo:Come si ottiene l'insieme di tutte le lettere in Java/Clojure?

>>> import string 
>>> string.letters 
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 

Esiste un modo per fare qualcosa di simile in Clojure (a parte copiando e incollando i caratteri sopra da qualche parte)? Ho esaminato sia la libreria standard Clojure che la libreria standard Java e non sono riuscita a trovarla.

+6

Considererei del tutto inesatto il risultato del pitone. Che dire di ä, é o ß? –

+4

Suppongo che sarebbe d'aiuto se fornissi la documentazione su 'string.letters' di python: http://docs.python.org/library/string.html#string.lettere È basato su ASCII, quindi il valore restituito dipende dal sistema. –

+4

come fa "il valore dipende dal sistema" da "È basato su ASCII"? ASCII è ben definito ed è esattamente lo stesso su ogni sistema. Quindi, se fosse veramente basato su ASCII, il risultato sarebbe sempre lo stesso. "ASCII" non è anche "qualsiasi codifica di caratteri a 8 bit". –

risposta

13

Un'implementazione correttamente non ASCII-centric:

private static String allLetters(String charsetName) 
{ 
    CharsetEncoder ce = Charset.forName(charsetName).newEncoder(); 
    StringBuilder result = new StringBuilder(); 
    for(char c=0; c<Character.MAX_VALUE; c++) 
    { 
     if(ce.canEncode(c) && Character.isLetter(c)) 
     { 
      result.append(c); 
     } 
    } 
    return result.toString(); 
} 

Chiamalo con "US-ASCII" e otterrai il risultato desiderato (tranne che le lettere maiuscole vengono prima). Potresti chiamarlo con Charset.defaultCharset(), ma sospetto che otterresti molto più delle lettere ASCII sulla maggior parte dei sistemi, anche negli Stati Uniti.

Avvertenza: considera solo il piano multilingue di base. Non sarebbe troppo difficile estenderlo agli aerei supplementari, ma richiederebbe molto più tempo e l'utilità è discutibile.

+3

Character.isLetter (char) è superiore uppercases e in minuscolo: Un carattere è considerata una lettera se il suo tipo categoria generale, fornito da Character.getType (ch), è uno dei seguenti: * UPPERCASE_LETTER * LOWERCASE_LETTER * TITLECASE_LETTER * MODIFIER_LETTER * OTHER_LETTER Non tutte le lettere hanno una custodia. Molti caratteri sono lettere ma non sono né maiuscoli né minuscoli né titolabili. –

+0

Solo un piccolo trucco: puoi scrivere la condizione 'for' in questo modo' for (char c = 1; c> 0; C++) '. – Elist

1

Sono abbastanza sicuro che le lettere non siano disponibili nella libreria standard, quindi probabilmente vi rimarrà l'approccio manuale.

5

No, perché si tratta semplicemente di stampare le lettere ASCII anziché il set completo. Ovviamente, è banale stampare le 26 lettere minuscole e maiuscole usando due cicli di ripetizione, ma il fatto è che ci sono molte più "lettere" al di fuori dei primi 127 punti di codice. Java "isLetter" fn su Character sarà vero per questi e molti altri.

+0

Questo è un punto eccellente, ma non sono terribilmente preoccupato per l'unicode adesso. Detto questo, suppongo che potrei semplicemente usare l'approccio manuale. Non è che l'alfabeto rischi di cambiare presto. :-) –

+1

@Jason: la lettera "Capitale ß" è entrata nello standard Unicode nel 2008! E questa è una lettera dell'alfabeto latino! (Certo, è usato molto raramente, ma ancora: nemmeno gli alfabeti sono al riparo dai cambiamenti). –

1

Lo stesso risultato come detto nella sua domanda sarebbe stata data dalla seguente dichiarazione che deve essere generato manualmente in contrasto con la soluzione di Python:

public class Letters { 

    public static String asString() { 
     StringBuffer buffer = new StringBuffer(); 
     for (char c = 'a'; c <= 'z'; c++) 
      buffer.append(c); 
     for (char c = 'A'; c <= 'Z'; c++) 
      buffer.append(c); 
     return buffer.toString(); 
    } 

    public static void main(String[] args) { 
     System.out.println(Letters.asString()); 
    } 

} 
3

string.letters: La concatenazione delle stringhe minuscole e maiuscole descritto di seguito. Il valore specifico è dipendente dalla locale e verrà aggiornato quando viene chiamato locale.setlocale().

Ho modificato la risposta da Michael Borgwardt. Nella mia applicazione ci sono due liste in minuscolo e upperCases per due motivi:

  1. string.letters è in minuscolo seguita da uppercases.

  2. Java Character.isLetter(char) è molto più di uppercases e in minuscolo, per cui l'uso di Character.isLetter (char) tornerà a risultati molto sotto alcuni set di caratteri, ad esempio "windows-1252"

Da Api-Doc: Character.isLetter(char):

un carattere è considerato essere una lettera se il suo tipo categoria generale, fornito da Character.getType (ch), è uno dei seguenti:

* UPPERCASE_LETTER 
* LOWERCASE_LETTER 
* TITLECASE_LETTER 
* MODIFIER_LETTER 
* OTHER_LETTER 

Non tutte le lettere hanno una custodia.Molti caratteri sono lettere ma non sono né maiuscole né minuscole né titolate.

Quindi, se string.letters deve restituire solo in minuscolo e uppercases, il TITLECASE_LETTER, , MODIFIER_LETTER e OTHER_LETTER caratteri devono essere ignorato.

public static String allLetters(final Charset charset) { 
    final CharsetEncoder encoder = charset.newEncoder(); 
    final StringBuilder lowerCases = new StringBuilder(); 
    final StringBuilder upperCases = new StringBuilder(); 
    for (char c = 0; c < Character.MAX_VALUE; c++) { 
    if (encoder.canEncode(c)) { 
    if (Character.isUpperCase(c)) { 
    upperCases.append(c); 
    } else if (Character.isLowerCase(c)) { 
    lowerCases.append(c); 
    } 
    } 
    } 
    return lowerCases.append(upperCases).toString(); 
} 

Inoltre: il comportamento di string.letters cambia quando si cambia l'impostazione internazionale. Questo forse non si applica alla mia soluzione, perché la modifica delle impostazioni internazionali predefinite non modifica il set di caratteri predefinito. Da apidoc:

Il set di caratteri predefinito viene determinato all'avvio macchine virtuali e tipicamente dipende dalla localizzazione e charset del sottostante sistema operativo .

Immagino che il set di caratteri predefinito non possa essere modificato all'interno della JVM avviata. Quindi il comportamento di "modifica locale" di string.letters non può essere realizzato con solo Locale.setDefault (Locale). Ma cambiare le impostazioni internazionali di default è comunque una cattiva idea:

Dal cambiare l'impostazione internazionale predefinita può interessare diverse aree di funzionalità, questo metodo dovrebbe essere utilizzato solo se il chiamante è pronto a reinizializzare locale-sensitive codice in esecuzione nella stessa macchina Java Virtual .

20

Se si desidera solo Ascii caratteri,

(map char (concat (range 65 91) (range 97 123))) 

si produrrà,

(\A \B \C \D \E \F \G \H \I \J \K \L \M \N \O \P \Q \R \S \T \U \V \W \X \Y \Z 
\a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z) 
+2

+1 Non c'è bisogno di racchiudere la chiamata in char in una funzione anonima, '(map char (concat (range 65 91) (range 97 123)))' funzionerà bene. – Jonas

6

Sulla base di soluzione imperativo Java Michaels, questo è un idiomatiche (sequenze pigri) soluzione Clojure:

(ns stackoverflow 
    (:import (java.nio.charset Charset CharsetEncoder))) 

(defn all-letters [charset] 
    (let [encoder (. (Charset/forName charset) newEncoder)] 
    (letfn [(valid-char? [c] 
      (and (.canEncode encoder (char c)) (Character/isLetter c))) 
     (all-letters-lazy [c] 
        (when (<= c (int Character/MAX_VALUE)) 
       (if (valid-char? c) 
        (lazy-seq 
        (cons (char c) (all-letters-lazy (inc c)))) 
        (recur (inc c)))))] 
     (all-letters-lazy 0)))) 

Aggiornamento: Grazie cgrand per questa soluzione di alto livello preferibile:

(defn letters [charset-name] 
    (let [ce (-> charset-name java.nio.charset.Charset/forName .newEncoder)] 
    (->> (range 0 (int Character/MAX_VALUE)) (map char) 
     (filter #(and (.canEncode ce %) (Character/isLetter %)))))) 

Ma il confronto performace tra il mio primo approccio

user> (time (doall (stackoverflow/all-letters "ascii"))) 
"Elapsed time: 33.333336 msecs"             
(\A \B \C \D \E \F \G \H \I \J \K \L \M \N \O \P \Q \R \S \T \U \V \W \X \Y \Z \\ 
a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z) 

e la soluzione

user> (time (doall (stackoverflow/letters "ascii"))) 
"Elapsed time: 666.666654 msecs"             
(\A \B \C \D \E \F \G \H \I \J \K \L \M \N \O \P \Q \R \S \T \U \V \W \X \Y \Z \\ 
a \b \c \d \e \f \g \h \i \j \k \l \m \n \o \p \q \r \s \t \u \v \w \x \y \z) 

è molto interessante .

+1

Idiomatic lazy seq fns usa scarsamente pigro-seq: lazy-seq è di basso livello. Il nucleo del tuo codice è scritto meglio come: (- >> (intervallo 0 (int Character/MAX_VALUE)) (map char) (filtro # (e (.canEncode ce%) (Character/isLetter%))) vedi http: //gist.github.com/357407. Un'altra cosa: .. e .. sono in qualche modo legacy quindi non li uso – cgrand

+0

Grazie! Perché "." e ".." sono considerati legacy? Qualsiasi risorsa? –

+1

-> è un migliore ... dato che si mischiano fn e metodi (in notazione .method), quindi .. non ha alcun interesse se non salvarti un punto per ogni chiamata al metodo (e rendendo meno facile individuarli quando vai a suggerire il testo). metodo obj) è più lispy mettendo il metodo in posizione di funzione Analogamente preferisce Foo a (nuovo Foo) Dai una prova alle forme zuccherate (.foo, Foo. e Foo/BAR) e vedrai che sono molto più bello da usare (e consentire una fattorizzazione più semplice in seguito) – cgrand

1

Nel caso in cui non si ricordino intervalli di punti di codice. Modo forza bruta :-P:

user> (require '[clojure.contrib.str-utils2 :as stru2]) 
nil 
user> (set (stru2/replace (apply str (map char (range 0 256))) #"[^A-Za-z]" "")) 
#{\A \a \B \b \C \c \D \d \E \e \F \f \G \g \H \h \I \i \J \j \K \k \L \l \M \m \N \n \O \o \P \p \Q \q \R \r \S \s \T \t \U \u \V \v \W \w \X \x \Y \y \Z \z} 
user>