파워포인트, 워드, 엑셀, 그림 파일(jpg, png) 등등을 간편하게 PDF로 변환해야 할 때가 있다.
각 프로그램으로 변환하면 정확하겠지만, 밖이거나 피시방 등에서는 쉽지 않다.
모바일 환경에서도 쉽게 사용할 수 있는 웹 변환 페이지를 소개한다.

https://adobe.ly/3w8MlzI

 

해당 사이트에서 원하는 파일을 업로드하면 PDF로 바로 변환할 수 있다.
또한, 어도비 공식 웹 사이트이기 때문에 부가적인 편집 기능도 활용 가능하다.

* 사설 서비스는 파일 수집 위험이 있지만, 어도비 공식 사이트는 비교적 안전하지 않을까 싶다.

 

변환이 완료 된 페이지

 

 

프로그래밍 언어를 설계할 때 크게 2가지 측면으로 나뉠 수 있습니다. 하나는 Syntax이고 또 하나는 Semantics입니다. 간단하게 설명하면 지켜야 하는 문법의 구조와 어떤 하나의 문장이나 단어의 역할(의미)입니다. 의미를 생각하기 전에 먼저 Syntax가 무엇인지에 대해 살펴보고자 합니다. "게으른 토끼가 달린다" 라는 문장은 어떻게 만들어진 걸까요? 사람은 자연스럽게 문장을 구성하지만, 그러한 문장을 구성하는 규칙이 있음은 너무 당연합니다. 어떤 단어가 다른 단어로 바뀌는 규칙 몇 개를 정해보겠습니다. 이 규칙은 '왼쪽'의 것이 |로 구분된 '오른쪽' 의 단어 중 하나를 선택해서 바꿀 수 있다.를 의미합니다.
* 여기서 다루는 단어는 그저 단어(이제부터 Term이라고 하겠습니다.)일 뿐 입니다. 그 의미를 생각하지 않고 단지 왼쪽의 Term이 그대로  오른쪽의 Term으로 바뀔 수 있다는 것만 생각합니다. 또한 Term을 구성하는 또 다른 Term 또한 규칙을 적용 받을 수 있음을 기본적인 전제로 일단 두겠습니다. (e.g, A->B Then A K -> B K)
'문장' -> '주어 동사' '주어' -> '명사' '명사' -> '형용사 명사' | '토끼' | '거북이' | '강아지' '형용사' -> '귀여운' | '게으른' | '예쁜' | '못생긴' '동사' -> '자동사' | '타동사 명사' '자동사' -> '달린다' | '수영한다' | '잔다' '타동사' -> '잡는다' | '부른다' | '던진다'
------이 규칙을 어떻게 적용할 수 있는 지 예시를 보겠습니다. 처음부터 제게 '문장' 이라는 Term이 주어져 있습니다. 그렇다면 각 규칙을 제가 임의로 적용하면
'문장' -> '주어 동사' -> '주어 자동사' -> '명사 자동사' -> '형용사 명사 자동사' -> '형용사 토끼 자동사' -> '형용사 토끼 달린다' -> '게으른 토끼 달린다'
라는 결과를 얻을 수 있습니다. 다소 규칙이 임의적인 감이 있지만 언어의 구체적인 문장들은 위와 같은 규칙을 통해 구성된다고 이해할 수 있습니다. 이를 통해 '거북이 수영한다' , '예쁜 강아지 던진다 귀여운 토끼' 등의 문장을 구성해 볼 수 있습니다. 


 

