ACHTUNG. Das ist ein Archiv des alten forum.ruby-portal.de. Die aktuelle Mailingliste gibt es auf lists.ruby-lang.org/pipermail/ruby-de.

NOTICE. This is a ready-only copy of the old forum.ruby-portal.de. You can find the current mailing list at lists.ruby-lang.org/pipermail/ruby-de.

Die Programmiersprache Ruby

Blog|

Forum|

Wiki  


Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]

Ein neues Thema erstellen Auf das Thema antworten  [ 8 Beiträge ] 
Autor Nachricht
 Betreff des Beitrags: RubyDictionary
BeitragVerfasst: 31 Mär 2010, 00:04 
Offline
Nuby

Registriert: 30 Mär 2010, 23:32
Beiträge: 4
Hallo,

Ich habe mich in der letzten Woche intensiv mit Ruby befasst (genauer gesagt mit The Ruby Programming Language, super Buch) und habe mich heute hingesetzt, um zum ersten Mal eine eigene kleine Anwendung zu schreiben.

Die Idee dahinter war ein virtuelles Wörterbuch, das der Benutzer mittels Kommandozeilen bedienen kann. Mögliche Befehle sind zum Beispiel insert/remove (Datensatz einfügen/entfernen), search (suchen) oder save/load (Dictionary in eine Datei speichern oder aus einer Datei laden) - unter Help (h) sind alle aufgelistet.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
# Dictionary : storage for dictionary data
class Dictionary
ENTRY_DELIMITER = "|"

def initialize(entries=nil)
if entries==nil
@entries = Array.new
else
@entries = entries
end
end

# INSERTING
def insert_words(word1, word2)
# check if word
raise ArgumentError, "Variables are no words" unless (Dictionary::is_word?(word1) && Dictionary::is_word?(word2))

# check if pair already exists in Dictionary
raise ArgumentError, "Dictionary already contains this pair" if has_word_pair?(word1, word2)

# create entry instance and insert into arrays
@entries << DictionaryEntry.new(word1, word2)
end

def insert_dict_entry(entry)
insert_words(entry.word1, entry.word2) # any better solution?
end

def insert_array(words1, words2)
# arrays have to have same length
raise ArgumentError, "Invalid array arguments (not same length)" unless words1.length==words2.length

# iterate on words1 and try to insert every pair
words1.each_index {|x| insert_words(words1[x], words2[x])}
end

def insert_entries_string entries_string
entries_arr = entries_string.split(ENTRY_DELIMITER)
entries_arr.each {|entry_s| insert_dict_entry(DictionaryEntry.from_string(entry_s)) }
end

# REMOVING
# ... by pair
def remove(r_word1, r_word2)
_flag_passed = false

# checks if words arrays contain r_words
if index = has_word_pair?(r_word1, r_word2)
remove_at(index)
_flag_passed = true
end

raise ArgumentError, "Words not found in Dictionary" unless _flag_passed
end

# ... by index
def remove_at(index)
unless (index.is_a?(Integer) && index >= 0 && index <= @entries.length-1)
raise ArgumentError, "Invalid Index"
end

@entries.delete_at index
end

# SORTING
def sort_alphabetically
@entries.sort! do |entry1,entry2|
entry1.word1.downcase <=> entry2.word1.downcase
end
end

# SEARCHING
def search s_word
result = Array.new
@entries.each do |entry|
if (entry.word1.include?(s_word) || entry.word2.include?(s_word))
result << entry
end
end
result
end

# entries string
def create_entries_string
s = @entries.join(ENTRY_DELIMITER)
end

def clear
@entries.clear
end

# checks, if string is a "word"
# self or not self?
def self.is_word? word
if (word.match(/[a-zA-Z]{2,15}/)[0].length == word.length) then true
else false
end
# (word.match(/[a-zA-Z]{2,15}/)[0].length == word.length)
# easier way?
end

