1+ #!/usr/bin/env python3
2+ """
3+ Programa para convertir preguntas de Java del archivo readme.md
4+ al formato JSON de opción múltiple para react-certification-course
5+ """
6+
7+ import json
8+ import re
9+ import random
10+ import os
11+
12+ def read_java_readme ():
13+ """Lee el archivo readme.md con las preguntas de Java"""
14+ readme_path = os .environ .get ('README_PATH' , os .path .join (os .getcwd (), 'readme.md' ))
15+ try :
16+ with open (readme_path , 'r' , encoding = 'utf-8' ) as file :
17+ content = file .read ()
18+ return content
19+ except FileNotFoundError :
20+ print (f"Error: No se encontró el archivo { readme_path } " )
21+ return None
22+
23+ def extract_questions_from_readme (content ):
24+ """Extrae todas las preguntas del contenido del readme"""
25+ questions = []
26+
27+ # Patrón para encontrar preguntas con formato: **n . Pregunta**
28+ question_pattern = r'-\s*\*\*(\d+)\s*\.\s*([^*]+?)\*\*'
29+
30+ # Buscar todas las preguntas
31+ question_matches = re .finditer (question_pattern , content , re .DOTALL )
32+
33+ current_category = "Java Basics"
34+
35+ for match in question_matches :
36+ question_num = int (match .group (1 ))
37+ question_text = match .group (2 ).strip ()
38+
39+ # Determinar categoría basada en el número de pregunta y contenido
40+ category = determine_category (question_num , question_text , content )
41+
42+ # Extraer la respuesta de la pregunta
43+ answer_content = extract_answer_content (content , match .end ())
44+
45+ if answer_content :
46+ question_data = {
47+ "id" : question_num ,
48+ "category" : category ,
49+ "question" : question_text ,
50+ "answer_content" : answer_content
51+ }
52+ questions .append (question_data )
53+
54+ return questions
55+
56+ def determine_category (question_num , question_text , content ):
57+ """Determina la categoría de la pregunta basada en el número y contexto"""
58+ categories_map = {
59+ (1 , 6 ): "Java Platform" ,
60+ (7 , 15 ): "Wrapper Classes" ,
61+ (16 , 25 ): "Strings" ,
62+ (26 , 35 ): "Object-Oriented Programming" ,
63+ (36 , 45 ): "Inheritance" ,
64+ (46 , 55 ): "Collections" ,
65+ (56 , 65 ): "Exception Handling" ,
66+ (66 , 75 ): "Multithreading" ,
67+ (76 , 85 ): "Generics" ,
68+ (86 , 95 ): "Java 8 Features" ,
69+ (96 , 105 ): "Spring Framework" ,
70+ (106 , 115 ): "Database Connectivity" ,
71+ (116 , 125 ): "Web Services" ,
72+ (126 , 135 ): "Design Patterns" ,
73+ (136 , 145 ): "JVM and Memory Management" ,
74+ (146 , 155 ): "Testing" ,
75+ (156 , 165 ): "Best Practices" ,
76+ (166 , 175 ): "Performance Optimization" ,
77+ (176 , 185 ): "Security" ,
78+ (186 , 195 ): "Frameworks" ,
79+ (196 , 205 ): "Advanced Topics" ,
80+ (206 , 215 ): "Enterprise Development" ,
81+ (216 , 230 ): "Modern Java Features"
82+ }
83+
84+ # Buscar la categoría apropiada
85+ for (start , end ), category in categories_map .items ():
86+ if start <= question_num <= end :
87+ return category
88+
89+ # Categoría por defecto o basada en contenido
90+ question_lower = question_text .lower ()
91+ if any (word in question_lower for word in ['string' , 'immutable' , 'stringbuilder' ]):
92+ return "Strings"
93+ elif any (word in question_lower for word in ['collection' , 'list' , 'set' , 'map' ]):
94+ return "Collections"
95+ elif any (word in question_lower for word in ['thread' , 'synchronized' , 'concurrent' ]):
96+ return "Multithreading"
97+ elif any (word in question_lower for word in ['exception' , 'try' , 'catch' , 'finally' ]):
98+ return "Exception Handling"
99+ elif any (word in question_lower for word in ['jvm' , 'memory' , 'garbage' ]):
100+ return "JVM and Memory Management"
101+ else :
102+ return "Java Basics"
103+
104+ def extract_answer_content (content , start_pos ):
105+ """Extrae el contenido de la respuesta después de una pregunta"""
106+ # Buscar desde la posición actual hasta la siguiente pregunta
107+ next_question_pattern = r'-\s*\*\*\d+\s*\.\s*[^*]+?\*\*'
108+ next_match = re .search (next_question_pattern , content [start_pos :])
109+
110+ if next_match :
111+ end_pos = start_pos + next_match .start ()
112+ answer_section = content [start_pos :end_pos ]
113+ else :
114+ # Si no hay más preguntas, tomar hasta el final
115+ answer_section = content [start_pos :]
116+
117+ return answer_section .strip ()
118+
119+ def create_multiple_choice_question (question_data ):
120+ """Convierte una pregunta en formato de opción múltiple"""
121+ question_text = question_data ["question" ]
122+ answer_content = question_data ["answer_content" ]
123+
124+ # Extraer la respuesta correcta del contenido
125+ correct_answer = extract_correct_answer (answer_content )
126+
127+ # Generar opciones incorrectas (distractores)
128+ distractors = generate_distractors (question_text , correct_answer )
129+
130+ # Mezclar opciones
131+ all_options = [correct_answer ] + distractors
132+ random .shuffle (all_options )
133+
134+ # Encontrar la posición de la respuesta correcta
135+ correct_index = all_options .index (correct_answer )
136+ answer_letter = chr (ord ('a' ) + correct_index )
137+
138+ # Generar explicación
139+ explanation = generate_explanation (answer_content )
140+
141+ return {
142+ "id" : question_data ["id" ],
143+ "category" : question_data ["category" ],
144+ "question" : question_text ,
145+ "options" : all_options ,
146+ "answer" : answer_letter ,
147+ "explanation" : explanation
148+ }
149+
150+ def extract_correct_answer (answer_content ):
151+ """Extrae la respuesta correcta del contenido de la respuesta"""
152+ # Buscar puntos clave en la respuesta
153+ lines = answer_content .split ('\n ' )
154+
155+ # Tomar las primeras líneas más relevantes
156+ relevant_lines = []
157+ for line in lines [:10 ]: # Primeras 10 líneas
158+ line = line .strip ()
159+ if line and not line .startswith ('-' ) and not line .startswith ('*' ):
160+ if len (line ) > 20 and len (line ) < 200 : # Longitud apropiada para opción
161+ relevant_lines .append (line )
162+
163+ if relevant_lines :
164+ return relevant_lines [0 ]
165+ else :
166+ # Respuesta genérica si no se puede extraer
167+ return "Esta es la respuesta correcta basada en el contenido de Java."
168+
169+ def generate_distractors (question_text , correct_answer ):
170+ """Genera opciones incorrectas plausibles"""
171+ question_lower = question_text .lower ()
172+
173+ # Distractores generales para diferentes tipos de preguntas
174+ general_distractors = [
175+ "Esta opción es incorrecta para esta pregunta de Java." ,
176+ "Esta no es la respuesta correcta según los estándares de Java." ,
177+ "Esta opción no aplica a este concepto de programación Java."
178+ ]
179+
180+ # Distractores específicos por tema
181+ if any (word in question_lower for word in ['jvm' , 'platform' , 'bytecode' ]):
182+ specific_distractors = [
183+ "Java se compila directamente a código máquina específico de la plataforma." ,
184+ "Java requiere recompilación para cada sistema operativo diferente." ,
185+ "Java no puede ejecutarse en diferentes sistemas operativos."
186+ ]
187+ elif any (word in question_lower for word in ['string' , 'immutable' ]):
188+ specific_distractors = [
189+ "Los objetos String en Java son completamente mutables." ,
190+ "StringBuffer y StringBuilder son inmutables en Java." ,
191+ "Java no tiene soporte para cadenas de texto inmutables."
192+ ]
193+ elif any (word in question_lower for word in ['wrapper' , 'boxing' ]):
194+ specific_distractors = [
195+ "Java no soporta conversión automática entre primitivos y objetos." ,
196+ "Los tipos wrapper consumen menos memoria que los primitivos." ,
197+ "Autoboxing solo funciona con tipos numéricos, no con boolean o char."
198+ ]
199+ elif any (word in question_lower for word in ['collection' , 'list' , 'set' ]):
200+ specific_distractors = [
201+ "Las colecciones en Java solo pueden almacenar tipos primitivos." ,
202+ "ArrayList y LinkedList tienen el mismo rendimiento en todas las operaciones." ,
203+ "Set permite elementos duplicados en Java."
204+ ]
205+ elif any (word in question_lower for word in ['thread' , 'synchronized' ]):
206+ specific_distractors = [
207+ "Java no soporta programación concurrente nativa." ,
208+ "Synchronized solo funciona con métodos estáticos." ,
209+ "Los threads en Java no pueden compartir memoria."
210+ ]
211+ else :
212+ specific_distractors = [
213+ "Esta funcionalidad no existe en Java." ,
214+ "Java no soporta esta característica hasta versiones muy recientes." ,
215+ "Esta es una característica exclusiva de otros lenguajes de programación."
216+ ]
217+
218+ # Combinar distractores y seleccionar 3
219+ all_distractors = specific_distractors + general_distractors
220+
221+ # Asegurar que no repetimos la respuesta correcta
222+ unique_distractors = [d for d in all_distractors if d != correct_answer ]
223+
224+ # Seleccionar 3 distractores únicos
225+ selected_distractors = []
226+ for distractor in unique_distractors :
227+ if len (selected_distractors ) < 3 :
228+ selected_distractors .append (distractor )
229+
230+ # Si necesitamos más distractores, generar algunos genéricos
231+ while len (selected_distractors ) < 3 :
232+ generic = f"Opción incorrecta número { len (selected_distractors ) + 1 } para esta pregunta."
233+ selected_distractors .append (generic )
234+
235+ return selected_distractors [:3 ]
236+
237+ def generate_explanation (answer_content ):
238+ """Genera una explicación basada en el contenido de la respuesta"""
239+ # Tomar las primeras líneas del contenido como explicación
240+ lines = answer_content .split ('\n ' )
241+ explanation_parts = []
242+
243+ for line in lines [:5 ]: # Primeras 5 líneas
244+ line = line .strip ()
245+ if line and not line .startswith ('-' ) and not line .startswith ('*' ):
246+ if len (line ) > 10 :
247+ explanation_parts .append (line )
248+
249+ if explanation_parts :
250+ explanation = ' ' .join (explanation_parts [:2 ]) # Tomar las dos primeras partes
251+ # Limpiar la explicación
252+ explanation = re .sub (r'\*\*([^*]+)\*\*' , r'\1' , explanation ) # Remover markdown bold
253+ explanation = re .sub (r'```[^`]*```' , '' , explanation ) # Remover bloques de código
254+ explanation = explanation .replace (' ' , ' ' ).strip ()
255+ return explanation if len (explanation ) < 500 else explanation [:500 ] + "..."
256+
257+ return "Esta es la respuesta correcta basada en las mejores prácticas y estándares de Java."
258+
259+ def main ():
260+ """Función principal"""
261+ print ("Iniciando conversión de preguntas de Java..." )
262+
263+ # Leer el archivo readme
264+ content = read_java_readme ()
265+ if not content :
266+ return
267+
268+ print ("Archivo leído correctamente. Extrayendo preguntas..." )
269+
270+ # Extraer preguntas
271+ questions_data = extract_questions_from_readme (content )
272+ print (f"Encontradas { len (questions_data )} preguntas" )
273+
274+ # Convertir a formato de opción múltiple
275+ java_questions = []
276+ for i , question_data in enumerate (questions_data ):
277+ print (f"Procesando pregunta { i + 1 } /{ len (questions_data )} : { question_data ['question' ][:50 ]} ..." )
278+ try :
279+ mc_question = create_multiple_choice_question (question_data )
280+ java_questions .append (mc_question )
281+ except Exception as e :
282+ print (f"Error procesando pregunta { question_data ['id' ]} : { e } " )
283+ continue
284+
285+ print (f"Procesadas { len (java_questions )} preguntas exitosamente" )
286+
287+ # Guardar en archivo JSON
288+ script_dir = os .path .dirname (os .path .abspath (__file__ ))
289+ output_dir = os .path .join (script_dir , "./data" )
290+ output_path = os .path .join (output_dir , "java-questions.json" )
291+
292+ # Ensure output directory exists
293+ os .makedirs (output_dir , exist_ok = True )
294+ print (f"Guardando preguntas en { output_path } ..." )
295+
296+ try :
297+ with open (output_path , 'w' , encoding = 'utf-8' ) as f :
298+ json .dump (java_questions , f , ensure_ascii = False , indent = 2 )
299+
300+ print (f"Archivo guardado exitosamente en: { output_path } " )
301+ print (f"Total de preguntas generadas: { len (java_questions )} " )
302+
303+ # Mostrar algunas estadísticas
304+ categories = {}
305+ for q in java_questions :
306+ cat = q ['category' ]
307+ categories [cat ] = categories .get (cat , 0 ) + 1
308+
309+ print ("\n Distribución por categorías:" )
310+ for cat , count in sorted (categories .items ()):
311+ print (f" { cat } : { count } preguntas" )
312+
313+ except Exception as e :
314+ print (f"Error guardando el archivo: { e } " )
315+
316+ if __name__ == "__main__" :
317+ main ()
0 commit comments