某家宠物SAAS平台笔试题部分答题

package com;

// 注意: 题目有四道, 请认真仔细读题,
//      如果有不理解的地方, 请联系 HR 或面试官,
//      如果有不会的, 请留空, 不要求做完, 不要盲目答题.

import java.util.*;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * Q1: 加权随机函数生成器
 * <p>
 * 给定一个正整数数组 input, 构造一个加权随机类实例,
 * 该实例的 {@link #next()} 方法被调用时, 随机返回一个该数组的下标, 下标 i 被返回的概率
 * 为该下标对应的元素的值 / 所有元素之和.
 * <p>
 * 要求: {@link #next()} 方法的时间复杂度不超过 O(log(N))
 */
class WeightedRandom {

    private int sum;
    private Random random;
    private TreeMap<Integer, Integer> treeMap = new TreeMap<>();

    public WeightedRandom(int[] input) {
        //init
        int len = input.length;
        for (int i = 0; i < len; i++) {
            sum += input[i];
            //current sum, index
            treeMap.put(sum, i);
        }
        random = new Random();
    }

    public int next() {
        // use treemap, search target key in O(log(N))
        final Map.Entry<Integer, Integer> higherEntry = treeMap.higherEntry(random.nextInt(sum));
        return higherEntry.getValue();
    }
}

/**
 * Q2: 字符跳动
 * <p>
 * 给定一个由不重复的小写字母组成的字符串,通过一系列跳动指令变换成一个新的字符串。
 * 跳动指令有:
 * * Move(移动),简写:M,例如:M:7 表示将字符串向右移动7位。
 * * eXchange(交换),简写:X,例如:X:a/c 表示将字符串中的 a c 位置交换。
 * * IndexChange(按位置交换),简写:I,例如:I:2/4 表示将位置2和位置4的字符交换,位置的索引从0开始计数。
 * 示例:
 * 给定初始字符串为:wosjgncxyakdbefh
 * 给定如下指令:
 * M:3   则变换后的新字符串为:efhwosjgncxyakdb
 * I:7/2  则变换后的新字符串为:efgwosjhncxyakdb
 * X:o/h  则变换后的新字符串为:efgwhsjoncxyakdb
 * 因此给定初始字符串:wosjgncxyakdbefh,在经过指令 M:3,I:7/2,X:o/h 的变换后得到新的字符串:efgwhsjoncxyakdb。
 */
class CharDance {

    private String OP_TMP = "-";

    /**
     * 题目1:
     * 给定一个随机的初始字符串: src,给定一组随机的指令: ops,(src 和 ops 一定是合法的),求经过转换后得到的新字符串。
     * 思路
     * 1、根据指令前缀做不同的操作,将操作结果返回
     * 2、封装不同的指令操作方法
     */
    public String transfer(String src, String ops) {
        //src ops is valid, dont'check here
        final String[] opsArr = ops.split(",");
        String result = src;
        for (String op : opsArr) {
            final String[] operator = op.split(":");
            String opt = operator[0];
            String opv = operator[1];
            switch (opt) {
                case "M":
                    result = opM(result, opv);
                    break;
                case "I":
                    result = opI(result, opv);
                    break;
                case "X":
                    result = opX(result, opv);
                    break;
                default:
                    break;
            }
        }
        return result;
    }

    /**
     * 题目2:
     * 将上一次转换后得到的新字符串作为初始字符串,使用相同的跳动指令集再进行转换,如此重复执行 count 次,求得到的最终字符串是什么?
     * 注意: count 足够大, 比如可能超过 2^32.
     */
    public String transferMultipleTimes(String src, String ops, long count) {
        List<String> resutList = new ArrayList<>();
        long period = 0;
        for (long i = 0; i < count; i++) {
            src = transfer(src, ops);
            if (resutList.contains(src)) {
                period = i;
                break;
            }
            resutList.add(src);
        }
        if (period > 0) {
            return resutList.get((int) ((count - 1) % period));
        }
        return src;
    }

    /**
     * 例如:M:7 表示将字符串向右移动7位。
     *
     * @param src
     * @param opv
     * @return
     */
    private String opM(String src, String opv) {
        final int index = src.length();

        int move = Integer.parseInt(opv) % index;
        if (move == 0) {
            return src;
        }
        int moveIndex = index - move;
        final String prefix = src.substring(0, moveIndex);
        final String moveStr = src.substring(moveIndex);
        return moveStr + prefix;
    }

    /**
     * 例如:I:2/4 表示将位置2和位置4的字符交换,位置的索引从0开始计数。
     *
     * @param src
     * @param opv
     * @return
     */
    private String opI(String src, String opv) {
        String[] split = opv.split("/");
        int firstIdx = Integer.parseInt(split[0]);
        int secondIdx = Integer.parseInt(split[1]);
        if (firstIdx == secondIdx) {
            return src;
        }
        final char[] chars = src.toCharArray();
        //exchange char
        char firstCh = chars[firstIdx];
        chars[firstIdx] = chars[secondIdx];
        chars[secondIdx] = firstCh;
        return new String(chars);
    }

    /**
     * 例如:X:a/c 表示将字符串中的 a c 位置交换。
     *
     * @param src
     * @param opv
     * @return
     */
    private String opX(String src, String opv) {
        String[] split = opv.split("/");
        String firstChar = split[0];
        String secondChar = split[1];
        //not exist repeat char
//        if (firstChar.equals(secondChar)) {
//            return src;
//        }

        src = src.replaceAll(firstChar, OP_TMP);
        src = src.replaceAll(secondChar, firstChar);
        src = src.replaceAll(OP_TMP, secondChar);
        return src;
    }
}

/**
 * Q3: 并发任务控制器
 * <p>
 * 注意: 不可使用 java 提供的线程池相关接口
 */
class AsyncWorker {

    private volatile int capacity;

    private volatile int RUNNING = 0;

    BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();

    private final HashSet<Worker> workers = new HashSet<Worker>();
    Map<Integer, Integer> map = new HashMap<Integer,Integer>();
    /**
     * 构造函数
     *
     * @param capacity 最大并发数量
     */
    public AsyncWorker(int capacity) {

        // show me your code

        this.capacity = capacity;

    }

    /**
     * 任务提交函数: 当前正在执行的任务数小于 capacity 时, 立即异步执行, 否则
     * 等到任意一个任务执行完成后立即执行.
     *
     * @param task 任务函数
     * @param <T>  返回值类型
     * @return 返回由 Future 包装的任务函数的返回值, 其状态应该和 task 的执行结果一致
     */
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
        // show me your code
    }

    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
        return new FutureTask<T>(callable);
    }

    private <T> void execute(RunnableFuture<T> ftask){
        if (RUNNING < capacity) {
            if (addWorker(ftask))
                return;
        }
        addWorker(ftask);
        return;

    }

    private boolean addWorker(Runnable firstTask) {
        for (; ; ) {
            if (RUNNING >= capacity)
                return false;
            RUNNING++;
            break;
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {

                try {
                    workers.add(w);
                    workerAdded = true;
                } finally {
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {

        }
        return workerStarted;

    }

    private final class Worker
            implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            this.firstTask = firstTask;
            this.thread = new Thread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

    }

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;

        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {

                try {

                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {

                    }
                } finally {
                    task = null;
                    w.completedTasks++;

                }
            }
            completedAbruptly = false;
        } finally {
            workers.remove(w);
            RUNNING--;
        }
    }

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            try {
                Runnable r = workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
}

/* ----------------- 以下是测试用例 -----------------*/

class TestWeightedRandom {

    public static void testWeightedRandom(Function<int[], WeightedRandom> factory) {
        //0--4--6--7--10--13
        // 4 -2 - 1- 3 - 3
        int[] input = {4, 2, 1, 3, 3};
        WeightedRandom random = factory.apply(input);
        int[] count = new int[input.length];
        for (int i = 0; i < 10000; i++) {
            int v = random.next();
            if (v < 0 || v >= input.length) {
                throw new RuntimeException("invalid random value: " + v);
            }
            count[v]++;
        }
        int sum = Arrays.stream(input).sum();
        for (int i = 0; i < input.length; i++) {
            double expectedWeight = (double) input[i] / (double) sum;
            double realWeight = (double) count[i] / 10000D;
            if (Math.abs(expectedWeight - realWeight) > 0.01) {
                throw new RuntimeException(
                        "invalid weight " + realWeight + " for index " + i + ", expected is " + expectedWeight
                );
            }
        }
    }
}

class TestCharDance {

    public static void testCharDance(Supplier<CharDance> factory) {
        CharDance charDance = factory.get();
        String src = "wosjgncxyakdbefh";
        String ops = "M:3,I:7/2,X:o/h";
        String dst = "efgwhsjoncxyakdb";
        String realDst = charDance.transfer(src, ops);
        if (!dst.equals(realDst)) {
            throw new RuntimeException("invalid transfer result " + realDst + ", expected is " + dst);
        }
        String dst100 = src;
        for (int i = 0; i < 127; i++) {
            dst100 = charDance.transfer(dst100, ops);
        }
        String realDst100 = charDance.transferMultipleTimes(src, ops, 127);
        if (!dst100.equals(realDst100)) {
            throw new RuntimeException("invalid transfer result " + realDst100 + " after 100 times, expected is " + dst100);
        }
    }
}

class TestAsyncWorker {

    private static AsyncWorker worker;
    private static List<Task> tasks;

    public static void testAsyncWorker(Function<Integer, AsyncWorker> factory) {
        worker = factory.apply(2);
        tasks = new ArrayList<>();

        runTask(1, 100, 100, false);
        runTask(2, 200, 200, true);
        runTask(3, 300, 400, false);
        runTask(4, 400, 600, true);
        runTask(5, 100, 500, false);
        runTask(6, 200, 700, true);
        runTask(7, 100, 700, false);
        runTask(8, 200, 900, false);

        for (Task task : tasks) {
            check(task);
        }
    }

    private static void runTask(int id, int delay, int expectedDelay, boolean fail) {
        Task task = new Task();
        task.id = id;
        task.expectedDelay = expectedDelay;
        task.fail = fail;
        long now = System.currentTimeMillis();
        task.value =
                worker.submit(() -> {
                    try {
                        Thread.sleep(delay);
                    } catch (InterruptedException ignored) {
                    }
                    long realDelay = System.currentTimeMillis() - now;
                    if (fail) {
                        throw new RuntimeException(String.valueOf(realDelay));
                    } else {
                        return (int) realDelay;
                    }
                });
        tasks.add(task);
    }

    private static class Task {

        private int id;
        private int expectedDelay;
        private boolean fail;
        private Future<Integer> value;
    }

    private static void check(Task task) {
        int realDelay;
        boolean realFail = false;
        try {
            realDelay = task.value.get();
        } catch (Exception e) {
            realDelay = Integer.parseInt(e.getCause().getMessage());
            realFail = true;
        }
        if (realFail != task.fail) {
            throw new RuntimeException(
                    "status of task " +
                            task.id +
                            " should be " +
                            (task.fail ? "failed" : "succeeded") +
                            ", rather than " +
                            (realFail ? "failed" : "succeeded")
            );
        }
        // 忽略延时误差
        if (realDelay / 100 != task.expectedDelay / 100) {
            throw new RuntimeException(
                    "delay of task " + task.id + " should be " + task.expectedDelay + ", rather than " + realDelay
            );
        }
    }
}

public class ShowMeBug {

    public static void main(String[] args) {
        TestWeightedRandom.testWeightedRandom(WeightedRandom::new);
       TestCharDance.testCharDance(CharDance::new);
       TestAsyncWorker.testAsyncWorker(AsyncWorker::new);
    }
}

  

posted @ 2023-01-13 22:12  quyf  阅读(37)  评论(0编辑  收藏  举报