Coding/Today I Learned (148)

06
19

키패드는 죄가 없는걸요

1. 문제의 발단

  • chatGPT의 도입으로, 서비스에 대화형 인공지능 기능을 개발했다.
  • 개발은 그리 어렵지 않았다.(사실 백엔드 처리가 주요 이슈였기 때문에 에러 응답 처리 및 로딩 애니메이션, 유저 인터렉션, 컴포넌트 구현중 레이아웃 흐트러짐등 정도만 하면 되는 일이었다.)
  • 하지만 생각지도 못 한 복병이 있었다. 모바일 환경에서 대화를 위한 모바일 키패드가 화면을 절반 이상 가려버렸다.

2. 환경 세팅

  • 먼저, 개발 환경의 PC 상황에서는 키패드를 열 수 조차 없었다.
  • 하지만 검색결과 Nexus5, Nexus5X 기기에서는 완벽하진 않아도 적당히 열 수 있는 방법이 있다고 했다. 

'크기' 옆의 기기 종류를 눌러 추가 시켜주고, 가장 오른쪽의 기기 회전 아이콘에서 키보드로 바꿔주면 된다.
이렇게 다 열린다. 물론 보이는 화면은 매우 작다.

3. 어떻게 처리 할 것인가?

  • 내용이 들어갈 공간을 window.innerHeight로 잡고 스크롤을 만들어 준다.
import React, { useState } from 'react';

const Container = () => {
	// state로 innerHeight를 잡아 줬다.
	const [innerHeight, setInnerHeight] = useState(0);
    
    // innerHeight가 바뀔경우 다시 계산한다.
    useEffect(() => {
    	setInnerHeight(window.innerHeight);
  	}, [window.innerHeight]);
	
    // 위 헤더나, 푸터가 있을경우 innerHeight - 여분의 사이즈로 맞출 수 도 있다.
    return (
    	<div className={tw`h-[${window.innerHeight}px]`}>
        	<div className={tw`h-[${innerHeight}px])`}>뭔가 굉장히 길어질 수 있는 컨텐츠</div>
        </div>
    )
}

4. 여담

  • 위의 코드는 예시일 뿐이라 리소스 낭비가 심하다. useEffect에 timeout으로 유저의 동작이 멈췄을 경우만 innerHeight를 계산하는 방식이 옳을 것이다.
  • useMemo를 이용하는 방법도 생각해 봤는데, 스크롤은 변화가 심해서 더 낭비일 것이다.
  • 프로젝트 초기 세팅이 twind라서 그 기반으로 예시 코드를 작성했다. 해석에는 문제가 없을 것이다. (사실 질문 환영)
  • 확실한 것은 다 구현이후, 올바르게 동작하는 것은 확인했지만, 처음부터 모바일 구성이 기획에 없었어서 급하게 사이즈만 줄여 넣자 라고 아쉽게 구현되었다. 처음에는 상단 헤더 또한 큰 사이즈를 차지해 곤란한 상황. 부랴부랴 맞춰 넣은 감이 없지 않다.
COMMENT
 
06
12

1. 문제링크

https://school.programmers.co.kr/learn/courses/30/lessons/134240

 

2. 입 출력 예시

food result
[1, 3, 4, 6] '1223330333221'
[1, 7, 1, 2] '111303111'

 

3. 풀이

function solution(food) {
    // 짝수가 아니면 제외한다. 둘이서 먹을 수 없다.
    const useFood = food.map((el) => Math.floor(el / 2));
    
    // 빈 배열을 선언하고,
    const result = [];
    
    // 가장 앞의 물인 0을 제외, 반복문을 통해 물을 뺀 index + 1을 result에 push한다.
    const arr = useFood.slice(1).map((el, index) => {
        let i = 0;
        while(i < el) {
            i++;
            result.push(index + 1)
        }
    })
    
    // 그리고 완성된 result를 복사하여 반대로 뒤집는다.
    // 여기서 result를 복사하지 않으면 원본 배열도 뒤집히기 때문에 복사를 꼭 해준다.
    const reverseArr = result.slice().reverse();
    
    // 물인 0을 push하고,
    result.push(0)
    // result와 뒤집힌 result를 합친후 join으로 string형태로 바꿔준다.
    return result.concat(reverseArr).join('');
}

 

  • isNull은 너무 간단하여 다시금, 간단한 문제도 풀어보았다.
  • while문을 쓴 게 아쉽다. map이나 reduce로 한 번에 한 줄로도 처리할 수 있을 법하다.
COMMENT
 
06
12

- 예시

_.isNull(null);
// => true
 
_.isNull(void 0);
// => false

- 구현

const isNull = (item) => {
	return item === null
}

const isFalsy = (item) => {
	return !!item
}

- 소감

  • 가끔은 꿀 빠는 날도 있어야지
  • isNull은 간단해서 isFalsy도 만들었다.
  • type지정 시 유리하게 사용될 수 있는 연산자들이 많다. '&&' (true 일 경우 return 아니면 null) 라든가, '??' (true일 경우 item return 아닐 경우 '??' 이후의 값 return) 라든가 '!!' (false의 다시 반대, 즉 true) 등등
COMMENT
 
06
10

- 예시

_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]
 
_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]

_.chunk(['a', 'b', 'c', 'd', 'e', 'f'], 2);
// => [['a', 'b'], ['c', 'd'], ['e', 'f']]

_.chunk(['a', 'b', 'c', 'd', 'e', 'f', 'g'], 3);
// =>  [['a', 'b', 'c'], ['d', 'e', 'f'], ['g']]

- 구현

const chunk = (arr, size) => {
  const result = [];
  
  // arr 원본을 수정하지 않기 위해, slice로 복사한다.
  const copyArr = arr.slice();

  // 배열을 돌면서,
  copyArr.map(() => {
    // splice로 size만큼 원본 배열을 빼면서,
    const temp = copyArr.splice(0, size)
    // 임시로 만든 result배열로 넣어서 이중배열 구조를 만든다.
    result.push(temp)
  })
  
  // splice로 배열을 빼버리면서 map으로 탐색 하고 있으므로, 
  // copyArr를 전부 돌고 나서, 남은 요소들을을 result와 합쳐서 return한다.
  return result.concat([copyArr]);
}

- 소감

  • 오랜만이니까 제일 앞에 거 해야지 하다가 혈압 올라서 요단강 래프팅 투어 갈 뻔했다. 무시하지 말자...
  • 생각보다 어렵다... 마지막 남은 것 처리는 어떻게 하지? 혹은, reduce로 한 번에 처리할 순 없을까?, 제귀로 처리할까? 와 같은 별거 아닌 고민들로 시간을 많이 날렸다.
  • 습관적으로 알고리즘을 보면 제귀함수부터 쓰려고 한다. 효율적인 방법도 아니고, 코드 가독성도 그리 좋지 못하니 배열을 순회하는 함수를 조금 더 사용하면 좋을 것 같다.
  • slice로 배열을 카피해서 사용하는 것이 아무래도 조금 불편하다. reduce로 한 번에 처리하고 싶다...!
COMMENT