# returns index if word pair found, else nil
def has_word_pair?(word1, word2)
@entries.each_with_index do |value,index|
if value == [word1, word2]
return index
end
end
false
end

def to_s
s="Current state of Dictionary (#{self.object_id})\n"
@entries.length.times do |i|
s+=sprintf("%3d %15s | %15s", i, @entries[i].word1 , @entries[i].word2)+"\n"
end
s
end
end

# Represents a Dictionary Entry
class DictionaryEntry
# used for to_s
WORD_DELIMITER = ","

# init
def initialize(word1, word2)
@word1, @word2 = word1, word2
end

attr_accessor :word1, :word2

# equality comparison operator
def ==(other)
if (other.is_a?(Enumerable) && @word1 == other[0] && @word2 == other[1])
true
elsif (other.is_a?(DictionaryEntry) && @word1 == other.word1 && @word2 == other.word2) # not tested
puts "dict entry"
true
else
false
end
end

def to_s
s = @word1+WORD_DELIMITER+@word2
end

def to_console_s
s = sprintf("%15s | %15s", @word1 , @word2)+"\n"
end

# create DictionaryEntry out of a string
def self.from_string(param_string)
params = param_string.split(WORD_DELIMITER)
new(params[0], params[1])
end
end


def check_number_of_arguments! args, number
raise ArgumentError, "Wrong number of arguments" unless args.length==number
end

# Ruby Dictionary Main Program
dict = Dictionary.new

puts "Welcome to Ruby Dictonary!"
puts "Enter command (h for help):"

while c = gets.chomp # removes "\n" or "\t"
com = c.split(" ")

begin
case com[0]
# FILE I/O

# load
when "l", "load"
check_number_of_arguments!(com,2)

begin
file = File.new(com[1]+".txt", "r")
file_contents = file.readlines.join
file.close
rescue
puts "Failed to open File"
end

dict.insert_entries_string(file_contents)
puts "Opened"

# save
when "sv", "save"
check_number_of_arguments!(com,2)

perm = false
file_name = com[1]+".txt";

if (File.exists?(file_name) && !File.zero?(file_name)) # not tested
# ask for permission to overwrite file
puts "Overwrite File: #{file_name}? (Y/N)"
perm_get = gets.chomp
if perm_get =="Y"
perm = true
end
else
perm = true
end

if perm
file = File.new(file_name, "w")
file.write(dict.create_entries_string)
file.close

puts "Saved"
end

# ALTER/OUTPUT DATABASE

# insert
when "i", "insert"
check_number_of_arguments!(com,3)

dict.insert_words(com[1], com[2])

# remove
when "r", "remove"
# remove by finding the pair
if com.length==3
dict.remove(com[1], com[2])
# remove by index
elsif com.length==2
dict.remove_at(com[1].to_i)
else
raise ArgumentError, "Wrong number of arguments"
end

# sort
when "so", "sort"
check_number_of_arguments!(com,2)

case com[1]
when "abc"
dict.sort_alphabetically
puts "Alphabetically sorted"
else
raise ArgumentError, "#{com[1]} is not a sort type"
end

# search
when "s", "search"
check_number_of_arguments!(com,2)
results = dict.search(com[1])
results.each_with_index do |result, index|
puts sprintf("%3d ",index)+result.to_console_s
end

# output
when "o", "output"
puts dict

# MISC

# help
when "h", "help"
puts "Help:\n" +
"LOAD (loads dictionary out of a .txt file): [[i][load]] [file name without extension]\n" +
"SAVE (saves dictionary in a .txt file): [[sv][save]] [file name without extension]\n" +
"INSERT: [[i][insert]] [word1] [word2]\n" +
"REMOVE (removes data set from dictionary): [[r][remove]] [[index][data set]]\n" +
"SORT (sorts dictionary): [[so][sort]] [[abc]]\n" +
"SEARCH (searchs string in directory): [[s][search]] [search string]\n" +
"CLEAR (clears dictionary): [[c][clear]]\n" +
"OUTPUT (output of dictionary): [[o][output]]"