실제 파이썬의 Syntax 중 일부입니다. 문장이라는 Term에서 연속적으로 각각의 Term이 바뀌어가면서 하나의 복잡한 코드를 구성하게 되는 것 처럼 사진 속 규칙은 Atom이라는 작은 단위에 대한 규칙을 정의하고 있습니다. (자세한 사항은 python reference를 참고. 각 문서는 각자의 표기와 규칙이 있으니 먼저 그것을 잘 읽어보는 것도 중요합니다.) 우리의 프로그램은 완성된 코드로 동작하지만, 그 완성된 코드는 사실 하나의 뿌리에서 출발합니다.  C언어에서 가장 대표적인 규칙을 생각해보면 코드 -> 코드;코드 가 있고 이러한 룰을 기반으로 우리는 세미콜론을 가지고 코드의 문장을 연결합니다. 컴파일러나 인터프리터가 Syntax error를 잡아내는 것도, 현재 작성한 Term이 규칙에 의해서 만들어질 수 있냐 없냐를 따져보는 것 입니다. 규칙 ㅑ 코드자 이제 우리는 프로그래밍 언어의 Syntax를 이해할 수 있고, 정의할 수 있습니다. 그렇다면 이렇게 작성 된 하나의 완성된 Term은 프로그램에 있어서 어떤 의미를 갖게 될까요? 이 프로그램의 실행은 무엇으로 정의되는 걸까요? 우리는 이제 문장의 구조가 아닌 문장의 의미에 대해서 생각해야 합니다. 그리고 '의미'라는 것은 무엇이고 '실행'이라는 것은 무엇인지.. 프로그램은 결국 무엇을 하는 것인지에 대해서도 이해해야 할 필요가 생길 것 같습니다. 다음 내용은 이러한 관점에서 프로그래밍 언어의 Semantics를 이야기 하겠습니다. 감사합니다!

 

대학 교재를 다운받기 위한 사이트를 소개한다. 

Library Genesis (libgen.is)

아래의 사이트에서 대학 교재의 이름이나 저자를 입력하고 

다운로드 항목에 있는 것 버튼 중 하나를 선택하면 된다.

 

 

이때, 각 파일이 나눠져 있는 경우가 있는데 이때 여러 개의 PDF 파일을 합치기 위해서

몇 가지 프로그램을 활용할 수 있다. 본인은 PDF 유출에 대한 신경이 쓰여 웬만하면 어도비 사의 웹 서비스를 활용한다.

https://adobe.ly/4df78Ci 

위의 링크로 들어가서 간단하게 파일을 드래그하고 순서를 정해주면 파일을 웹상에서 합칠 수 있다.

타 서버에 비해서 안정적인 듯하고 합치기 뿐 아니라 다른 서비스도 많아서 괜찮다.

 

PDF 합치기: 온라인에서 무료로 PDF 병합 | 어도비 애크로뱃 (Adobe Acrobat)

어도비 애크로뱃(Adobe Acrobat) 온라인에서 무료로 PDF 파일을 병합해 보세요. 여러 PDF 파일을 결합하여 하나의 파일로 만들고 공유할 수 있습니다. 무료로 PDF합치기 해보세요!

www.adobe.com

 

우선적으로 Flutter 앱 내에 Firebase를 추가하고, 등록하는 절차를 수행해야 한다.
아래 링크에서 시키는 대로 하자.

https://firebase.google.com/docs/flutter/setup?hl=ko&platform=ios

 

Flutter 앱에 Firebase 추가

Google은 흑인 공동체를 위한 인종적 평등을 추구하기 위해 노력하고 있습니다. 자세히 알아보기 이 페이지는 Cloud Translation API를 통해 번역되었습니다. Switch to English 의견 보내기 컬렉션을 사용해

firebase.google.com

1,2,3 단계 수행 이후

flutter pub add cloud_firestore
flutter pub add firebase_messaging
flutterfire configure
flutter run

 

위의 명령어를 이용해 Flutter app에 파이어스토어와 클라우드메세지 플러그인을 추가한다.
파이어스토어가 필요한 이유는 기기마다 앱의 고유한 주소가 형성되어 그쪽으로 알림을 보내게 되는데, 서버(Python)에서 그 주소(토큰)를 저장하고 조회하는 수단으로 DB를 사용하기 위함이다.

이 포스트에서는 안드로이드 대상으로만 구현한다. ios는 추가적인 설정이 필요함.

 

/android/build.gradle 

만약 빌드 시 sdk version 관련 에러가 뜨는 경우, compileSdkVersion 을 33으로 minSdkVersion을 32로 수정한다. 
임시 조치지만, 현재로써는 이게 최선.

 

/main.dart

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'firebase_options.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  print("Handling a background message: ${message.messageId}");
}

main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  final fcmToken = await FirebaseMessaging.instance.getToken();
  await FirebaseFirestore.instance.collection("UserTokens").doc("User").set({
    'token': fcmToken,
  });
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  void initState() {
    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      final snackBar = SnackBar(content: Text(message.data.toString()));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
      ),
      body: Center(),
      floatingActionButton: FloatingActionButton(
        onPressed: (() => 0),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

맨 위에 선언된 Handler는 백그라운드를 담당하고, 포그라운드에서 메세지를 수신한 경우에는 MyHomePageState에서
실행된 FirebaseMessaging.onMessage.listen 리스너가 메세지를 수신하여 message로 콜백한다. 위의 예제는 스낵바를 이용해서 포그라운드 메세지를 출력할 뿐이다. 백그라운드 알림을 수정하기 위해서는 다른 설정을 해줘야 한다. 주의할 점은 main 에서 Firebase.InitializeApp()를 호출하는 부분에서 await 키워드를 사용했다는 점, 그리고 아래와 같이 fcmToken을 얻어서 파이어스토어에 송신한다는 점.

이제 콘솔에서 새 캠페인을 시작해서 테스트 메세지를 보내보면 백그라운드 상태와 포그라운드 상태일 때 Debug Console에서 수신했다는 메세지가 뜬다. 백그라운드 알림은 Title/body에 따라서 오는데, 포그라운드 알림은 데이터가 텅 비어 있는 것처럼 보인다. 내용물을 전달하려면 Title/body가 아니라,  추가 옵션에서 key:value를 추가해서 보내면 된다.

 

이제 fcm으로 메세지를 보내는 서버를 구현해야한다.

먼저 firebase 내 프로젝트 설정 페이지에서 키를 만든다.
그리고 아래의 python 파일을 새롭게 만들고, 키 파일의 경로를 입력한다.

 

fcm_messaging.py

import firebase_admin
from firebase_admin import credentials
from firebase_admin import messaging
from firebase_admin import firestore
cred = credentials.Certificate("키 파일 경로")
default_app = firebase_admin.initialize_app(cred)
db = firestore.client()
doc_ref = db.collection("UserTokens").document("User")
registration_token = doc_ref.get().to_dict()['token']
message = messaging.Message(
    notification=messaging.Notification(
    title="테스트 제목",
    body="테스트 내용",
),
    data={
        'score': '8501',
        'time': '2:45',
    },
    token=registration_token,
)
response = messaging.send(message)
print('Successfully sent message:', response)

간단히 설명하면 DB에 저장된 fcmToken을 불러온 뒤, 그쪽으로 메세지를 송신한다. 
쓰임새에 따라 함수로 만들어서 원하는 알고리즘에 집어넣으면 된다.

 

동일한 연산임에도 답이 다르게 나올 때가 있다.

나누기 연산의 결과는 float로 바뀌므로 아랫 자리 수가 소실됨.
int를 이용해서 캐스팅해도 막을 수 없음.

동적 프로그래밍은 n번째의 정답을 추론하기 위해 n-1 번째 정답을 이용하는 것을 의미한다.

수열에서의 가장 큰 합을 가지는 Subarray를 찾는 문제를 해설.

풀이는 다음과 같다.

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        max_num = -99999
        now_num = 0
        for i in nums:
            if(now_num < 0):
                now_num = 0
            now_num = now_num+i
            max_num = max(max_num, now_num)
        return max_num

수열 nums에 대해서 maximum subarray의 합을 구하는 것은

nums[:-2] 즉, 마지막 바로 직전까지의 수열을 이용하면 가능하다.

nums보다 하나 적은 수열에서 얻은 maximum subarray의 합이

다음 정답에 어떻게 영향을 미치는 가를 고민해보자.

 

x_n이 수열의 마지막 숫자라고 생각했을 때,
생각할 수 있는 정답은 3가지 경우 중 하나가 된다.

① 바로 이전 항을 포함하며 계속 이어지는 수열
② x_n 단 하나 (바로 이전 항까지의 합이 음수인 경우)
③ x_(n-1) 을 포함하지 않는 이전의 수열

 

여기서 헷갈릴 수 있는 부분이 x_n이라는 수를 만나기 전까지

그 직전까지의 Subarray를 생각하는 것이다. 

최대의 Subarray를 찾기 위해서는 수열의 첫번째 항부터

그 다음 수가 양수이든 음수이든 상관없이 계속 더해보면서

부분 합을 체크해야 한다. 왜냐하면 subarray를 추가하는 과정에서

중간에 음수가 나온다고 해도 그것을 포함한 수열의 합이 양수라면,

다음에 나타날 숫자들이 역대 최대의 부분합을 만들어 낼 가능성이 있기 때문이다.

( +1이라도 최대에 보탬이 될 수 있음)

다시 말해 새로운 수가 음수여서 직전의 부분합보다 감소한다고 해도

이후 또 새로운 숫자를 추가하면 그 부분합이 역대 최대가 될 수 있다는 뜻이다.

 

다만 만들던 subarray를 끊고, 새롭게 시작해야 하는 순간이 있는데

그것은 여태까지의 subarray의 합이 음수가 되는 시점이다.

이어오던 subarray를 새로운 수에 붙이면 오히려 손해이기 때문에

Subarray를 이어나갈 필요가 없이, 새로운 수를 기점으로 다시 subarray를 따진다.

 

 

 

다시 본론으로 돌아오면 n개의 수열 nums의 최대 부분합은

n-1개의 수열 nums[:-2]의 최대 부분합과,

혹시 몰라서 끌고 온 subarray를 이용해서 구할 수 있다.

 

1. 혹시 몰라서 직전의 수를 포함하면서 끌고 온 subarray + 새로운 수(x_n)

2. 새로운 수(x_n)

3. 1번과 같은 수인지 아닌지는 모르지만 지금까지 중 역대 최대였던 합

 

위의 코드에 의하면 '혹시 몰라서 직전의 수를 포함하면서 끌고 온 subarray'의 부분합은

now_num 이라는 변수에 저장되어 있다. now_num이 역대 최대의 부분합인지는 모르지만

새로운 수 x_n을 더했을 때 최대의 부분합이 될 지,

아니면 그 뒤로도 계속 이어나갈 때 어느 순간 최대의 부분합이 될지 모른다.

그렇기 때문에 now_num에 저장하면서 부분합을 저장해나가는 것이다. 

이것은 지금까지 제일 컸던 부분합을 기억하는 것과는 별개의 것이 된다.

 

물론 끌고 온 subarray가 음수라면 그것은 전혀 의미가 없다. 무조건 방해가 된다.

이때는 0으로 바꾼다. 이것은 새로운 수로부터 다시 subarray를 만들겠다는 

단순한 테크닉에 불과하다. now_num = max( now_num + i, i) 의 꼴로 생각해도 무방하다.

 

 

이전까지의 부분합에 x_n을 더하거나, 혹은 x_n으로 새로운 subarray를 시작한다.

 

최종적으로 지금까지 역대 최대의 합(nums[:-2]의 최대 부분합) max_num과

새롭게 만들어진 subarray를 비교한다. 여기서 이긴 수가 최대의 부분합이 된다.

 

 

카데인 알고리즘은 이 문제만을 풀기 위해 생각한 알고리즘이기 때문에,

단순히 이 문제를 이렇게 풀었다라는 이해를 넘어서는

동적 프로그래밍 관점에서의 포괄적인 이해가 필요하다.

우리는 n번째 항의 정답을 얻기 위해서 n-1번째 정답을 기억해놨다.

그렇지만 n-1번째 정답과 무관한 n번째 정답이 만들어질 가능성이 있었기 때문에

정답이 될 수 있는 경우를 모두 고려해봐야만 했다.

(이를 Greedy 하다고 생각할 수도 있는 것 같다.)

알고리즘의 핵심은 "이전까지의 사례"를 이용했다는 점이다.

subarray를 이어가는 것도, max_num을 기억하고 비교하는 것도 그러하다.

 

결국 직전까지의 항과 지금의 항의 '관계'와 정답의 가능성을 얼마나 잘 이해하고,

가장 효율적인 형태의 식을 정의할 수 있느냐가 동적 프로그래밍의 열쇠인듯 하다.

 

한글 파일만 탐색기 폴더 내에서 날짜가 보이지 않는다. 때문에 날짜로

정렬하게 되면 맨 밑으로 떨어져 파일을 찾기가 매우 귀찮아진다. 이상하게

즐겨찾기에서 접근하면 날짜가 안뜨는데, 직접 파일 경로로 접근하면 보이는 현상이 있다.

원인을 알 수가 없어서 짜증이 났는데 해결 방법은 의외로 간단하다.

 

폴더에서 오른쪽 클릭을 하고 속성을 누르면 다음과 같은 창이 뜨는데 여기에서

사용자 지정 - 폴더 최적화 [ 일반 항목 ] 으로 바꾸어주면 날짜가 정상적으로 표시된다.

아마 사진에 대한 정보를 날짜로 입력받은 듯 하고, 특히 한글 파일이 미리보기 등이 뜨지 않는 등

탐색기 내에서 호환성이 좋지 않아서 문제가 생긴 것 같다.

 

Ctrl + Shift + P 혹은  F1을 눌러서 Developer: Insepct Editor Tokens and Scopes 를 연다. 그리고 태그를 지정할 부분을 클릭해서 scope가 될 이름을 찾을 수 있다. 아래의 예제에서는 keyword.control.tag-name.djangostorage..... 어쩌구로 이어지는데 이 부분을 기억한다.

 

 

이후에 아래의 그림처럼 대상이 될 Theme를 찾아서 json 파일을 연다. 이 과정은 알아서 찾아야 한다.

아랫 부분을 찾아보면 위와 같은 내용이 써있을텐데 임의로 항목을 하나 만들어서 name은 적당한 걸 지정해주고 위 그림처럼 scope 부분에 내가 적용하고 싶은 항목을 적으면 된다. 하위 항목에 전부 적용되므로 keyword.control.tag-name 까지만 입력하였다. 적용되는 설정은 settings 부분에서 foreground 나 background 를 입력하면 된다.

 

완료.

 

출처 https://stackoverflow.com/questions/50243989/how-do-i-customise-the-color-of-html-tags-in-visual-studio-code

3.x 버전에서 깔린 라이브러리가 그대로 남아있는 경우 새로운 Package 를 설치할 때 발생하는 문제인 듯 하다.

스택 오버 플로우에 제시된 해결 방법으로 잘 안돼서 그냥 대충 해결 봤다.

 

.libPaths()

# [1] "/usr/local/lib/R/site-library"
# [2] "/usr/lib/R/site-library"
# [3] "/usr/lib/R/library"

해당 하는 폴더로 가서 안에 있는 폴더를 전부 삭제한다.

 

sudo rm -rf /usr/lib/R/library/*
sudo rm -rf /usr/lib/R/site-library/*

그냥 이렇게 다 지워버리고

 

sudo apt-get remove r-base-core
sudo apt-get remove r-base
sudo apt-get autoremove
sudo apt install r-base

그리고 재설치 하면 되긴 한다.

바탕화면이 뜨지 않고, 계속 새로고침 되면서 렉이 걸리는 경우가 있다.

이때, 다른 해결 방법이 통하지 않는다면 파일이 너무 많아서 불러오지 못하는 경우다.

탐색기도 실행하지 못할 가능성이 크다. 따라서 cmd를 열고, cd Desktop 명령어를 통해

직접 바탕화면 폴더로 들어가서 dir을 치면 파일 목록이 나온다.

이때 원인이 되는 파일을 del *.(확장자) 명령어로 삭제한다.

+ Recent posts