거창하진 않지만 간략하게 해당 코드를 설명하자면 사용자의 위치를 기반으로 주변 음식점을 찾을 수 있고, 사용자의 이전 선택을 기반으로 한 추천 목록을 제공하여 새로운 음식점을 찾는 데 도움을 주는 말 그대로 사용자의 취향을 분석하여 음식점을 추천해주는 간단한 프로그램을 React와 TypeScript를 통하여 구현해보았다.
사용 기술
React.js: 사용자 인터페이스 구현
TypeScript: 타입 검사 및 안정성 제공
LocalStorage: 사용자 취향 데이터 저장 (별도의 서버 구축이 없어 DB로 사용)
Google Places API: 주변 음식점 정보 제공
코사인 유사도 알고리즘: 사용자의 취향과 음식점의 특징을 비교하여 가장 적합한 음식점을 추천
주요 기능
Foodie-Finder의 주요 기능은 사용자의 위치를 기반으로 주변 음식점을 찾아주는 것과 사용자가 과거에 선택한 음식점을 기반으로 새로운 음식점을 추천해주는 기능이다. 이를 위해 사용자의 선택이 LocalStorage에 저장되고, 이 정보는 추후 사용자의 취향을 분석하여 음식점을 추천하는 데 사용된다.
음식점 정보는 Google Maps API를 통해 가져오며, 각 음식점은 별점(rating), 가격(price_level), 음식점 타입(types) 이렇게 3가지의 정보를 가지고 있고, 이러한 정보들은 사용자의 취향과 비교하기 위해 벡터로 변환되고, 코사인 유사도를 계산하는 데 사용된다.
// utils.ts
...
const typeToNumber: { [index: string]: number } = {
'restaurant': 0,
'food': 1,
'point_of_interest': 2,
'establishment': 3,
'bar': 4,
'cafe': 5,
'meal_takeaway': 6,
'meal_delivery': 7,
'bakery': 8,
// 추가 type 값들이 있다면 추가
};
function convertTypeToNumber(type: string): number {
return typeToNumber[type] || 0; // 만약 알려지지 않은 타입이라면 음식점의 기본값인 0을 반환
}
...
export const loadUserVectorFromLocalStorage
= (): { price_level: number, rating: number, types: number } => {
const userVectorString = localStorage.getItem('selectedRestaurant');
if (userVectorString) {
return JSON.parse(userVectorString)
as { price_level: number, rating: number, types: number };
}
return { price_level: 0, rating: 0, types: 0 };
}
export const calculateCosineSimilarity
= (userVector: { price_level: number, rating: number, types: number },
restaurantData: Restaurant[]): Restaurant[] => {
let userVectorArray = Object.values(userVector);
return restaurantData.map((restaurant: Restaurant) => {
// restaurant를 비교할 수 있는 Vector의 형태로 변환
const restaurantVector = [restaurant.price_level || 0, restaurant.rating
|| 0, restaurant.types ? convertTypeToAverageNumber(restaurant.types) : 0];
if (userVector) {
restaurant.cosineSimilarity
= cosineSimilarity(userVectorArray, restaurantVector);
}
return restaurant;
});
}
이 코드는 사용자의 취향에 기반한 음식점을 추천하기 위한 기능을 구현하기 위해 작성된 코드의 일부이다.
convertTypeToNumber 함수: types의 문자배열의 값들을 숫자로 변환시키는 함수
loadUserVectorFromLocalStorage 함수: localStorage에서 사용자의 선호도 벡터를 로드하는 기능을 수행
calculateCosineSimilarity 함수: 사용자 벡터와 각각의 음식점 벡터 사이의 코사인 유사도를 계산
TypeScript의 관점에서 작성된 특징
정적 타입 체킹
TypeScript의 가장 큰 특징 중 하나는 정적 타입 체킹을 제공한다는 것이다. TypeScript는 JavaScript의 슈퍼셋이므로, JavaScript의 모든 기능을 포함하면서 추가적으로 타입을 부여할 수 있다. 이 코드에서는 { price_level: number, rating: number, types: number }와 같은 타입을 정의하고 있습니다. 이는 사용자의 취향 벡터를 나타내는 객체이며, 이 객체는 price_level, rating, types 세 가지 속성을 갖고, 모두 숫자 타입으로 관리된다. 이렇게 타입을 지정하면, 코드 작성 시 이 변수나 객체가 어떤 속성을 가져야 하는지, 어떤 타입의 값을 가질 수 있는지 명확하게 알 수 있다.
최초의 코드 작성 시에는 typeToNumber 함수를 만들지 않고, types에 API에서 제공받는 문자배열들을 그대로 저장하여 벡터를 구현하려 하였다. 그때는 해당 객체의 속성들을 { price_level: number, rating: number, types: string[] }와 같이 정의하여 관리하였다.
명시적인 반환 타입
TypeScript에서는 함수의 반환 타입도 명시적으로 지정할 수 있다. 예를 들어 convertTypeToNumber 함수에서는 반환 타입을 number로 명시하고, loadUserVectorFromLocalStorage 함수에서는 반환 타입을 { price_level: number, rating: number, types: number }로 명시하고 있다. 해당 TypeScript의 특징은 코드의 가독성을 높이며, 예상치 못한 오류를 줄이는데 도움이 될 수 있다.
인터페이스와 타입
TypeScript에서는 interface와 type 키워드를 사용해 복잡한 데이터 구조를 정의할 수 있습니다. 이 프로젝트에서는 음식점별 정보를 관리하기 위해 Restaurant interface로 사용하여 음식점 데이터의 구조를 정의하고 있습니다. 이러한 type 정의는 코드의 가독성을 높이고 데이터를 효율적으로 관리할 수 있게 해준다.
모듈화
TypeScript는 ES6 모듈 시스템을 따르며, 이 코드에서도 export 키워드를 사용하여 함수들을 내보내고 있습니다. 이렇게 하면 다른 TypeScript 파일에서 이 함수들을 가져와 사용할 수 있게 됩니다
이처럼 TypeScript는 정적 타입 체킹, 명시적인 반환 타입, 인터페이스와 타입 정의, 모듈화 등의 기능을 통해 코드의 안정성, 가독성, 유지 보수성을 높여줄 수 있다.
(해당 작업물은 이러한 TypeScript의 특징을 잘 활용하여 사용자의 취향에 맞는 음식점을 추천하는 알고리즘을 구현하였다. TypeScript의 활용 방안과 특징에 대해서 설명하기위해 작성된 포스팅이기 때문에 해당 코드를 구현하였던 자세한 내용에 대해서는 해당 포스팅에서는 다루지 않으려 한다.)