when "c", "clear"
dict.clear
puts "Dictionary cleared"

# quit
when "q", "quit"
puts "Quit"
break

# invalid
else
puts "Invalid command"
end
rescue ArgumentError => ex
puts ex.message
end
end


Gibt es Codestellen, an denen ich den Code hätte effizienter schreiben können? Ist unter dem Gesichtspunkt der OO-Programmierung etwas zu bemängeln? Wie siehts mit der "Kostemtik" - also mit Code-Stil / Kommentierung - aus? Im Code findet ihr außerdem auskommentierte Detailfragen, bei denen ich mir ebenfalls unsicher bin (Z.26; Z.93; Z.99;).

Ich wäre echt froh über ein paar Hilfestellungen,

Danke


Dateianhänge:
rdict.rb [6.77 KiB]
133-mal heruntergeladen
Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 31 Mär 2010, 00:23 
Offline
Interpreter
Benutzeravatar

Registriert: 03 Jul 2006, 14:53
Beiträge: 4872
Wohnort: RLP
Mir ists jetzt zu spät für ein tiefgreifendes Review, aber als Stilkritik:

In der Rubywelt verwendet man 2 Leerzeichen zum Einrücken von Code, keine 8. Das ist die weit verbreitetste Konvention und meist fällt es schwer, Code zu lesen, der anderes formatiert ist.

Gruß,
Skade


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 31 Mär 2010, 16:22 
Offline
Nuby

Registriert: 30 Mär 2010, 23:32
Beiträge: 4
Hallo,

Ich hab bisher immer den Tabulator zum Einrücken benutzt, aber danke für die Info. Freue mich schon auf die Stilkritik ;)

LG


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 31 Mär 2010, 21:15 
Offline
Interpreter
Benutzeravatar

Registriert: 03 Jul 2006, 14:53
Beiträge: 4872
Wohnort: RLP
Ich hab (nachdem ichs umformatiert habe) mal noch drüber geschaut und finds für den ersten Versuch eigentlich ganz gut, allerdings:



1
2
3
file = File.new(com[1]+".txt", "r")
file_contents = file.readlines.join
file.close


Das ist:




contents = File.read "#{com[1]}.txt"


Und:



1
2
3
file = File.new(file_name, "w")
file.write(dict.create_entries_string)
file.close


Das ist in idiomatischem Ruby:



1
2
3
File.open(file_name, "w") do |f|
f.write(dict.create_entries_string)
end


Das hat den Vorteil, dass man "close" nicht vergessen kann.

Weiter:

* dein Hauptprogramm steht in einer Klasse. Da gehört es aber nicht hin.
* der case im Hauptprogramm ist viel zu lang. Du solltest jeden einzelnen case in eine Methode fassen.

Das fällt mir jetzt so schnell noch ein.

Noch ein Tipp: für Kommandozeileninteraktion mit Menüs, Frage, etc. haben wir eine ganz tolle Bibliothek namens "Highline". Wenn du mal Lust hast, das etwas auszubauen, schau dir die als Übung mal an.

http://highline.rubyforge.org/

Gruß,
Skade


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 01 Apr 2010, 01:29 
Offline
Nuby

Registriert: 30 Mär 2010, 23:32
Beiträge: 4
Hallo,

Zitat:
* dein Hauptprogramm steht in einer Klasse. Da gehört es aber nicht hin.

Meinst du, dass man Klassen und das Hauptprogramm seperat halten soll oder verstehe ich dich falsch?

Zitat:
* der case im Hauptprogramm ist viel zu lang. Du solltest jeden einzelnen case in eine Methode fassen.

Das halte ich für übertrieben für durchschnittlich 8-Zeilen lange case-Blöcke - wenn ich deine Vorschläge bezüglich Laden/Speichern der Dateien berüchsichtige sind es sogar noch weniger...

Zitat:
http://highline.rubyforge.org/

Werd ich mir anschauen.

Danke und gute Nacht


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 01 Apr 2010, 09:35 
Offline
Interpreter
Benutzeravatar

Registriert: 03 Jul 2006, 14:53
Beiträge: 4872
Wohnort: RLP
bounded hat geschrieben:
Hallo,

Zitat:
* dein Hauptprogramm steht in einer Klasse. Da gehört es aber nicht hin.

Meinst du, dass man Klassen und das Hauptprogramm seperat halten soll oder verstehe ich dich falsch?[/code]


Das Problem ist, dass du die Klasse verwendest, bevor sie überhaupt fertig definiert ist! Auf dem Klassenlevel sollte einfach noch kein Runtimecode stehen.

Du könntest dein Hauptprogramm in einem Methode "run" fassen, und dann ganz unten "Dictionary.new.run".

Zitat:
Zitat:
* der case im Hauptprogramm ist viel zu lang. Du solltest jeden einzelnen case in eine Methode fassen.

Das halte ich für übertrieben für durchschnittlich 8-Zeilen lange case-Blöcke - wenn ich deine Vorschläge bezüglich Laden/Speichern der Dateien berüchsichtige sind es sogar noch weniger...


Das Problem ist, dass der Case ~100 Zeilen und damit locker länger als ein Bildschirm ist.
a) Man überblickt nicht einfach, was das Menü denn alles kann
b) Man kann in einer Unterklasse nicht mal eine Menüoperation überschreiben
c) Kein Codeeditor bietet Sprungpunkte an Case-Klauseln, nur an Methoden
d) Das Case-Konstrukt ist in objektorientierten Sprachen etwas schwierig, eben weil es kein Laufzeit-Repräsentation hat, im Gegensatz zu Methoden. Es gibt hier im Forum massen Threads zu genau dem Thema.

Abgesehen davon ist case natürlich selbst inhärent schlecht zu erweitern, daher dann auch solche Bibliotheken wie Highline.

Zu den 8 Zeilen: sehr häufig sind Methoden in Rubycode nicht viel länger. Das ist schon viel.

Gruß,
Skade


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 01 Apr 2010, 14:57 
Offline
ri
Benutzeravatar

Registriert: 02 Jun 2006, 23:18
Beiträge: 702
Hallo,

von mir gibt es auch keine umfassende Analyse, nur ein paar stilistische Sachen, die mir beim Überfliegen aufgefallen sind.



1
2
3
4
5
if entries==nil
@entries = Array.new
else
@entries = entries
end

Den Vergleich mit nil kann man auch mit entries.nil? durchführen, finde ich persönlich hübscher. Aber der gesamte Block lässt sich auch idiomatischer formulieren als


@entries = entries || Array.new

Allerdings ändert sich die Semantik dadurch etwas (nämlich wenn entries auf false gesetzt sein sollte).



1
2
3
4
5
6
7
8
9
10
11
12
13
# REMOVING
# ... by pair
def remove(r_word1, r_word2)
_flag_passed = false

# checks if words arrays contain r_words
if index = has_word_pair?(r_word1, r_word2)
remove_at(index)
_flag_passed = true
end

raise ArgumentError, "Words not found in Dictionary" unless _flag_passed
end

Wieso der Umstand mit dem Flag? Einfach das raise in den else-Zweig und gut ist.



1
2
3
4
5
6
7
8
9
def search s_word
result = Array.new
@entries.each do |entry|
if (entry.word1.include?(s_word) || entry.word2.include?(s_word))
result << entry
end
end
result
end

Das ist eine Aufgabe für Array#select:

1
2
3
4
5
def search(s_word)
@entries.select do |entry|
entry.word1.include?(s_word) || entry.word2.include?(s_word)
end
end

Man könnte auch überlegen ob man DictionaryEntry eine Methode

1
2
3
def matches?(word)
word1.include?(word) || word2.include?(word)
end

spendiert, die man dann in Dictionary#search benutzt.



1
2
3
4
5
6
7
8
9
# checks, if string is a "word"
# self or not self?
def self.is_word? word
if (word.match(/[a-zA-Z]{2,15}/)[0].length == word.length) then true
else false
end
# (word.match(/[a-zA-Z]{2,15}/)[0].length == word.length)
# easier way?
end

self ist schon okay hier. Ein Konstrukt der Form if x then true else false sollte man fast immer durch x ersetzen. Die Klammern kann man übrigens auch weglassen. Die Überprüfung lässt sich außerdem noch etwas eleganter formulieren:

1
2
3
def self.is_word? word
word =~ /^[a-zA-Z]{2,15}+$/
end


Das sollte denk ich erst mal reichen :wink:

Grüße,
Matthias


Nach oben
 Profil  
 
 Betreff des Beitrags: Re: RubyDictionary
BeitragVerfasst: 01 Apr 2010, 19:51 
Offline
Nuby

Registriert: 30 Mär 2010, 23:32
Beiträge: 4
Hallo,

Zitat:
Das Problem ist, dass du die Klasse verwendest, bevor sie überhaupt fertig definiert ist! Auf dem Klassenlevel sollte einfach noch kein Runtimecode stehen.

Wieso sollten meine Klassen nicht "fertig definiert sein", bevor das Hauptprogramm anfängt? Kann man eigentlich Klassen - so wie Module - in einzelne Dateien unterteilen und später ins Hauptprogramm inkludieren?

Zitat:
Das Problem ist, dass der Case ~100 Zeilen und damit locker länger als ein Bildschirm ist. [...]


Lösung so vielleicht:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

def load com
check_number_of_arguments!(com,2)

begin
file_contents = File.read "#{com[1]}.txt"
rescue
puts "Failed to open File"
end

$dict.insert_entries_string(file_contents)
puts "Opened"
end

# Ruby Dictionary Main Program
$dict = Dictionary.new # jetzt global
puts "Welcome to Ruby Dictonary!"
puts "Enter command (h for help):"

[...]

begin
case com[0]
# FILE I/O

# load
when "l", "load"
load com
[...]


Zitat:
Allerdings ändert sich die Semantik dadurch etwas (nämlich wenn entries auf false gesetzt sein sollte).

false und nil. In einem anderen Forum wurde folgenge Variante vorgeschlagen:


1
2
3

def initialize(entries=Array.new)
@entries = entries

Find ich persönlich am besten.

Zitat:
Wieso der Umstand mit dem Flag? Einfach das raise in den else-Zweig und gut ist.

Stimmt, hab ich bei mir geändert.

Zitat:
Das ist eine Aufgabe für Array#select:

Darauf haben mich auch andere schon aufmerksam gemacht. Ich muss mir diese Array-Methoden später nochmals anschauen.

Zitat:
Man könnte auch überlegen ob man DictionaryEntry eine Methode ...

Nette Idee, das mach ich jetzt auch :)
Muss es nicht jeweils "@word1" und "@word2" heißen?

Danke fürs durchschauen und kommentieren!



[aktuelle Version angehängt]


Dateianhänge:
rdict.rb [7.57 KiB]
130-mal heruntergeladen
Nach oben
 Profil  
 
Beiträge der letzten Zeit anzeigen:  Sortiere nach  
Ein neues Thema erstellen Auf das Thema antworten  [ 8 Beiträge ] 

Alle Zeiten sind UTC + 1 Stunde [ Sommerzeit ]


Wer ist online?

Mitglieder in diesem Forum: 0 Mitglieder und 3 Gäste


Du darfst keine neuen Themen in diesem Forum erstellen.
Du darfst keine Antworten zu Themen in diesem Forum erstellen.
Du darfst deine Beiträge in diesem Forum nicht ändern.
Du darfst deine Beiträge in diesem Forum nicht löschen.
Du darfst keine Dateianhänge in diesem Forum erstellen.

Suche nach: